diff --git a/code/src/main/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTree.java index 7f745c8..5a4de95 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTree.java @@ -428,7 +428,38 @@ public int size() { } } - // ------------- Helper method for put() ------------- + @Override + public Iterator> iterator() { + return new LazyIterator>() { + + final LinkedList stack = new LinkedList(); + { + stack.push(new NodeKeyPair(root, root.getIncomingEdge())); + } + + @Override + protected KeyValuePair computeNext() { + while (!stack.isEmpty()) { + NodeKeyPair current = stack.removeFirst(); + List childNodes = current.node.getOutgoingEdges(); + // -> Iterate child nodes in reverse order and so push them onto the stack in reverse order, + // to counteract that pushing them onto the stack alone would otherwise reverse their processing order. + // This ensures that we actually process nodes in ascending alphabetical order. + for (int i = childNodes.size(); i > 0; i--) { + Node child = childNodes.get(i - 1); + stack.addFirst(new NodeKeyPair(child, CharSequences.concatenate(current.key, child.getIncomingEdge()))); + } + Object value = current.node.getValue(); + if (value != null) { + return new KeyValuePairImpl(CharSequences.toString(current.key), value); + } + } + return endOfData(); + } + }; + } + +// ------------- Helper method for put() ------------- /** * Atomically adds the given value to the tree, creating a node for the value as necessary. If the value is already @@ -811,7 +842,7 @@ protected NodeKeyPair computeNext() { /** - * Encapsulates a node and its associated key. Used internally by {@link #lazyTraverseDescendants}. + * Encapsulates a node and its associated key. Used internally by {@link #lazyTraverseDescendants} and {@link #iterator()}. */ protected static class NodeKeyPair { public final Node node; diff --git a/code/src/main/java/com/googlecode/concurrenttrees/radix/RadixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/radix/RadixTree.java index f319369..954c691 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/radix/RadixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/radix/RadixTree.java @@ -28,7 +28,7 @@ * * @author Niall Gallagher */ -public interface RadixTree { +public interface RadixTree extends Iterable> { /** * Associates the given value with the given key; replacing any previous value associated with the key. diff --git a/code/src/main/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTree.java index 89e3ff5..e2708c7 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTree.java @@ -457,10 +457,13 @@ public int size() { return radixTree.size(); } + @Override + public Iterator> iterator() { + return radixTree.iterator(); + } + @Override public Node getNode() { return radixTree.getNode(); } - - } diff --git a/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTree.java index b312186..94438d3 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTree.java @@ -17,12 +17,14 @@ import com.googlecode.concurrenttrees.common.CharSequences; import com.googlecode.concurrenttrees.common.KeyValuePair; +import com.googlecode.concurrenttrees.common.LazyIterator; import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree; import com.googlecode.concurrenttrees.radix.node.Node; import com.googlecode.concurrenttrees.radix.node.NodeFactory; import com.googlecode.concurrenttrees.radix.node.util.PrettyPrintable; import java.io.Serializable; +import java.util.Iterator; /** * An implementation of {@link ReversedRadixTree} which supports lock-free concurrent reads, and allows items to be added @@ -124,6 +126,26 @@ public int size() { return radixTree.size(); } + @Override + public Iterator> iterator() { + return new LazyIterator>() { + + final Iterator> it = radixTree.iterator(); + + @Override + protected KeyValuePair computeNext() { + if (it.hasNext()) { + KeyValuePair current = it.next(); + return new ConcurrentRadixTree.KeyValuePairImpl( + CharSequences.toString(CharSequences.reverse(current.getKey())), + current.getValue()); + } else { + return endOfData(); + } + } + }; + } + @Override public Node getNode() { return radixTree.getNode(); diff --git a/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ReversedRadixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ReversedRadixTree.java index 85ecdb4..a086922 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ReversedRadixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/radixreversed/ReversedRadixTree.java @@ -34,7 +34,7 @@ * @see com.googlecode.concurrenttrees.radix.RadixTree * @author Niall Gallagher */ -public interface ReversedRadixTree { +public interface ReversedRadixTree extends Iterable> { /** * Associates the given value with the given key; replacing any previous value associated with the key. diff --git a/code/src/main/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTree.java index 950d41e..26cfcca 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTree.java @@ -371,42 +371,16 @@ protected O computeNext() { */ @Override public Iterable> getKeyValuePairsForKeysContaining(final CharSequence fragment) { + final Iterable> values = radixTree.getValuesForKeysStartingWith(fragment); return new Iterable>() { @Override public Iterator> iterator() { - return new LazyIterator>() { - - Iterator> originalKeysSets = radixTree.getValuesForKeysStartingWith(fragment).iterator(); - Iterator keyIterator = Collections.emptyList().iterator(); - - // A given fragment can be contained many times within the same key, so track keys processed - // so far, so that we can avoid re-processing the same key multiple times... - Set keysAlreadyProcessed = new HashSet(); - + return ConcurrentSuffixTree.this.iterator(values, new KeyIterator>() { @Override - protected KeyValuePair computeNext() { - String originalKey = null; - O value = null; - while (value == null) { - while (!keyIterator.hasNext()) { - if (!originalKeysSets.hasNext()) { - return endOfData(); - } - keyIterator = originalKeysSets.next().iterator(); - } - originalKey = keyIterator.next(); - - if (keysAlreadyProcessed.add(originalKey)) { - // Key was not in the already-processed set, so proceed with looking up the value... - value = valueMap.get(originalKey); - - // value could still be null due to race condition if key/value was removed while - // iterating, hence if so, we loop again to find the next non-null key/value... - } - } - return new ConcurrentRadixTree.KeyValuePairImpl(originalKey, value); + public Iterable toKeys(Set value) { + return value; } - }; + }); } }; } @@ -419,6 +393,58 @@ public int size() { return valueMap.size(); } + @Override + public Iterator> iterator() { + return iterator(radixTree, new KeyIterator>>() { + @Override + public Iterable toKeys(KeyValuePair> value) { + return value.getValue(); + } + }); + } + + Iterator> iterator(final Iterable it, final KeyIterator resolver) { + return new LazyIterator>() { + + final Iterator originalKeysSets = it.iterator(); + Iterator keyIterator = Collections.emptyList().iterator(); + + // A given fragment can be contained many times within the same key, so track keys processed + // so far, so that we can avoid re-processing the same key multiple times... + final Set keysAlreadyProcessed = new HashSet(); + + @Override + protected KeyValuePair computeNext() { + String originalKey = null; + O value = null; + while (value == null) { + while (!keyIterator.hasNext()) { + if (!originalKeysSets.hasNext()) { + return endOfData(); + } + V current = originalKeysSets.next(); + keyIterator = resolver.toKeys(current).iterator(); + } + originalKey = keyIterator.next(); + + if (keysAlreadyProcessed.add(originalKey)) { + // Key was not in the already-processed set, so proceed with looking up the value... + value = valueMap.get(originalKey); + + // value could still be null due to race condition if key/value was removed while + // iterating, hence if so, we loop again to find the next non-null key/value... + } + } + return new ConcurrentRadixTree.KeyValuePairImpl(originalKey, value); + } + }; + } + + private interface KeyIterator { + + Iterable toKeys(V value); + } + /** * Utility method to return an iterator for the given iterable, or an empty iterator if the iterable is null. */ diff --git a/code/src/main/java/com/googlecode/concurrenttrees/suffix/SuffixTree.java b/code/src/main/java/com/googlecode/concurrenttrees/suffix/SuffixTree.java index 250e946..7a4197a 100644 --- a/code/src/main/java/com/googlecode/concurrenttrees/suffix/SuffixTree.java +++ b/code/src/main/java/com/googlecode/concurrenttrees/suffix/SuffixTree.java @@ -28,7 +28,7 @@ * * @author Niall Gallagher */ -public interface SuffixTree { +public interface SuffixTree extends Iterable> { /** * Associates the given value with the given key; replacing any previous value associated with the key. diff --git a/code/src/test/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTreeTest.java b/code/src/test/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTreeTest.java index ef38e28..ab3c1ef 100644 --- a/code/src/test/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTreeTest.java +++ b/code/src/test/java/com/googlecode/concurrenttrees/radix/ConcurrentRadixTreeTest.java @@ -32,6 +32,7 @@ import java.io.*; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; import static org.junit.Assert.*; @@ -963,6 +964,29 @@ public void testSearchResult_FailureToClassify3() { new ConcurrentRadixTree.SearchResult("DUMMY", dummyNodeFound, 4, 70, null, null); } + @Test + public void testIteration() { + ConcurrentRadixTree tree = new ConcurrentRadixTree(getNodeFactory()); + tree.put("TEST", 1); + tree.put("TEAM", 2); + tree.put("TOAST", 3); + + Iterator> it = tree.iterator(); + assertTrue(it.hasNext()); + KeyValuePair team = it.next(); + assertEquals("TEAM", team.getKey()); + assertEquals(2, (Object) team.getValue()); + assertTrue(it.hasNext()); + KeyValuePair test = it.next(); + assertEquals("TEST", test.getKey()); + assertEquals(1, (Object) test.getValue()); + assertTrue(it.hasNext()); + KeyValuePair toast = it.next(); + assertEquals("TOAST", toast.getKey()); + assertEquals(3, (Object) toast.getValue()); + assertFalse(it.hasNext()); + } + @Test public void testSerialization() { ConcurrentRadixTree tree1 = new ConcurrentRadixTree(getNodeFactory()); diff --git a/code/src/test/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTreeTest.java b/code/src/test/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTreeTest.java index 155a9b8..ce82922 100644 --- a/code/src/test/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTreeTest.java +++ b/code/src/test/java/com/googlecode/concurrenttrees/radixinverted/ConcurrentInvertedRadixTreeTest.java @@ -16,12 +16,15 @@ package com.googlecode.concurrenttrees.radixinverted; import com.googlecode.concurrenttrees.common.Iterables; +import com.googlecode.concurrenttrees.common.KeyValuePair; import com.googlecode.concurrenttrees.common.PrettyPrinter; import com.googlecode.concurrenttrees.radix.node.NodeFactory; import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory; import com.googlecode.concurrenttrees.testutil.TestUtility; import org.junit.Test; +import java.util.Iterator; + import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; @@ -440,6 +443,29 @@ public void testInheritedMethods() { assertEquals("[(FOO, 2), (FOOD, 1)]", Iterables.toString(tree.getKeyValuePairsForClosestKeys("FOB"))); } + @Test + public void testIteration() { + ConcurrentInvertedRadixTree tree = new ConcurrentInvertedRadixTree(getNodeFactory()); + tree.put("TEST", 1); + tree.put("TEAM", 2); + tree.put("TOAST", 3); + + Iterator> it = tree.iterator(); + assertTrue(it.hasNext()); + KeyValuePair team = it.next(); + assertEquals("TEAM", team.getKey()); + assertEquals(2, (Object) team.getValue()); + assertTrue(it.hasNext()); + KeyValuePair test = it.next(); + assertEquals("TEST", test.getKey()); + assertEquals(1, (Object) test.getValue()); + assertTrue(it.hasNext()); + KeyValuePair toast = it.next(); + assertEquals("TOAST", toast.getKey()); + assertEquals(3, (Object) toast.getValue()); + assertFalse(it.hasNext()); + } + @Test public void testSerialization() { ConcurrentInvertedRadixTree tree1 = new ConcurrentInvertedRadixTree(getNodeFactory()); diff --git a/code/src/test/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTreeTest.java b/code/src/test/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTreeTest.java index 5bbcb2f..1cb3747 100644 --- a/code/src/test/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTreeTest.java +++ b/code/src/test/java/com/googlecode/concurrenttrees/radixreversed/ConcurrentReversedRadixTreeTest.java @@ -16,12 +16,15 @@ package com.googlecode.concurrenttrees.radixreversed; import com.googlecode.concurrenttrees.common.Iterables; +import com.googlecode.concurrenttrees.common.KeyValuePair; import com.googlecode.concurrenttrees.common.PrettyPrinter; import com.googlecode.concurrenttrees.radix.node.NodeFactory; import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory; import com.googlecode.concurrenttrees.testutil.TestUtility; import org.junit.Test; +import java.util.Iterator; + import static org.junit.Assert.*; /** @@ -167,6 +170,29 @@ public void testRemove() throws Exception { assertEquals(expected, actual); } + @Test + public void testIteration() { + ConcurrentReversedRadixTree tree = new ConcurrentReversedRadixTree(getNodeFactory()); + tree.put("TEST", 1); + tree.put("TEAM", 2); + tree.put("TOAST", 3); + + Iterator> it = tree.iterator(); + assertTrue(it.hasNext()); + KeyValuePair team = it.next(); + assertEquals("TEAM", team.getKey()); + assertEquals(2, (Object) team.getValue()); + assertTrue(it.hasNext()); + KeyValuePair toast = it.next(); + assertEquals("TOAST", toast.getKey()); + assertEquals(3, (Object) toast.getValue()); + assertTrue(it.hasNext()); + KeyValuePair test = it.next(); + assertEquals("TEST", test.getKey()); + assertEquals(1, (Object) test.getValue()); + assertFalse(it.hasNext()); + } + @Test public void testSerialization() { ConcurrentReversedRadixTree tree1 = new ConcurrentReversedRadixTree(getNodeFactory()); diff --git a/code/src/test/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTreeTest.java b/code/src/test/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTreeTest.java index cfb69eb..c757bf3 100644 --- a/code/src/test/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTreeTest.java +++ b/code/src/test/java/com/googlecode/concurrenttrees/suffix/ConcurrentSuffixTreeTest.java @@ -16,6 +16,7 @@ package com.googlecode.concurrenttrees.suffix; import com.googlecode.concurrenttrees.common.Iterables; +import com.googlecode.concurrenttrees.common.KeyValuePair; import com.googlecode.concurrenttrees.common.PrettyPrinter; import com.googlecode.concurrenttrees.radix.node.NodeFactory; import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory; @@ -457,6 +458,29 @@ protected Set createSetForOriginalKeys() { }; } + @Test + public void testIteration() { + ConcurrentSuffixTree tree = new ConcurrentSuffixTreeTestImpl(getNodeFactory()); + tree.put("TEST", 1); + tree.put("TEAM", 2); + tree.put("TOAST", 3); + + Iterator> it = tree.iterator(); + assertTrue(it.hasNext()); + KeyValuePair team = it.next(); + assertEquals("TEAM", team.getKey()); + assertEquals(2, (Object) team.getValue()); + assertTrue(it.hasNext()); + KeyValuePair toast = it.next(); + assertEquals("TOAST", toast.getKey()); + assertEquals(3, (Object) toast.getValue()); + assertTrue(it.hasNext()); + KeyValuePair test = it.next(); + assertEquals("TEST", test.getKey()); + assertEquals(1, (Object) test.getValue()); + assertFalse(it.hasNext()); + } + @Test public void testSerialization() { ConcurrentSuffixTree tree1 = new ConcurrentSuffixTreeTestImpl(getNodeFactory());