All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cassandra.index.sasi.utils.trie.PatriciaTrie Maven / Gradle / Ivy

There is a newer version: 3.11.12.3
Show newest version
/*
 * Copyright 2005-2010 Roger Kapsi, Sam Berlin
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package org.apache.cassandra.index.sasi.utils.trie;

import java.io.Serializable;
import java.util.*;

/**
 * This class is taken from https://github.com/rkapsi/patricia-trie (v0.6), and slightly modified
 * to correspond to Cassandra code style, as the only Patricia Trie implementation,
 * which supports pluggable key comparators (e.g. commons-collections PatriciaTrie (which is based
 * on rkapsi/patricia-trie project) only supports String keys)
 * but unfortunately is not deployed to the maven central as a downloadable artifact.
 */

/**
 * 

PATRICIA {@link Trie}

* * Practical Algorithm to Retrieve Information Coded in Alphanumeric * *

A PATRICIA {@link Trie} is a compressed {@link Trie}. Instead of storing * all data at the edges of the {@link 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 {@link #select(Object)} * 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 {@link Trie} can return operations in lexicographical order using the * {@link #traverse(Cursor)}, 'prefix', 'submap', or 'iterator' methods. The * {@link Trie} can also scan for items that are 'bitwise' (using an XOR * metric) by the 'select' method. Bitwise closeness is determined by the * {@link KeyAnalyzer} returning true or false for a bit being set or not in * a given key. * *

Any methods here that take an {@link Object} argument may throw a * {@link ClassCastException} if the method is expecting an instance of K * and it isn't K. * * @see Radix Tree * @see PATRICIA * @see Crit-Bit Tree * * @author Roger Kapsi * @author Sam Berlin */ public class PatriciaTrie extends AbstractPatriciaTrie implements Serializable { private static final long serialVersionUID = -2246014692353432660L; public PatriciaTrie(KeyAnalyzer keyAnalyzer) { super(keyAnalyzer); } public PatriciaTrie(KeyAnalyzer keyAnalyzer, Map m) { super(keyAnalyzer, m); } @Override public Comparator comparator() { return keyAnalyzer; } @Override public SortedMap prefixMap(K prefix) { return lengthInBits(prefix) == 0 ? this : new PrefixRangeMap(prefix); } @Override public K firstKey() { return firstEntry().getKey(); } @Override public K lastKey() { TrieEntry entry = lastEntry(); return entry != null ? entry.getKey() : null; } @Override public SortedMap headMap(K toKey) { return new RangeEntryMap(null, toKey); } @Override public SortedMap subMap(K fromKey, K toKey) { return new RangeEntryMap(fromKey, toKey); } @Override public SortedMap tailMap(K fromKey) { return new RangeEntryMap(fromKey, null); } /** * Returns an entry strictly higher than the given key, * or null if no such entry exists. */ private TrieEntry higherEntry(K key) { // TODO: Cleanup so that we don't actually have to add/remove from the // tree. (We do it here because there are other well-defined // functions to perform the search.) int lengthInBits = lengthInBits(key); if (lengthInBits == 0) { if (!root.isEmpty()) { // If data in root, and more after -- return it. return size() > 1 ? nextEntry(root) : null; } else { // Root is empty & we want something after empty, return first. return firstEntry(); } } TrieEntry found = getNearestEntryForKey(key); if (compareKeys(key, found.key)) return nextEntry(found); int bitIndex = bitIndex(key, found.key); if (Tries.isValidBitIndex(bitIndex)) { return replaceCeil(key, bitIndex); } else if (Tries.isNullBitKey(bitIndex)) { if (!root.isEmpty()) { return firstEntry(); } else if (size() > 1) { return nextEntry(firstEntry()); } else { return null; } } else if (Tries.isEqualBitKey(bitIndex)) { return nextEntry(found); } // we should have exited above. throw new IllegalStateException("invalid lookup: " + 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. */ TrieEntry ceilingEntry(K key) { // Basically: // Follow the steps of adding an entry, but instead... // // - If we ever encounter a situation where we found an equal // key, we return it immediately. // // - If we hit an empty root, return the first iterable item. // // - If we have to add a new item, we temporarily add it, // find the successor to it, then remove the added item. // // These steps ensure that the returned value is either the // entry for the key itself, or the first entry directly after // the key. // TODO: Cleanup so that we don't actually have to add/remove from the // tree. (We do it here because there are other well-defined // functions to perform the search.) int lengthInBits = lengthInBits(key); if (lengthInBits == 0) { if (!root.isEmpty()) { return root; } else { return firstEntry(); } } TrieEntry found = getNearestEntryForKey(key); if (compareKeys(key, found.key)) return found; int bitIndex = bitIndex(key, found.key); if (Tries.isValidBitIndex(bitIndex)) { return replaceCeil(key, bitIndex); } else if (Tries.isNullBitKey(bitIndex)) { if (!root.isEmpty()) { return root; } else { return firstEntry(); } } else if (Tries.isEqualBitKey(bitIndex)) { return found; } // we should have exited above. throw new IllegalStateException("invalid lookup: " + key); } private TrieEntry replaceCeil(K key, int bitIndex) { TrieEntry added = new TrieEntry<>(key, null, bitIndex); addEntry(added); incrementSize(); // must increment because remove will decrement TrieEntry ceil = nextEntry(added); removeEntry(added); modCount -= 2; // we didn't really modify it. return ceil; } private TrieEntry replaceLower(K key, int bitIndex) { TrieEntry added = new TrieEntry<>(key, null, bitIndex); addEntry(added); incrementSize(); // must increment because remove will decrement TrieEntry prior = previousEntry(added); removeEntry(added); modCount -= 2; // we didn't really modify it. return prior; } /** * Returns a key-value mapping associated with the greatest key * strictly less than the given key, or null if there is no such key. */ TrieEntry lowerEntry(K key) { // Basically: // Follow the steps of adding an entry, but instead... // // - If we ever encounter a situation where we found an equal // key, we return it's previousEntry immediately. // // - If we hit root (empty or not), return null. // // - If we have to add a new item, we temporarily add it, // find the previousEntry to it, then remove the added item. // // These steps ensure that the returned value is always just before // the key or null (if there was nothing before it). // TODO: Cleanup so that we don't actually have to add/remove from the // tree. (We do it here because there are other well-defined // functions to perform the search.) int lengthInBits = lengthInBits(key); if (lengthInBits == 0) return null; // there can never be anything before root. TrieEntry found = getNearestEntryForKey(key); if (compareKeys(key, found.key)) return previousEntry(found); int bitIndex = bitIndex(key, found.key); if (Tries.isValidBitIndex(bitIndex)) { return replaceLower(key, bitIndex); } else if (Tries.isNullBitKey(bitIndex)) { return null; } else if (Tries.isEqualBitKey(bitIndex)) { return previousEntry(found); } // we should have exited above. throw new IllegalStateException("invalid lookup: " + 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. */ TrieEntry floorEntry(K key) { // TODO: Cleanup so that we don't actually have to add/remove from the // tree. (We do it here because there are other well-defined // functions to perform the search.) int lengthInBits = lengthInBits(key); if (lengthInBits == 0) { return !root.isEmpty() ? root : null; } TrieEntry found = getNearestEntryForKey(key); if (compareKeys(key, found.key)) return found; int bitIndex = bitIndex(key, found.key); if (Tries.isValidBitIndex(bitIndex)) { return replaceLower(key, bitIndex); } else if (Tries.isNullBitKey(bitIndex)) { if (!root.isEmpty()) { return root; } else { return null; } } else if (Tries.isEqualBitKey(bitIndex)) { return found; } // we should have exited above. throw new IllegalStateException("invalid lookup: " + key); } /** * Finds the subtree that contains the prefix. * * This is very similar to getR but with the difference that * we stop the lookup if h.bitIndex > lengthInBits. */ private TrieEntry subtree(K prefix) { int lengthInBits = lengthInBits(prefix); TrieEntry current = root.left; TrieEntry path = root; while(true) { if (current.bitIndex <= path.bitIndex || lengthInBits < current.bitIndex) break; path = current; current = !isBitSet(prefix, current.bitIndex) ? current.left : current.right; } // Make sure the entry is valid for a subtree. TrieEntry entry = current.isEmpty() ? path : current; // If entry is root, it can't be empty. if (entry.isEmpty()) return null; // if root && length of root is less than length of lookup, // there's nothing. // (this prevents returning the whole subtree if root has an empty // string and we want to lookup things with "\0") if (entry == root && lengthInBits(entry.getKey()) < lengthInBits) return null; // Found key's length-th bit differs from our key // which means it cannot be the prefix... if (isBitSet(prefix, lengthInBits) != isBitSet(entry.key, lengthInBits)) return null; // ... or there are less than 'length' equal bits int bitIndex = bitIndex(prefix, entry.key); return (bitIndex >= 0 && bitIndex < lengthInBits) ? null : entry; } /** * Returns the last entry the {@link Trie} is storing. * *

This is implemented by going always to the right until * we encounter a valid uplink. That uplink is the last key. */ private TrieEntry lastEntry() { return followRight(root.left); } /** * Traverses down the right path until it finds an uplink. */ private TrieEntry followRight(TrieEntry node) { // if Trie is empty, no last entry. if (node.right == null) return null; // Go as far right as possible, until we encounter an uplink. while (node.right.bitIndex > node.bitIndex) { node = node.right; } return node.right; } /** * Returns the node lexicographically before the given node (or null if none). * * This follows four simple branches: * - 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 * * @param start the start entry */ private TrieEntry previousEntry(TrieEntry start) { if (start.predecessor == null) throw new IllegalArgumentException("must have come from somewhere!"); if (start.predecessor.right == start) { return isValidUplink(start.predecessor.left, start.predecessor) ? start.predecessor.left : followRight(start.predecessor.left); } TrieEntry node = start.predecessor; while (node.parent != null && node == node.parent.left) { node = node.parent; } if (node.parent == null) // can be null if we're looking up root. return null; if (isValidUplink(node.parent.left, node.parent)) { if (node.parent.left == root) { return root.isEmpty() ? null : root; } else { return node.parent.left; } } else { return followRight(node.parent.left); } } /** * Returns the entry lexicographically after the given entry. * If the given entry is null, returns the first node. * * This will traverse only within the subtree. If the given node * is not within the subtree, this will have undefined results. */ private TrieEntry nextEntryInSubtree(TrieEntry node, TrieEntry parentOfSubtree) { return (node == null) ? firstEntry() : nextEntryImpl(node.predecessor, node, parentOfSubtree); } private boolean isPrefix(K key, K prefix) { return keyAnalyzer.isPrefix(key, prefix); } /** * A range view of the {@link Trie} */ private abstract class RangeMap extends AbstractMap implements SortedMap { /** * The {@link #entrySet()} view */ private transient volatile Set> entrySet; /** * Creates and returns an {@link #entrySet()} * view of the {@link RangeMap} */ protected abstract Set> createEntrySet(); /** * Returns the FROM Key */ protected abstract K getFromKey(); /** * Whether or not the {@link #getFromKey()} is in the range */ protected abstract boolean isFromInclusive(); /** * Returns the TO Key */ protected abstract K getToKey(); /** * Whether or not the {@link #getToKey()} is in the range */ protected abstract boolean isToInclusive(); @Override public Comparator comparator() { return PatriciaTrie.this.comparator(); } @Override public boolean containsKey(Object key) { return inRange(Tries.cast(key)) && PatriciaTrie.this.containsKey(key); } @Override public V remove(Object key) { return (!inRange(Tries.cast(key))) ? null : PatriciaTrie.this.remove(key); } @Override public V get(Object key) { return (!inRange(Tries.cast(key))) ? null : PatriciaTrie.this.get(key); } @Override public V put(K key, V value) { if (!inRange(key)) throw new IllegalArgumentException("Key is out of range: " + key); return PatriciaTrie.this.put(key, value); } @Override public Set> entrySet() { if (entrySet == null) entrySet = createEntrySet(); return entrySet; } @Override public SortedMap subMap(K fromKey, K toKey) { if (!inRange2(fromKey)) throw new IllegalArgumentException("FromKey is out of range: " + fromKey); if (!inRange2(toKey)) throw new IllegalArgumentException("ToKey is out of range: " + toKey); return createRangeMap(fromKey, isFromInclusive(), toKey, isToInclusive()); } @Override public SortedMap headMap(K toKey) { if (!inRange2(toKey)) throw new IllegalArgumentException("ToKey is out of range: " + toKey); return createRangeMap(getFromKey(), isFromInclusive(), toKey, isToInclusive()); } @Override public SortedMap tailMap(K fromKey) { if (!inRange2(fromKey)) throw new IllegalArgumentException("FromKey is out of range: " + fromKey); return createRangeMap(fromKey, isFromInclusive(), getToKey(), isToInclusive()); } /** * Returns true if the provided key is greater than TO and * less than FROM */ protected boolean inRange(K key) { K fromKey = getFromKey(); K toKey = getToKey(); return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, false)); } /** * This form allows the high endpoint (as well as all legit keys) */ protected boolean inRange2(K key) { K fromKey = getFromKey(); K toKey = getToKey(); return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, true)); } /** * Returns true if the provided key is in the FROM range * of the {@link RangeMap} */ protected boolean inFromRange(K key, boolean forceInclusive) { K fromKey = getFromKey(); boolean fromInclusive = isFromInclusive(); int ret = keyAnalyzer.compare(key, fromKey); return (fromInclusive || forceInclusive) ? ret >= 0 : ret > 0; } /** * Returns true if the provided key is in the TO range * of the {@link RangeMap} */ protected boolean inToRange(K key, boolean forceInclusive) { K toKey = getToKey(); boolean toInclusive = isToInclusive(); int ret = keyAnalyzer.compare(key, toKey); return (toInclusive || forceInclusive) ? ret <= 0 : ret < 0; } /** * Creates and returns a sub-range view of the current {@link RangeMap} */ protected abstract SortedMap createRangeMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive); } /** * A {@link RangeMap} that deals with {@link Entry}s */ private class RangeEntryMap extends RangeMap { /** * The key to start from, null if the beginning. */ protected final K fromKey; /** * The key to end at, null if till the end. */ protected final K toKey; /** * Whether or not the 'from' is inclusive. */ protected final boolean fromInclusive; /** * Whether or not the 'to' is inclusive. */ protected final boolean toInclusive; /** * Creates a {@link RangeEntryMap} with the fromKey included and * the toKey excluded from the range */ protected RangeEntryMap(K fromKey, K toKey) { this(fromKey, true, toKey, false); } /** * Creates a {@link RangeEntryMap} */ protected RangeEntryMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { if (fromKey == null && toKey == null) throw new IllegalArgumentException("must have a from or to!"); if (fromKey != null && toKey != null && keyAnalyzer.compare(fromKey, toKey) > 0) throw new IllegalArgumentException("fromKey > toKey"); this.fromKey = fromKey; this.fromInclusive = fromInclusive; this.toKey = toKey; this.toInclusive = toInclusive; } @Override public K firstKey() { Map.Entry e = fromKey == null ? firstEntry() : fromInclusive ? ceilingEntry(fromKey) : higherEntry(fromKey); K first = e != null ? e.getKey() : null; if (e == null || toKey != null && !inToRange(first, false)) throw new NoSuchElementException(); return first; } @Override public K lastKey() { Map.Entry e = toKey == null ? lastEntry() : toInclusive ? floorEntry(toKey) : lowerEntry(toKey); K last = e != null ? e.getKey() : null; if (e == null || fromKey != null && !inFromRange(last, false)) throw new NoSuchElementException(); return last; } @Override protected Set> createEntrySet() { return new RangeEntrySet(this); } @Override public K getFromKey() { return fromKey; } @Override public K getToKey() { return toKey; } @Override public boolean isFromInclusive() { return fromInclusive; } @Override public boolean isToInclusive() { return toInclusive; } @Override protected SortedMap createRangeMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); } } /** * A {@link Set} view of a {@link RangeMap} */ private class RangeEntrySet extends AbstractSet> { private final RangeMap delegate; private int size = -1; private int expectedModCount = -1; /** * Creates a {@link RangeEntrySet} */ public RangeEntrySet(RangeMap delegate) { if (delegate == null) throw new NullPointerException("delegate"); this.delegate = delegate; } @Override public Iterator> iterator() { K fromKey = delegate.getFromKey(); K toKey = delegate.getToKey(); TrieEntry first = fromKey == null ? firstEntry() : ceilingEntry(fromKey); TrieEntry last = null; if (toKey != null) last = ceilingEntry(toKey); return new EntryIterator(first, last); } @Override public int size() { if (size == -1 || expectedModCount != PatriciaTrie.this.modCount) { size = 0; for (Iterator it = iterator(); it.hasNext(); it.next()) { ++size; } expectedModCount = PatriciaTrie.this.modCount; } return size; } @Override public boolean isEmpty() { return !iterator().hasNext(); } @Override public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry) o; K key = entry.getKey(); if (!delegate.inRange(key)) return false; TrieEntry node = getEntry(key); return node != null && Tries.areEqual(node.getValue(), entry.getValue()); } @Override public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry) o; K key = entry.getKey(); if (!delegate.inRange(key)) return false; TrieEntry node = getEntry(key); if (node != null && Tries.areEqual(node.getValue(), entry.getValue())) { removeEntry(node); return true; } return false; } /** * An {@link Iterator} for {@link RangeEntrySet}s. */ private final class EntryIterator extends TrieIterator> { private final K excludedKey; /** * Creates a {@link EntryIterator} */ private EntryIterator(TrieEntry first, TrieEntry last) { super(first); this.excludedKey = (last != null ? last.getKey() : null); } @Override public boolean hasNext() { return next != null && !Tries.areEqual(next.key, excludedKey); } @Override public Map.Entry next() { if (next == null || Tries.areEqual(next.key, excludedKey)) throw new NoSuchElementException(); return nextEntry(); } } } /** * A submap used for prefix views over the {@link Trie}. */ private class PrefixRangeMap extends RangeMap { private final K prefix; private K fromKey = null; private K toKey = null; private int expectedModCount = -1; private int size = -1; /** * Creates a {@link PrefixRangeMap} */ private PrefixRangeMap(K prefix) { this.prefix = prefix; } /** * This method does two things. It determinates the FROM * and TO range of the {@link PrefixRangeMap} and the number * of elements in the range. This method must be called every * time the {@link Trie} has changed. */ private int fixup() { // The trie has changed since we last // found our toKey / fromKey if (size == - 1 || PatriciaTrie.this.modCount != expectedModCount) { Iterator> it = entrySet().iterator(); size = 0; Map.Entry entry = null; if (it.hasNext()) { entry = it.next(); size = 1; } fromKey = entry == null ? null : entry.getKey(); if (fromKey != null) { TrieEntry prior = previousEntry((TrieEntry)entry); fromKey = prior == null ? null : prior.getKey(); } toKey = fromKey; while (it.hasNext()) { ++size; entry = it.next(); } toKey = entry == null ? null : entry.getKey(); if (toKey != null) { entry = nextEntry((TrieEntry)entry); toKey = entry == null ? null : entry.getKey(); } expectedModCount = PatriciaTrie.this.modCount; } return size; } @Override public K firstKey() { fixup(); Map.Entry e = fromKey == null ? firstEntry() : higherEntry(fromKey); K first = e != null ? e.getKey() : null; if (e == null || !isPrefix(first, prefix)) throw new NoSuchElementException(); return first; } @Override public K lastKey() { fixup(); Map.Entry e = toKey == null ? lastEntry() : lowerEntry(toKey); K last = e != null ? e.getKey() : null; if (e == null || !isPrefix(last, prefix)) throw new NoSuchElementException(); return last; } /** * Returns true if this {@link PrefixRangeMap}'s key is a prefix * of the provided key. */ @Override protected boolean inRange(K key) { return isPrefix(key, prefix); } /** * Same as {@link #inRange(Object)} */ @Override protected boolean inRange2(K key) { return inRange(key); } /** * Returns true if the provided Key is in the FROM range * of the {@link PrefixRangeMap} */ @Override protected boolean inFromRange(K key, boolean forceInclusive) { return isPrefix(key, prefix); } /** * Returns true if the provided Key is in the TO range * of the {@link PrefixRangeMap} */ @Override protected boolean inToRange(K key, boolean forceInclusive) { return isPrefix(key, prefix); } @Override protected Set> createEntrySet() { return new PrefixRangeEntrySet(this); } @Override public K getFromKey() { return fromKey; } @Override public K getToKey() { return toKey; } @Override public boolean isFromInclusive() { return false; } @Override public boolean isToInclusive() { return false; } @Override protected SortedMap createRangeMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); } } /** * A prefix {@link RangeEntrySet} view of the {@link Trie} */ private final class PrefixRangeEntrySet extends RangeEntrySet { private final PrefixRangeMap delegate; private TrieEntry prefixStart; private int expectedModCount = -1; /** * Creates a {@link PrefixRangeEntrySet} */ public PrefixRangeEntrySet(PrefixRangeMap delegate) { super(delegate); this.delegate = delegate; } @Override public int size() { return delegate.fixup(); } @Override public Iterator> iterator() { if (PatriciaTrie.this.modCount != expectedModCount) { prefixStart = subtree(delegate.prefix); expectedModCount = PatriciaTrie.this.modCount; } if (prefixStart == null) { Set> empty = Collections.emptySet(); return empty.iterator(); } else if (lengthInBits(delegate.prefix) >= prefixStart.bitIndex) { return new SingletonIterator(prefixStart); } else { return new EntryIterator(prefixStart, delegate.prefix); } } /** * An {@link Iterator} that holds a single {@link TrieEntry}. */ private final class SingletonIterator implements Iterator> { private final TrieEntry entry; private int hit = 0; public SingletonIterator(TrieEntry entry) { this.entry = entry; } @Override public boolean hasNext() { return hit == 0; } @Override public Map.Entry next() { if (hit != 0) throw new NoSuchElementException(); ++hit; return entry; } @Override public void remove() { if (hit != 1) throw new IllegalStateException(); ++hit; PatriciaTrie.this.removeEntry(entry); } } /** * An {@link Iterator} for iterating over a prefix search. */ private final class EntryIterator extends TrieIterator> { // values to reset the subtree if we remove it. protected final K prefix; protected boolean lastOne; protected TrieEntry subtree; // the subtree to search within /** * Starts iteration at the given entry & search only * within the given subtree. */ EntryIterator(TrieEntry startScan, K prefix) { subtree = startScan; next = PatriciaTrie.this.followLeft(startScan); this.prefix = prefix; } @Override public Map.Entry next() { Map.Entry entry = nextEntry(); if (lastOne) next = null; return entry; } @Override protected TrieEntry findNext(TrieEntry prior) { return PatriciaTrie.this.nextEntryInSubtree(prior, subtree); } @Override public void remove() { // If the current entry we're removing is the subtree // then we need to find a new subtree parent. boolean needsFixing = false; int bitIdx = subtree.bitIndex; if (current == subtree) needsFixing = true; super.remove(); // If the subtree changed its bitIndex or we // removed the old subtree, get a new one. if (bitIdx != subtree.bitIndex || needsFixing) subtree = subtree(prefix); // If the subtree's bitIndex is less than the // length of our prefix, it's the last item // in the prefix tree. if (lengthInBits(prefix) >= subtree.bitIndex) lastOne = true; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy