public class PatriciaTrie<K,V> extends AbstractMap<K,V> implements Trie<K,V>, Serializable
PATRICIA = Practical Algorithm to Retrieve Information Coded in Alphanumeric
A PATRICIA Trie is a compressed Trie. Instead of storing all data at the edges of the Trie (and having empty internal nodes), PATRICIA stores data in every node. This allows for very efficient traversal, insert, delete, predecessor, successor, prefix, range, and 'select' operations. All operations are performed at worst in O(K) time, where K is the number of bits in the largest item in the tree. In practice, operations actually take O(A(K)) time, where A(K) is the average number of bits of all items in the tree.
Most importantly, PATRICIA requires very few comparisons to keys while doing any operation. While performing a lookup, each comparison (at most K of them, described above) will perform a single bit comparison against the given key, instead of comparing the entire key to another key.
The Trie can return operations in lexicographical order using the 'traverse',
'prefix', 'submap', or 'iterator' methods. The Trie can also scan for items
that are 'bitwise' (using an XOR metric) by the 'select' method. Bitwise
closeness is determined by the PatriciaTrie.KeyAnalyzer
returning true or
false for a bit being set or not in a given key.
This PATRICIA Trie supports both variable length and fixed length keys.
Some methods, such as getPrefixedBy(...)
are suited only to
variable length keys, whereas getPrefixedByBits(...)
is suited
to fixed-size keys.
Additionally see PATRICIA for more information.
Any methods here that take an Object
may throw a
ClassCastException
if the method is expecting an instance of K
(and it isn't K).
PatriciaTrie<String, String> trie = new PatriciaTrie<String, String> (new CharSequenceKeyAnalyzer()); trie.put("Lime", "Lime"); trie.put("LimeWire", "LimeWire"); trie.put("LimeRadio", "LimeRadio"); trie.put("Lax", "Lax"); trie.put("Lake", "Lake"); trie.put("Lovely", "Lovely"); System.out.println(trie.select("Lo")); System.out.println(trie.select("Lime")); System.out.println(trie.getPrefixedBy("La").toString()); Output: Lovely Lime {Lake=Lake, Lax=Lax}
Modifier and Type | Class and Description |
---|---|
private static class |
PatriciaTrie.EmptyIterator |
private class |
PatriciaTrie.EntryIterator |
private class |
PatriciaTrie.EntrySet |
static interface |
PatriciaTrie.KeyAnalyzer<K>
Defines the interface to analyze
Trie keys on a bit
level. |
private class |
PatriciaTrie.KeyIterator |
private class |
PatriciaTrie.KeySet |
private class |
PatriciaTrie.NodeIterator<E>
An iterator for the entries.
|
private class |
PatriciaTrie.PrefixEntryIterator
An iterator for iterating over a prefix search.
|
private class |
PatriciaTrie.PrefixSubMap
A submap used for prefix views over the Trie.
|
private class |
PatriciaTrie.SingletonIterator
An iterator that stores a single TrieEntry.
|
private class |
PatriciaTrie.SubMap |
private class |
PatriciaTrie.SubMapEntryIterator
An iterator for submaps.
|
private static class |
PatriciaTrie.TrieEntry<K,V>
The actual Trie nodes.
|
private class |
PatriciaTrie.ValueIterator |
private class |
PatriciaTrie.Values |
AbstractMap.SimpleEntry<K,V>, AbstractMap.SimpleImmutableEntry<K,V>
Trie.Cursor<K,V>
Modifier and Type | Field and Description |
---|---|
private Set<Map.Entry<K,V>> |
entrySet |
private PatriciaTrie.KeyAnalyzer<? super K> |
keyAnalyzer
KeyAnalyzer used to analyze bit values of keys.
|
private Set<K> |
keySet
Each of these fields are initialized to contain an instance of the
appropriate view the first time this view is requested.
|
private int |
modCount
Number of times this has been modified (to fail-fast the iterators).
|
private PatriciaTrie.TrieEntry<K,V> |
root
Root element of the Trie.
|
private static long |
serialVersionUID |
private int |
size
Current size (total number of elements) of the Trie.
|
private Collection<V> |
values |
Constructor and Description |
---|
PatriciaTrie(PatriciaTrie.KeyAnalyzer<? super K> keyAnalyzer)
Constructs a new PatriciaTrie using the given keyAnalyzer.
|
Modifier and Type | Method and Description |
---|---|
private PatriciaTrie.TrieEntry<K,V> |
addEntry(PatriciaTrie.TrieEntry<K,V> toAdd,
int keyLength)
Adds the given entry into the Trie.
|
protected K |
asKey(Object key)
Gets the key as an object of type
K . |
private int |
bitIndex(K key,
K foundKey)
Utility method for calling
PatriciaTrie.KeyAnalyzer.bitIndex(Object, int, int, Object, int, int) . |
protected PatriciaTrie.TrieEntry<K,V> |
ceilingEntry(K key)
Returns a key-value mapping associated with the least key greater
than or equal to the given key, or null if there is no such key.
|
void |
clear()
Clears the Trie (i.e. removes all elements).
|
Comparator<? super K> |
comparator()
Returns the KeyAnalyzer as a comparator.
|
boolean |
containsKey(Object k)
Returns true if this trie contains the specified Key
This may throw ClassCastException if the object is not
of type K.
|
boolean |
containsValue(Object o)
Returns true if this Trie contains the specified value.
|
private void |
decrementSize()
Decrements the size and increments the mod counter.
|
Set<Map.Entry<K,V>> |
entrySet() |
private PatriciaTrie.TrieEntry<K,V> |
firstEntry()
Returns the first entry the Trie is storing.
|
K |
firstKey() |
protected PatriciaTrie.TrieEntry<K,V> |
floorEntry(K key)
Returns a key-value mapping associated with the greatest key
less than or equal to the given key, or null if there is no such key.
|
private PatriciaTrie.TrieEntry<K,V> |
followLeft(PatriciaTrie.TrieEntry<K,V> node)
Goes left through the tree until it finds a valid node.
|
protected PatriciaTrie.TrieEntry<K,V> |
followRight(PatriciaTrie.TrieEntry<K,V> node)
Traverses down the right path until it finds an uplink.
|
V |
get(Object k)
Attempt to find the given key within the trie.
|
(package private) PatriciaTrie.TrieEntry<K,V> |
getEntry(Object k)
Returns the entry associated with the specified key in the
PatriciaTrie.
|
PatriciaTrie.KeyAnalyzer<? super K> |
getKeyAnalyzer()
Returns the KeyAnalyzer that constructed the trie.
|
private PatriciaTrie.TrieEntry<K,V> |
getNearestEntryForKey(K key,
int keyLength)
Returns the nearest entry for a given key.
|
SortedMap<K,V> |
getPrefixedBy(K key)
Returns a view of this Trie of all elements that are
prefixed by the given key.
|
SortedMap<K,V> |
getPrefixedBy(K key,
int length)
Returns a view of this Trie of all elements that are
prefixed by the
length of the key . |
SortedMap<K,V> |
getPrefixedBy(K key,
int offset,
int length)
Returns a view of this Trie of all elements that are prefixed
by the
key , starting at the given offset and for the
given length . |
SortedMap<K,V> |
getPrefixedByBits(K key,
int bitLength)
Returns a view of this Trie of all elements that are prefixed
by the number of bits in the given
key . |
private SortedMap<K,V> |
getPrefixedByBits(K key,
int offset,
int length)
Returns a view of this map, with entries containing only those that
are prefixed by a value whose bits matches the bits between
offset and length in the given key . |
SortedMap<K,V> |
headMap(K toKey) |
protected PatriciaTrie.TrieEntry<K,V> |
higherEntry(K key)
Returns an entry strictly higher than the given key,
or null if no such entry exists.
|
private void |
incrementModCount()
Increments the mod counter.
|
private void |
incrementSize()
Increments both the size and mod counter.
|
private boolean |
isBitSet(K key,
int keyLength,
int bitIndex)
Returns whether or not the given bit on the key is set, or false if
the key is null.
|
boolean |
isEmpty()
Returns
true if the Trie is empty. |
private static boolean |
isEqualBitKey(int bitIndex)
Returns true if bitIndex is a
PatriciaTrie.KeyAnalyzer.EQUAL_BIT_KEY . |
private static boolean |
isNullBitKey(int bitIndex)
Returns true if bitIndex is a
PatriciaTrie.KeyAnalyzer.NULL_BIT_KEY . |
private static boolean |
isValidBitIndex(int bitIndex)
Returns true if bitIndex is a valid index.
|
private boolean |
isValidUplink(PatriciaTrie.TrieEntry<K,V> next,
PatriciaTrie.TrieEntry<K,V> from)
Returns true if 'next' is a valid uplink coming from 'from'.
|
Set<K> |
keySet()
Returns a set view of the keys contained in this map.
|
private PatriciaTrie.TrieEntry<K,V> |
lastEntry()
Returns the last entry the Trie is storing.
|
K |
lastKey() |
private int |
length(K key)
Returns the length of the key, or 0 if the key is null.
|
protected PatriciaTrie.TrieEntry<K,V> |
lowerEntry(K key)
Returns a key-value mapping associated with the greatest key
strictly less than the given key, or null if there is no such key.
|
(package private) Iterator<Map.Entry<K,V>> |
newEntryIterator() |
(package private) Iterator<K> |
newKeyIterator() |
(package private) Iterator<V> |
newValueIterator() |
private PatriciaTrie.TrieEntry<K,V> |
nextEntry(PatriciaTrie.TrieEntry<K,V> node)
Returns the entry lexicographically after the given entry.
|
private PatriciaTrie.TrieEntry<K,V> |
nextEntryImpl(PatriciaTrie.TrieEntry<K,V> start,
PatriciaTrie.TrieEntry<K,V> previous,
PatriciaTrie.TrieEntry<K,V> tree)
Scans for the next node, starting at the specified point, and using 'previous'
as a hint that the last node we returned was 'previous' (so we know not to return
it again).
|
private PatriciaTrie.TrieEntry<K,V> |
nextEntryInSubtree(PatriciaTrie.TrieEntry<K,V> node,
PatriciaTrie.TrieEntry<K,V> parentOfSubtree)
Returns the entry lexicographically after the given entry.
|
private PatriciaTrie.TrieEntry<K,V> |
previousEntry(PatriciaTrie.TrieEntry<K,V> start)
Returns the node lexicographically before the given node (or null if none).
|
V |
put(K key,
V value)
Adds a new
<key, value> pair to the Trie and if a pair already
exists it will be replaced. |
V |
remove(Object k)
Removes a Key from the Trie if one exists
This may throw ClassCastException if the object is not of type K.
|
private V |
removeEntry(PatriciaTrie.TrieEntry<K,V> h)
Removes a single entry from the Trie.
|
private void |
removeExternalEntry(PatriciaTrie.TrieEntry<K,V> h)
Removes an external entry from the Trie.
|
private void |
removeInternalEntry(PatriciaTrie.TrieEntry<K,V> h)
Removes an internal entry from the Trie.
|
V |
select(K key)
Returns the Value whose Key has the longest prefix
in common with our lookup key.
|
Map.Entry<K,V> |
select(K key,
Trie.Cursor<? super K,? super V> cursor)
Iterates through the Trie, starting with the entry whose bitwise
value is closest in an XOR metric to the given key.
|
private boolean |
selectR(PatriciaTrie.TrieEntry<K,V> h,
int bitIndex,
K key,
int keyLength,
PatriciaTrie.TrieEntry[] result)
This is equivalent to the other selectR() method but without
its overhead because we're selecting only one best matching
Entry from the Trie.
|
private boolean |
selectR(PatriciaTrie.TrieEntry<K,V> h,
int bitIndex,
K key,
int keyLength,
Trie.Cursor<? super K,? super V> cursor,
PatriciaTrie.TrieEntry[] result) |
int |
size()
Returns the number items in the Trie.
|
SortedMap<K,V> |
subMap(K fromKey,
K toKey) |
private PatriciaTrie.TrieEntry<K,V> |
subtree(K prefix,
int offset,
int length)
Finds the subtree that contains the prefix.
|
SortedMap<K,V> |
tailMap(K fromKey) |
String |
toString()
Returns each entry as a string.
|
Map.Entry<K,V> |
traverse(Trie.Cursor<? super K,? super V> cursor)
Traverses the Trie in lexicographical order.
|
private static boolean |
valEquals(Object o1,
Object o2)
Test two values for equality.
|
Collection<V> |
values()
Returns a collection view of the values contained in this map.
|
clone, equals, hashCode, putAll
finalize, getClass, notify, notifyAll, wait, wait, wait
compute, computeIfAbsent, computeIfPresent, equals, forEach, getOrDefault, hashCode, merge, putAll, putIfAbsent, remove, replace, replace, replaceAll
private static final long serialVersionUID
private final PatriciaTrie.TrieEntry<K,V> root
private int size
private transient int modCount
private final PatriciaTrie.KeyAnalyzer<? super K> keyAnalyzer
private transient volatile Set<K> keySet
private transient volatile Collection<V> values
public PatriciaTrie(PatriciaTrie.KeyAnalyzer<? super K> keyAnalyzer)
keyAnalyzer
- Used to analyze bit values of keys.public PatriciaTrie.KeyAnalyzer<? super K> getKeyAnalyzer()
keyAnalyzer
public Comparator<? super K> comparator()
comparator
in interface SortedMap<K,V>
keyAnalyzer
casted as a
Comparator
.public void clear()
public boolean isEmpty()
true
if the Trie is empty.public int size()
private void incrementSize()
private void decrementSize()
private void incrementModCount()
public V put(K key, V value)
<key, value>
pair to the Trie and if a pair already
exists it will be replaced. In the latter case it will return
the old value.private PatriciaTrie.TrieEntry<K,V> addEntry(PatriciaTrie.TrieEntry<K,V> toAdd, int keyLength)
toAdd
- Trie entry to add.keyLength
- Length of the entry's key.PatriciaTrie.TrieEntry<K,V> getEntry(Object k)
k
- Key to search for within the trie.protected final K asKey(Object key)
K
.key
- Key to "cast" to type K
.key
casted to type K
.private PatriciaTrie.TrieEntry<K,V> getNearestEntryForKey(K key, int keyLength)
key
- Key to search for within the trie.keyLength
- Length of key
.key
.public V select(K key)
private boolean selectR(PatriciaTrie.TrieEntry<K,V> h, int bitIndex, K key, int keyLength, PatriciaTrie.TrieEntry[] result)
h
- Node representing root of subtree being searched.bitIndex
- key
- Key being matched.keyLength
- Length of key
.result
- Array containing best match.public Map.Entry<K,V> select(K key, Trie.Cursor<? super K,? super V> cursor)
Trie
Cursor.SelectStatus.EXIT
.Cursor.SelectStatus.CONTINUE
to
continue traversing.Cursor.SelectStatus.REMOVE_AND_EXIT
is used to remove the current element
and stop traversing.
Note: The Trie.Cursor.SelectStatus.REMOVE
operation is not supported.
private boolean selectR(PatriciaTrie.TrieEntry<K,V> h, int bitIndex, K key, int keyLength, Trie.Cursor<? super K,? super V> cursor, PatriciaTrie.TrieEntry[] result)
public SortedMap<K,V> getPrefixedBy(K key)
firstKey()
, lastKey()
, and
size()
methods must iterate over all possible values in order
to determine the results. This information is cached until the Patricia
tree changes. All other methods (except Iterator) must compare the
given key to the prefix to ensure that it is within the range of
the view. The Iterator's remove method must also relocate the subtree
that contains the prefixes if the entry holding the subtree is removed
or changes. Changing the subtree takes O(K) time.getPrefixedBy
in interface Trie<K,V>
key
- Key to search for within the trie.Map
containing keys matching the given prefix and their
values.public SortedMap<K,V> getPrefixedBy(K key, int length)
length
of the key
.
Fixed-keysize Tries will not support this operation
(because all keys will be the same length).
For example, if the trie contains 'Lime', 'LimeWire',
'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then
a lookup of 'LimePlastics' with a length of 4 would
return 'Lime', 'LimeRadio', and 'LimeWire'.
The view that this returns is optimized to have a very efficient
Iterator. The firstKey()
, lastKey()
, and
size()
methods must iterate over all possible values in order
to determine the results. This information is cached until the
Patricia tree changes. All other methods (except Iterator) must
compare the given key to the prefix to ensure that it is within the
range of the view. The Iterator's remove method must also relocate the
subtree that contains the prefixes if the entry holding the subtree is
removed or changes. Changing the subtree takes O(K) time.getPrefixedBy
in interface Trie<K,V>
key
- Key to search for within the trie.length
- Length of key
. Ignored for tries with a fixed key
size.Map
containing keys matching the given prefix and their
values.public SortedMap<K,V> getPrefixedBy(K key, int offset, int length)
key
, starting at the given offset
and for the
given length
.
Fixed-keysize Tries will not support this operation
(because all keys are the same length).
For example, if the trie contains 'Lime', 'LimeWire',
'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then
a lookup of 'The Lime Plastics' with an offset of 4 and a
length of 4 would return 'Lime', 'LimeRadio', and 'LimeWire'.
The view that this returns is optimized to have a very efficient
Iterator. The firstKey()
, lastKey()
, and
size()
methods must iterate over all possible values in order
to determine the results. This information is cached until the
Patricia tree changes. All other methods (except Iterator) must
compare the given key to the prefix to ensure that it is within the
range of the view. The Iterator's remove method must also relocate the
subtree that contains the prefixes if the entry holding the subtree is
removed or changes. Changing the subtree takes O(K) time.getPrefixedBy
in interface Trie<K,V>
key
- Key to search for within the trie.offset
- Offset to begin search at.length
- Length of key
. Ignored for tries with a fixed key
size.Map
containing keys matching the given prefix and their
values.public SortedMap<K,V> getPrefixedByBits(K key, int bitLength)
key
.
Fixed-keysize Tries can support this operation as a way to do
lookups of partial keys. That is, if the Trie is storing IP
addresses, you can lookup all addresses that begin with
'192.168' by providing the key '192.168.X.X' and a length of 16
would return all addresses that begin with '192.168'.
The view that this returns is optimized to have a very efficient
Iterator. The firstKey()
, lastKey()
, and
size()
methods must iterate over all possible values in order
to determine the results. This information is cached until the Patricia
tree changes. All other methods (except Iterator) must compare the
given key to the prefix to ensure that it is within the range of the
view. The Iterator's remove method must also relocate the subtree that
contains the prefixes if the entry holding the subtree is removed or
changes. Changing the subtree takes O(K) time.getPrefixedByBits
in interface Trie<K,V>
key
- Key to search for within the trie.bitLength
- Length of key
in bits.Map
containing keys matching the given prefix and their
values.private SortedMap<K,V> getPrefixedByBits(K key, int offset, int length)
offset
and length
in the given key
.
The view that this returns is optimized to have a very efficient
Iterator. The firstKey()
, lastKey()
, and
size()
methods must iterate over all possible values in order
to determine the results. This information is cached until the
Patricia tree changes. All other methods (except Iterator) must
compare the given key to the prefix to ensure that it is within the
range of the view. The Iterator's remove method must also relocate
the subtree that contains the prefixes if the entry holding the
subtree is removed or changes. Changing the subtree takes O(K) time.key
- Key to search for within the trie.offset
- Offset within key to begin search.length
- Length of key
in bits.Map
containing keys matching the given prefix and their
values.public boolean containsKey(Object k)
containsKey
in interface Map<K,V>
containsKey
in class AbstractMap<K,V>
k
- Key to search for within trie.true
if the trie contains the given k
.public boolean containsValue(Object o)
containsValue
in interface Map<K,V>
containsValue
in class AbstractMap<K,V>
o
- Value to search for within trie.true
if the trie contains something equal to o
.public V remove(Object k)
private V removeEntry(PatriciaTrie.TrieEntry<K,V> h)
h
- Node to remove from the trie.private void removeExternalEntry(PatriciaTrie.TrieEntry<K,V> h)
h
- Node to remove from the trie.private void removeInternalEntry(PatriciaTrie.TrieEntry<K,V> h)
h
- Node to remove from the trie.private PatriciaTrie.TrieEntry<K,V> previousEntry(PatriciaTrie.TrieEntry<K,V> start)
- If the uplink that returned us was a right uplink: - If predecessor's left is a valid uplink from predecessor, return it. - Else, follow the right path from the predecessor's left. - If the uplink that returned us was a left uplink: - Loop back through parents until we encounter a node where node != node.parent.left. - If node.parent.left is uplink from node.parent: - If node.parent.left is not root, return it. - If it is root & root isEmpty, return null. - If it is root & root !isEmpty, return root. - If node.parent.left is not uplink from node.parent: - Follow right path for first right child from node.parent.left
start
- Node from which the search should begin.start
.private PatriciaTrie.TrieEntry<K,V> nextEntry(PatriciaTrie.TrieEntry<K,V> node)
node
- Node whose subsequent entry is desired.node
.private PatriciaTrie.TrieEntry<K,V> nextEntryInSubtree(PatriciaTrie.TrieEntry<K,V> node, PatriciaTrie.TrieEntry<K,V> parentOfSubtree)
node
- Node whose subsequent entry is desired.parentOfSubtree
- Node that is parent of subtree being searched.node
.private PatriciaTrie.TrieEntry<K,V> nextEntryImpl(PatriciaTrie.TrieEntry<K,V> start, PatriciaTrie.TrieEntry<K,V> previous, PatriciaTrie.TrieEntry<K,V> tree)
start
- Node from which the search should begin.previous
- Previously-searched node.tree
- If not null
, search will be limited to this subtree.previous
.public String toString()
toString
in class AbstractMap<K,V>
public Map.Entry<K,V> traverse(Trie.Cursor<? super K,? super V> cursor)
Trie
Cursor.select
will be called on each entry.
The traversal will stop when the cursor returns Cursor.SelectStatus.EXIT
.
Cursor.SelectStatus.CONTINUE
is used to continue traversing.
Cursor.SelectStatus.REMOVE
is used to remove the element that was
selected and continue traversing.
Cursor.SelectStatus.REMOVE_AND_EXIT
is used to remove the current element
and stop traversing.
private boolean isValidUplink(PatriciaTrie.TrieEntry<K,V> next, PatriciaTrie.TrieEntry<K,V> from)
next
- from
- true
if next
is a valid "uplink" from
from
.private static boolean isValidBitIndex(int bitIndex)
bitIndex
- Bit index to check.bitIndex
is valid.private static boolean isNullBitKey(int bitIndex)
PatriciaTrie.KeyAnalyzer.NULL_BIT_KEY
.bitIndex
- private static boolean isEqualBitKey(int bitIndex)
PatriciaTrie.KeyAnalyzer.EQUAL_BIT_KEY
.bitIndex
- private int length(K key)
key
- Key whose length is desired. null
is allowed.private boolean isBitSet(K key, int keyLength, int bitIndex)
key
- Key to check. null
is allowed.keyLength
- Length of key
.bitIndex
- Bit index of key
that should be checked.bitIndex
on key
is
set. If key
is null
, false
will be returned.private int bitIndex(K key, K foundKey)
PatriciaTrie.KeyAnalyzer.bitIndex(Object, int, int, Object, int, int)
.key
- foundKey
- Iterator<K> newKeyIterator()
Iterator<V> newValueIterator()
Iterator<Map.Entry<K,V>> newEntryIterator()
public Set<K> keySet()
Iterator.remove
,
Set.remove
, removeAll
, retainAll
, and
clear
operations. It does not support the add
or
addAll
operations.public Collection<V> values()
Iterator.remove
, Collection.remove
,
removeAll
, retainAll
, and clear
operations.
It does not support the add
or addAll
operations.private static boolean valEquals(Object o1, Object o2)
private PatriciaTrie.TrieEntry<K,V> firstEntry()
private PatriciaTrie.TrieEntry<K,V> followLeft(PatriciaTrie.TrieEntry<K,V> node)
private PatriciaTrie.TrieEntry<K,V> lastEntry()
This is implemented by going always to the right until we encounter a valid uplink. That uplink is the last key.
protected PatriciaTrie.TrieEntry<K,V> followRight(PatriciaTrie.TrieEntry<K,V> node)
protected PatriciaTrie.TrieEntry<K,V> higherEntry(K key)
key
- protected PatriciaTrie.TrieEntry<K,V> ceilingEntry(K key)
key
- protected PatriciaTrie.TrieEntry<K,V> lowerEntry(K key)
key
- protected PatriciaTrie.TrieEntry<K,V> floorEntry(K key)
key
- private PatriciaTrie.TrieEntry<K,V> subtree(K prefix, int offset, int length)
prefix
- offset
- length
-