Search code examples
javatreeprefixtrie

Java: make prefix tree remember last value that was not null


I have code for prefix tree (Trie) like this:

public class Trie<V> {

Entry<V> entry;
char key;
Map<Character, Trie<V>> childrens;

public Trie() {
    this.childrens = new HashMap<Character, Trie<V>>(10);
    entry = new Entry<V>();
}

/** non-public, used by _put() */
Trie(char key) {
    this.childrens = new HashMap<Character, Trie<V>>(10);
    this.key = key;
    entry = new Entry<V>();
}

public void put(String key, V value) {
    _put(new StringBuffer(key), new StringBuffer(""), value);
}

void _put(StringBuffer remainder, StringBuffer prefix, V value) {
    if (remainder.length() > 0) {
        char keyElement = remainder.charAt(0);
        Trie<V> t = null;
        try {
            t = childrens.get(keyElement);
        } catch (IndexOutOfBoundsException e) {
        }
        if (t == null) {
            t = new Trie<V>(keyElement);
            childrens.put(keyElement, t);
        }
        prefix.append(remainder.charAt(0));
        t._put(remainder.deleteCharAt(0), prefix, value);
    } else {
        this.entry.value = value;
        this.entry.prefix = prefix.toString();
    }

}

/**
 * Retrieves element from prefix table matching as a prefix to provided key.
 * E.g. is key is "abcde" and prefix table has node "ab" then this call will
 * return "ab"
 * 
 * @param key
 *            a string which starts with prefix to be searched in the table
 *            (e.g. phone number)
 * @return an Object assosiated with matching prefix (i.e if key is a phone
 *         number it may return a corresponding country name)
 */
public V get(String key) {
    return _get(new StringBuffer(key), 0);
}

/**
 * Returns true if key has matching prefix in the table
 */
public boolean hasPrefix(String key) {
    return ((this.get(key) != null) ? true : false);
}

V _get(StringBuffer key, int level) {
    if (key.length() > 0) {
        Trie<V> t = childrens.get(key.charAt(0));
        if (t != null) {
            return t._get(key.deleteCharAt(0), ++level);
        } else {
            return (level > 0) ? entry.value : null;
        }

    } else {
        return entry.value;
    }
}

@Override
public String toString() {
    return "Trie [entry=" + entry + ", key=" + key + ", childrens="
            + childrens + "]";
}

static public class Entry<V> {
    String prefix;
    V value;

    public Entry() {
    }

    public Entry(String p, V v) {
        prefix = p;
        value = v;
    }

    public String prefix() {
        return prefix;
    }

    public V value() {
        return value;
    }

    @Override
    public String toString() {
        return "Entry [prefix=" + prefix + ", value=" + value + "]";
    }

}
}

Insertion goes like this:

private static Trie<String> trie = new Trie<>();
trie.put("7", "Some country");
trie.put("77", "Some other country");
trie.put("745", "Entirely different place");

Searching will go like this:

String result = trie.get("746878788");
System.out.println(result);

This search will result null because there is no value for 74.

My question is: how can I modify _get method in Trie class so that it will remember last value that is not null. So when it ends up in 74, then it will remember that 7 had some value "Some country", so it will return that instead of null.

Any ideas how to solve this?

Any help is appreciated!


Solution

  • First of all, I modified the toString() method of the Trie to get some better debug information. The only important rows to achieve what you are asking are these in _get method:

    V result = t._get(key.deleteCharAt(0), ++level);
    return result == null ? entry.value : result;
    

    The trie now prefers current entry's value, if subentry's value is null.

    The whole code modified:

    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    
    public class Trie<V> {
    
        Entry<V> entry;
        char key;
        Map<Character, Trie<V>> children;
    
        public Trie() {
            this.children = new HashMap<Character, Trie<V>>(10);
            entry = new Entry<V>();
        }
    
        /** non-public, used by _put() */
        Trie(char key) {
            this.children = new HashMap<Character, Trie<V>>(10);
            this.key = key;
            entry = new Entry<V>();
        }
    
        public void put(String key, V value) {
            _put(new StringBuffer(key), new StringBuffer(""), value);
        }
    
        void _put(StringBuffer remainder, StringBuffer prefix, V value) {
            if (remainder.length() > 0) {
                char keyElement = remainder.charAt(0);
                Trie<V> t = null;
                try {
                    t = children.get(keyElement);
                } catch (IndexOutOfBoundsException e) {
                }
                if (t == null) {
                    t = new Trie<V>(keyElement);
                    children.put(keyElement, t);
                }
                prefix.append(remainder.charAt(0));
                t._put(remainder.deleteCharAt(0), prefix, value);
            } else {
                this.entry.value = value;
                this.entry.prefix = prefix.toString();
            }
    
        }
    
        /**
         * Retrieves element from prefix table matching as a prefix to provided key.
         * E.g. is key is "abcde" and prefix table has node "ab" then this call will
         * return "ab"
         * 
         * @param key
         *            a string which starts with prefix to be searched in the table
         *            (e.g. phone number)
         * @return an Object assosiated with matching prefix (i.e if key is a phone
         *         number it may return a corresponding country name)
         */
        public V get(String key) {
            return _get(new StringBuffer(key), 0);
        }
    
        /**
         * Returns true if key has matching prefix in the table
         */
        public boolean hasPrefix(String key) {
            return ((this.get(key) != null) ? true : false);
        }
    
        V _get(StringBuffer key, int level) {
            if (key.length() > 0) {
                Trie<V> t = children.get(key.charAt(0));
                if (t != null) {
                     V result = t._get(key.deleteCharAt(0), ++level);
                     return result == null ? entry.value : result;
    
                } else {
                    return (level > 0) ? entry.value : null;
                }
    
            } else {
                return entry.value;
            }
        }
    
        @Override
        public String toString() {
    
            Iterator<Character> it = children.keySet().iterator();
            StringBuffer childs = new StringBuffer();
    
            while (it.hasNext()) {
                Character key = it.next();
                childs.append(String.format("\n%s\n",
                        // adding a tab to the beginning of every line to create a visual tree
                        String.format("%s: %s", key, children.get(key)).toString().replaceAll("(?m)(^)", "\t")));
            }
    
            return String.format("Trie [entry=%s, children=%s]", entry, childs);
        }
    
        static public class Entry<V> {
            String prefix;
            V value;
    
            public Entry() {
            }
    
            public Entry(String p, V v) {
                prefix = p;
                value = v;
            }
    
            public String prefix() {
                return prefix;
            }
    
            public V value() {
                return value;
            }
    
            @Override
            public String toString() {
                return "Entry [prefix=" + prefix + ", value=" + value + "]";
            }
    
        }
    }