diff --git a/reactfx/src/main/java/org/reactfx/util/FingerTree.java b/reactfx/src/main/java/org/reactfx/util/FingerTree.java index 952bdc0..9e8d38d 100644 --- a/reactfx/src/main/java/org/reactfx/util/FingerTree.java +++ b/reactfx/src/main/java/org/reactfx/util/FingerTree.java @@ -7,9 +7,12 @@ import java.util.AbstractList; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.Stack; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToIntFunction; @@ -486,6 +489,102 @@ public List subList(int start, int end) { Lists.checkRange(start, end, to - from); return Branch.this.subList(from + start, from + end); } + + @Override + public Iterator iterator() { + return listIterator(0); + } + + /** + * Iterates in both directions in time O(n), + * with at most O(log(n)) allocations (as the stack expands). + */ + @Override + public ListIterator listIterator(int pos) { + final int dd = 3; // maximum depth for which we directly call get(idx) + Lists.checkPosition(pos, size()); + return new ListIterator() { + private int position = from + pos; // position within this finger tree + private int topOffset = 0; // absolute offset of top of the stack relative to this finger tree + private Stack> stack = new Stack<>(); + { stack.push(Branch.this); } + + @Override public boolean hasNext() { return position < to; } + @Override public boolean hasPrevious() { return position > from; } + @Override public int nextIndex() { return position - from; } + @Override public int previousIndex() { return position - from - 1; } + + @Override public void remove() { throw new UnsupportedOperationException(); } + @Override public void set(T e) { throw new UnsupportedOperationException(); } + @Override public void add(T e) { throw new UnsupportedOperationException(); } + + @Override + public T next() { + if(position == topOffset + stack.peek().getLeafCount()) { + up(); + return next(); + } else if(stack.peek().getDepth() <= dd) { + return stack.peek().getLeaf(position++ - topOffset); + } else { + downR(); + return next(); + } + } + + @Override + public T previous() { + if(position == topOffset) { + up(); + return previous(); + } else if(stack.peek().getDepth() <= dd) { + return stack.peek().getLeaf(--position - topOffset); + } else { + downL(); + return previous(); + } + } + + private void up() { + NonEmptyFingerTree child = stack.pop(); + Branch top = (Branch) stack.peek(); + int chOffsetInParent = 0; + LL> children = top.children; + while(children.head() != child) { + chOffsetInParent += children.head().getLeafCount(); + children = children.tail(); + } + topOffset -= chOffsetInParent; + } + + private void downR() { + downR(((Branch) stack.peek()).children); + } + + private void downR(LL> children) { + NonEmptyFingerTree head = children.head(); + if(position - topOffset < head.getLeafCount()) { + stack.push(head); + } else { + topOffset += head.getLeafCount(); + downR(children.tail()); + } + } + + private void downL() { + downL(((Branch) stack.peek()).children); + } + + private void downL(LL> children) { + NonEmptyFingerTree head = children.head(); + if(position - topOffset <= head.getLeafCount()) { + stack.push(head); + } else { + topOffset += head.getLeafCount(); + downL(children.tail()); + } + } + }; + } }; } @@ -1072,6 +1171,19 @@ public NonEmptyFingerTree prepend(T data) { return leaf(data).appendTree(this); } + /** + * Returns a list view of this tree. + * Complexity of operations on the returned list: + *
    + *
  • {@code size()}: O(1);
  • + *
  • {@code get}: O(log(n));
  • + *
  • iteration: O(n) in either direction, + * with O(log(n)) total allocations;
  • + *
  • {@code subList}: O(log(n));
  • + *
  • iterative {@code subList}, i.e. calling {@code subList} + * on the result of previous {@code subList}, up to n times: O(n).
  • + *
+ */ public abstract List asList(); abstract T getData(); // valid for leafs only diff --git a/reactfx/src/test/java/org/reactfx/util/FingerTreeTest.java b/reactfx/src/test/java/org/reactfx/util/FingerTreeTest.java index ce9e61b..c0f26e5 100644 --- a/reactfx/src/test/java/org/reactfx/util/FingerTreeTest.java +++ b/reactfx/src/test/java/org/reactfx/util/FingerTreeTest.java @@ -2,8 +2,11 @@ import static org.junit.Assert.*; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.ListIterator; import java.util.Random; import org.junit.Test; @@ -41,4 +44,31 @@ public void testSubList() { } } + @Test + public void testIteration() { + final int n = 50000; + final int from = 10000; + final int to = 40000; + + Integer[] arr = new Integer[n]; + for(int i=0; i list = Arrays.asList(arr); + List treeList = FingerTree.mkTree(list).asList(); + + list = list.subList(from, to); + treeList = treeList.subList(from, to); + + ListIterator it = treeList.listIterator(); + + List fwRes = new ArrayList<>(treeList.size()); + while(it.hasNext()) fwRes.add(it.next()); + assertEquals(list, fwRes); + + List bwRes = new ArrayList<>(treeList.size()); + while(it.hasPrevious()) bwRes.add(it.previous()); + Collections.reverse(bwRes); + assertEquals(list, bwRes); + } + }