Initializing private HashMap<Character, HashMap> root;
at declaration will simplify your code
public class Trie {
private HashMap<Character, HashMap> root = new HashMap<>();
private final Character END_CHARACTER = '$';
public Trie() {}
public Trie(String s) {
add(s);
}
public Trie(Collection<String> collection) {
for (String s : collection) {
add(s);
}
}
public void add(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (!node.containsKey(character)) {
node.put(character, new HashMap<Character, HashMap>());
}
node = node.get(character);
}
node.put(END_CHARACTER, new HashMap<>());
}
public boolean contains(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (node.containsKey(character)) {
node = node.get(character);
} else {
return false;
}
}
return node.containsKey(END_CHARACTER);
}
}
From whats-wrong-with-overridable-method-calls-in-constructors
Simply put, this is wrong because it unnecessarily opens up possibilities to MANY bugs. When the @Override is invoked, the state of the object may be inconsistent and/or incomplete.
This having in mind we will refactor the class, to add a private internalAdd()
method which we call from the constructor and the public add()
methods
public class Trie {
private HashMap<Character, HashMap> root = new HashMap<>();
private final Character END_CHARACTER = '$';
public Trie() {}
public Trie(String s) {
internalAdd(s);
}
public Trie(Collection<String> collection) {
for (String s : collection) {
internalAdd(s);
}
}
public void add(String s) {
internalAdd(s);
}
private void internalAdd(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (!node.containsKey(character)) {
node.put(character, new HashMap<Character, HashMap>());
}
node = node.get(character);
}
node.put(END_CHARACTER, new HashMap<>());
}
public boolean contains(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (node.containsKey(character)) {
node = node.get(character);
} else {
return false;
}
}
return node.containsKey(END_CHARACTER);
}
}
but we can do better.. let us take a look at internalAdd()
private void internalAdd(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (!node.containsKey(character)) {
node.put(character, new HashMap<Character, HashMap>());
}
node = node.get(character);
}
node.put(END_CHARACTER, new HashMap<>());
}
If node.isEmpty()
we won't need to check if (!node.containsKey(character))
anymore.
Also if (!node.containsKey(character))
evaluates one time to true
, we won't need to check this anymore. Let us add a new method:
private void internalAdd(String s, HashMap<Character, HashMap> node) {
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
node.put(character, new HashMap<>());
node = node.get(character);
}
}
and call it
private void internalAdd(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (node.isEmpty() || !node.containsKey(character)) {
internalAdd(s.substring(i), node);
break;
}
node = node.get(character);
}
node.put(END_CHARACTER, new HashMap<>());
}
But wait, we can do even better, as node.isEmpty()
should also be used in the contains()
method
public boolean contains(String s) {
HashMap<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
if(node.isEmpty()){
return false;
}
Character character = s.charAt(i);
if (node.containsKey(character)) {
node = node.get(character);
} else {
return false;
}
}
return node.containsKey(END_CHARACTER);
}
Finished ? No, we still can do better. As the passed String parameters won't be changed, let us make them final
the same is true for the root
field. Also as janos has answered
use interface types instead of the implementation
Putting altogether
public class Trie {
private final Map<Character, HashMap> root = new HashMap<>();
private final Character END_CHARACTER = '$';
public Trie() {
}
public Trie(final String s) {
internalAdd(s);
}
public Trie(final Collection<String> collection) {
for (String s : collection) {
internalAdd(s);
}
}
public void add(final String s) {
internalAdd(s);
}
private void internalAdd(final String s) {
Map<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
if (node.isEmpty() || !node.containsKey(character)) {
internalAdd(s.substring(i), node);
break;
}
node = node.get(character);
}
node.put(END_CHARACTER, new HashMap<>());
}
private void internalAdd(final String s, Map<Character, HashMap> node) {
for (int i = 0; i < s.length(); i++) {
Character character = s.charAt(i);
node.put(character, new HashMap<>());
node = node.get(character);
}
}
public boolean contains(final String s) {
Map<Character, HashMap> node = root;
for (int i = 0; i < s.length(); i++) {
if (node.isEmpty()) {
return false;
}
Character character = s.charAt(i);
if (node.containsKey(character)) {
node = node.get(character);
} else {
return false;
}
}
return node.containsKey(END_CHARACTER);
}
}