I looked at your TST.java, which contains this:
private class Node {
private char c; // character
private Node left, mid, right; // left, middle, and right subtries
private Value val; // value associated with string
}
And, that can be seen as containing a letter, references to its children, and a flag (indicating, I expect, the completion of a word).
So, a single letter, a flag, and pointers..well, a single character does not represent an entire word, or key. Is it, then, a 'node'?
Or is it an 'edge'? Which, is only meant as a whimsical point. But also, I wonder if this could be realized with an array of integers. That's what the computer is doing anyways, implicitly or explicitly (memory is a big array). Suppose that you've got 400,000 of these Nodes, and it takes 15 megs of memory. At the opposite end, even an 'int' is, like, four bytes, do we need four bytes? Maybe..but, while we're into this matter of how building a trie can take (waste) a lot of memory, consider this trie, with two words in it.
h-->e-->l-->l-->o
j-->e-->l-->l-->o
You could first build the trie, and look for duplicate branches. Also, it might be much better, if one could create a deterministic acyclic finite state automaton (as they are called), right away. I'm sure I can find a Python implementation. Although, there's still a problem, if you wind up w/this:
h-->e-->l-->l-->o
j---^
It's not possible to retrieve values associated with the words. Since the last node in a word is shared with other words, it is not possible to store data in it.So, that means, we use an additional data structure, we can store it in a hash table. Look up a word,
run it through a hash function, which returns a number. Look at all the items in that bucket to find the data.
We know all the keys in advance, so, we could throw away the keys of the hash table. Easy, minimal perfect (minimal, one entry for each key, perfect, no collisions) hashing, I'm, here too, sure I can find a Python implementation..
Come to think of it, there is the hash function itself, I mean, in Java, there are two methods that a class needs to override to
make objects of that class work as hash map keys.
public int hashCode();
public boolean equals(Object o);
But, never mind that, that Node class (above) could include a Hashtable<Character, Node> children;
And, I mean, children = new Hashtable<Character, Node>();
You're using left, mid, right nodes, though I think you could just have
children.put(letter, child);
Because, it's a hashmap. Anyways, just remembering my Java. If it goes into Python, I can see wanting to do this kind of optimization, I do think this is useful to explore, at least, as the kind of thing we might occasionally want to do. But, it might also be good to remember, that being an evil genius is more than code efficacy (true evil comes from the passion to crush the will of men).