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

org.reactfx.util.FingerTree Maven / Gradle / Ivy

There is a newer version: 1.11
Show newest version
package org.reactfx.util;

import static org.reactfx.util.Either.*;
import static org.reactfx.util.LL.*;
import static org.reactfx.util.Tuples.*;

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;

import org.reactfx.util.LL.Cons;

public abstract class FingerTree {

    public static abstract class NonEmptyFingerTree extends FingerTree {

        private NonEmptyFingerTree(ToSemigroup semigroup) {
            super(semigroup);
        }

        @Override
        public Either, NonEmptyFingerTree> caseEmpty() {
            return right(this);
        }

        public abstract S getSummary();

        @Override
        public BiIndex locate(
                BiFunction> navigate,
                int position) {
            if(navigate.apply(getSummary(), position).isRight()) {
                throw new IndexOutOfBoundsException("Position " + position + " is out of bounds");
            }
            return locate0(navigate, position);
        }

        @Override
        public BiIndex locateProgressively(
                ToIntFunction metric,
                int position) {
            Lists.checkPosition(position, measure(metric));
            return locateProgressively0(metric, position);
        }

        @Override
        public BiIndex locateRegressively(
                ToIntFunction metric,
                int position) {
            Lists.checkPosition(position, measure(metric));
            return locateRegressively0(metric, position);
        }

        public Tuple3, T, FingerTree> splitAt(int leaf) {
            Lists.checkIndex(leaf, getLeafCount());
            return split0(leaf).map((l, r0) ->
                   r0.split0(1).map((m, r) ->
                           t(l, m.getLeaf0(0), r)));
        }

        public Tuple3, Tuple2, FingerTree> split(
                ToIntFunction metric, int position) {
            Lists.checkPosition(position, measure(metric));
            return split((s, i) -> {
                int n = metric.applyAsInt(s);
                return i <= n ? left(i) : right(i - n);
            }, position);
        }

        public Tuple3, Tuple2, FingerTree> split(
                BiFunction> navigate,
                int position) {
            if(navigate.apply(getSummary(), position).isRight()) {
                throw new IndexOutOfBoundsException("Position " + position + " is out of bounds");
            }

            BiIndex loc = locate0(navigate, position);
            return splitAt(loc.major).map((l, m, r) -> t(l, t(m, loc.minor), r));
        }

        @Override
        public NonEmptyFingerTree join(FingerTree rightTree) {
            return appendTree(rightTree);
        }

        final NonEmptyFingerTree appendTree(FingerTree right) {
            if(this.getDepth() >= right.getDepth()) {
                return appendLte(right).unify(
                        Function.identity(),
                        two -> two.map(FingerTree::branch));
            } else {
                return ((NonEmptyFingerTree) right).prependTree(this);
            }
        }

        final NonEmptyFingerTree prependTree(FingerTree left) {
            if(this.getDepth() >= left.getDepth()) {
                return prependLte(left).unify(
                        Function.identity(),
                        two -> two.map(FingerTree::branch));
            } else {
                return ((NonEmptyFingerTree) left).appendTree(this);
            }
        }

        abstract BiIndex locate0(
                BiFunction> navigate,
                int position);

        abstract BiIndex locateProgressively0(
                ToIntFunction metric,
                int position);

        abstract BiIndex locateRegressively0(
                ToIntFunction metric,
                int position);

        abstract Either, Tuple2, NonEmptyFingerTree>> appendLte(FingerTree right);
        abstract Either, Tuple2, NonEmptyFingerTree>> prependLte(FingerTree left);
    }

    private static final class Empty extends FingerTree {

        Empty(ToSemigroup semigroup) {
            super(semigroup);
        }

        @Override
        public String toString() {
            return"";
        }

        @Override
        public Either, NonEmptyFingerTree> caseEmpty() {
            return left(this);
        }

        @Override
        public
        int getDepth() {
            return 0;
        }

        @Override
        public
        int getLeafCount() {
            return 0;
        }

        @Override
        public FingerTree join(FingerTree rightTree) {
            return rightTree;
        }

        @Override
        public List asList() {
            return Collections.emptyList();
        }

        @Override
        T getLeaf0(int index) {
            throw new IndexOutOfBoundsException();
        }

        @Override
        NonEmptyFingerTree updateLeaf0(int index, T data) {
            throw new IndexOutOfBoundsException();
        }

        @Override
        T getData() {
            throw new NoSuchElementException();
        }

        @Override
        public
        Optional getSummaryOpt() {
            return Optional.empty();
        }

        @Override
        public  R fold(
                R acc,
                BiFunction reduction) {
            return acc;
        }

        @Override
         R foldBetween0(
                R acc,
                BiFunction reduction,
                int startLeaf, int endLeaf) {
            assert Lists.isValidRange(startLeaf, endLeaf, 0);
            return acc;
        }

        @Override
         R foldBetween0(
                R acc,
                BiFunction reduction,
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TetraFunction rangeReduction) {
            assert Lists.isValidRange(startPosition, endPosition, 0);
            return acc;
        }

        @Override
        S getSummaryBetween0(int startLeaf, int endLeaf) {
            throw new AssertionError("Unreachable code");
        }

        @Override
        S getSummaryBetween0(
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TriFunction subSummary) {
            throw new AssertionError("Unreachable code");
        }

        @Override
        Tuple2, FingerTree> split0(
                int beforeLeaf) {
            assert beforeLeaf == 0;
            return t(this, this);
        }
    }

    private static class Leaf extends NonEmptyFingerTree {
        private final T data;
        private final S summary;

        Leaf(ToSemigroup semigroup, T data) {
            super(semigroup);
            this.data = data;
            this.summary = semigroup.apply(data);
        }

        @Override
        public String toString() {
            return "Leaf(" + data + ")";
        }

        @Override
        public int getDepth() {
            return 1;
        }

        @Override
        public int getLeafCount() {
            return 1;
        }

        @Override
        public List asList() {
            return Collections.singletonList(data);
        }

        @Override
        T getLeaf0(int index) {
            assert index == 0;
            return data;
        }

        @Override
        NonEmptyFingerTree updateLeaf0(int index, T data) {
            assert index == 0;
            return leaf(data);
        }

        @Override
        T getData() {
            return data;
        }

        @Override
        public S getSummary() {
            return summary;
        }

        @Override
        public Optional getSummaryOpt() {
            return Optional.of(summary);
        }

        @Override
        BiIndex locateProgressively0(ToIntFunction metric, int position) {
            assert Lists.isValidPosition(position, measure(metric));
            return new BiIndex(0, position);
        }

        @Override
        BiIndex locateRegressively0(ToIntFunction metric, int position) {
            assert Lists.isValidPosition(position, measure(metric));
            return new BiIndex(0, position);
        }

        @Override
        public  R fold(
                R acc,
                BiFunction reduction) {
            return reduction.apply(acc, data);
        }

        @Override
         R foldBetween0(
                R acc,
                BiFunction reduction,
                int startLeaf, int endLeaf) {

            assert 0 <= startLeaf;
            assert endLeaf <= 1;

            if(startLeaf < endLeaf) {
                return reduction.apply(acc, data);
            } else {
                return acc;
            }
        }

        @Override
         R foldBetween0(
                R acc,
                BiFunction reduction,
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TetraFunction rangeReduction) {

            assert Lists.isValidRange(startPosition, endPosition, measure(metric));
            return rangeReduction.apply(acc, data, startPosition, endPosition);
        }

        @Override
        S getSummaryBetween0(int startLeaf, int endLeaf) {
            assert startLeaf == 0 && endLeaf == 1;
            return summary;
        }

        @Override
        S getSummaryBetween0(
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TriFunction subSummary) {

            assert Lists.isNonEmptyRange(startPosition, endPosition, measure(metric))
                    : "Didn't expect empty range [" + startPosition + ", " + endPosition + ")";

            if(startPosition == 0 && endPosition == measure(metric)) {
                return summary;
            } else {
                return subSummary.apply(data, startPosition, endPosition);
            }
        }

        @Override
        Either, Tuple2, NonEmptyFingerTree>> appendLte(
                FingerTree right) {
            assert right.getDepth() <= this.getDepth();
            return right.caseEmpty()
                    .mapLeft(emptyRight -> this)
                    .mapRight(nonEmptyRight -> t(this, nonEmptyRight));
        }

        @Override
        Either, Tuple2, NonEmptyFingerTree>> prependLte(
                FingerTree left) {
            assert left.getDepth() <= this.getDepth();
            return left.caseEmpty()
                    .mapLeft(emptyLeft -> this)
                    .mapRight(nonEmptyLeft -> t(nonEmptyLeft, this));
        }

        @Override
        Tuple2, FingerTree> split0(int beforeLeaf) {
            assert Lists.isValidPosition(beforeLeaf, 1);
            if(beforeLeaf == 0) {
                return t(empty(), this);
            } else {
                return t(this, empty());
            }
        }

        @Override
        BiIndex locate0(
                BiFunction> navigate,
                int position) {
            return navigate.apply(summary,  position).unify(
                    inl -> new BiIndex(0, inl),
                    inr -> { throw new AssertionError("Unreachable code"); });
        }
    }

    private static final class Branch extends NonEmptyFingerTree {
        private final Cons> children;
        private final int depth;
        private final int leafCount;
        private final S summary;

        private Branch(Cons> children) {
            super(children.head().semigroup);
            assert children.size() == 2 || children.size() == 3;
            FingerTree head = children.head();
            int headDepth = head.getDepth();
            assert children.all(n -> n.getDepth() == headDepth);
            this.children = children;
            this.depth  = 1 + headDepth;
            this.leafCount = children.fold(0, (s, n) -> s + n.getLeafCount());
            this.summary = children.mapReduce1(
                    NonEmptyFingerTree::getSummary,
                    semigroup::reduce);
        }

        @Override
        public String toString() {
            return "Branch" + children;
        }

        @Override
        public int getDepth() {
            return depth;
        }

        @Override
        public int getLeafCount() {
            return leafCount;
        }

        @Override
        public List asList()  {
            return subList0(0, leafCount);
        }

        /**
         * Complexity of calling once: O(log(n)).
         * Complexity of calling subList recursively on the resulting list,
         * i.e. {@code tree.asList().subList(...).subList(...). ... .subList(...)}
         * up to n times: O(n).
         * When the resulting list has size m, it may prevent up to m additional
         * elements of the original tree from being garbage collected.
         */
        private List subList(int from, int to) {
            int len = to - from;

            if(2*len >= getLeafCount()) {
                return subList0(from, to);
            } else {
                FingerTree tree = this;
                if(2*(getLeafCount() - to) > len) {
                    tree = tree.split(to)._1;
                }
                if(2*from > len) {
                    tree = tree.split(from)._2;
                    to -= from;
                    from = 0;
                }
                return tree.asList().subList(from, to);
            }
        }

        private List subList0(int from, int to) {
            return new AbstractList() {

                @Override
                public T get(int index) {
                    Lists.checkIndex(index, to - from);
                    return getLeaf(from + index);
                }

                @Override
                public int size() {
                    return to - from;
                }

                @Override
                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());
                            }
                        }
                    };
                }
            };
        }

        @Override
        final T getData() {
            throw new UnsupportedOperationException("Only leaf nodes hold data");
        }

        @Override
        final T getLeaf0(int index) {
            assert Lists.isValidIndex(index, getLeafCount());
            return getLeaf0(index, children);
        }

        private T getLeaf0(int index, LL> nodes) {
            FingerTree head = nodes.head();
            int headSize = head.getLeafCount();
            if(index < headSize) {
                return head.getLeaf0(index);
            } else {
                return getLeaf0(index - headSize, nodes.tail());
            }
        }

        @Override
        NonEmptyFingerTree updateLeaf0(int index, T data) {
            assert Lists.isValidIndex(index, getLeafCount());
            return branch(updateLeaf0(index, data, children));
        }

        private Cons> updateLeaf0(
                int index, T data, LL> nodes) {
            NonEmptyFingerTree head = nodes.head();
            int headSize = head.getLeafCount();
            if(index < headSize) {
                return cons(head.updateLeaf0(index, data), nodes.tail());
            } else {
                return cons(head, updateLeaf0(index - headSize, data, nodes.tail()));
            }
        }

        @Override
        final BiIndex locateProgressively0(ToIntFunction metric, int position) {
            assert Lists.isValidPosition(position, measure(metric));
            return locateProgressively0(metric, position, children);
        }

        private BiIndex locateProgressively0(
                ToIntFunction metric,
                int position,
                LL> nodes) {
            NonEmptyFingerTree head = nodes.head();
            int headLen = head.measure(metric);
            if(position < headLen ||
                    (position == headLen && nodes.tail().isEmpty())) {
                return head.locateProgressively0(metric, position);
            } else {
                return locateProgressively0(metric, position - headLen, nodes.tail())
                        .adjustMajor(head.getLeafCount());
            }
        }

        @Override
        final BiIndex locateRegressively0(ToIntFunction metric, int position) {
            assert Lists.isValidPosition(position, measure(metric));
            return locateRegressively0(metric, position, children);
        }

        private BiIndex locateRegressively0(
                ToIntFunction metric,
                int position,
                LL> nodes) {
            NonEmptyFingerTree head = nodes.head();
            int headLen = head.measure(metric);
            if(position <= headLen) {
                return head.locateRegressively0(metric, position);
            } else {
                return locateRegressively0(metric, position - headLen, nodes.tail())
                        .adjustMajor(head.getLeafCount());
            }
        }

        @Override
        public final  R fold(
                R acc,
                BiFunction reduction) {
            return children.fold(acc, (r, n) -> n.fold(r, reduction));
        }

        @Override
        final  R foldBetween0(
                R acc,
                BiFunction reduction,
                int startLeaf,
                int endLeaf) {
            assert Lists.isNonEmptyRange(startLeaf, endLeaf, getLeafCount());
            return foldBetween0(acc, reduction, startLeaf, endLeaf, children);
        }

        private  R foldBetween0(
                R acc,
                BiFunction reduction,
                int startLeaf,
                int endLeaf,
                LL> nodes) {
            FingerTree head = nodes.head();
            int headSize = head.getLeafCount();
            int headTo = Math.min(endLeaf, headSize);
            int tailFrom = Math.max(startLeaf - headSize, 0);
            int tailTo = endLeaf - headSize;
            if(startLeaf < headTo) {
                acc = head.foldBetween0(acc, reduction, startLeaf, headTo);
            }
            if(tailFrom < tailTo) {
                acc = foldBetween0(acc, reduction, tailFrom, tailTo, nodes.tail());
            }
            return acc;
        }

        @Override
        final  R foldBetween0(
                R acc,
                BiFunction reduction,
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TetraFunction rangeReduction) {
            assert Lists.isNonEmptyRange(startPosition, endPosition, measure(metric));
            return foldBetween0(acc, reduction, metric, startPosition, endPosition, rangeReduction, children);
        }

        private  R foldBetween0(
                R acc,
                BiFunction reduction,
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TetraFunction rangeReduction,
                LL> nodes) {
            FingerTree head = nodes.head();
            int headLen = head.measure(metric);
            int headTo = Math.min(endPosition, headLen);
            int tailFrom = Math.max(startPosition - headLen, 0);
            int tailTo = endPosition - headLen;
            if(startPosition < headTo) {
                acc = head.foldBetween0(acc, reduction, metric, startPosition, headTo, rangeReduction);
            }
            if(tailFrom < tailTo) {
                acc = foldBetween0(acc, reduction, metric, tailFrom, tailTo, rangeReduction, nodes.tail());
            }
            return acc;
        }

        @Override
        public S getSummary() {
            return summary;
        }

        @Override
        public Optional getSummaryOpt() {
            return Optional.of(summary);
        }

        @Override
        final S getSummaryBetween0(int startLeaf, int endLeaf) {
            assert Lists.isNonEmptyRange(startLeaf, endLeaf, getLeafCount());
            if(startLeaf == 0 && endLeaf == getLeafCount()) {
                return summary;
            } else {
                return getSummaryBetween0(startLeaf, endLeaf, children);
            }
        }

        private S getSummaryBetween0(
                int startLeaf,
                int endLeaf,
                LL> nodes) {
            FingerTree head = nodes.head();
            int headSize = head.getLeafCount();
            int headTo = Math.min(endLeaf, headSize);
            int tailFrom = Math.max(startLeaf - headSize, 0);
            int tailTo = endLeaf - headSize;
            if(startLeaf < headTo && tailFrom < tailTo) {
                return semigroup.reduce(
                        head.getSummaryBetween0(startLeaf, headTo),
                        getSummaryBetween0(tailFrom, tailTo, nodes.tail()));
            } else if(startLeaf < headTo) {
                return head.getSummaryBetween0(startLeaf, headTo);
            } else if(tailFrom < tailTo) {
                return getSummaryBetween0(tailFrom, tailTo, nodes.tail());
            } else {
                throw new AssertionError("Didn't expect empty range: "
                        + "[" + startLeaf + ", " + endLeaf + ")");
            }
        }

        @Override
        final S getSummaryBetween0(
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TriFunction subSummary) {
            int len = measure(metric);
            assert Lists.isNonEmptyRange(startPosition, endPosition, len);
            if(startPosition == 0 && endPosition == len) {
                return getSummary();
            } else {
                return getSummaryBetween0(metric, startPosition, endPosition, subSummary, children);
            }
        }

        private S getSummaryBetween0(
                ToIntFunction metric,
                int startPosition,
                int endPosition,
                TriFunction subSummary,
                LL> nodes) {
            FingerTree head = nodes.head();
            int headLen = head.measure(metric);
            int headTo = Math.min(endPosition, headLen);
            int tailFrom = Math.max(startPosition - headLen, 0);
            int tailTo = endPosition - headLen;
            if(startPosition < headTo && tailFrom < tailTo) {
                return semigroup.reduce(
                        head.getSummaryBetween0( metric, startPosition, headTo, subSummary),
                        getSummaryBetween0(metric, tailFrom, tailTo, subSummary, nodes.tail()));
            } else if(startPosition < headTo) {
                return head.getSummaryBetween0(metric, startPosition, headTo, subSummary);
            } else if(tailFrom < tailTo) {
                return getSummaryBetween0(metric, tailFrom, tailTo, subSummary, nodes.tail());
            } else {
                throw new AssertionError("Didn't expect empty range: [" + startPosition + ", " + endPosition + ")");
            }
        }

        @Override
        Either, Tuple2, NonEmptyFingerTree>> appendLte(
                FingerTree suffix) {
            assert suffix.getDepth() <= this.getDepth();
            if(suffix.getDepth() == this.getDepth()) {
                return right(t(this, (NonEmptyFingerTree) suffix));
            } else if(children.size() == 2) {
                return children.mapFirst2((left, right) -> {
                    return right.appendLte(suffix).unify(
                            r -> left(branch(left, r)),
                            mr -> left(mr.map((m, r) -> branch(left, m, r))));
                });
            } else {
                assert children.size() == 3;
                return children.mapFirst3((left, middle, right) -> {
                    return right.appendLte(suffix)
                            .mapLeft(r -> branch(left, middle, r))
                            .mapRight(mr -> t(branch(left, middle), mr.map(FingerTree::branch)));
                });
            }
        }

        @Override
        Either, Tuple2, NonEmptyFingerTree>> prependLte(
                FingerTree prefix) {
            assert prefix.getDepth() <= this.getDepth();
            if(prefix.getDepth() == this.getDepth()) {
                return right(t((NonEmptyFingerTree) prefix, this));
            } else if(children.size() == 2) {
                return children.mapFirst2((left, right) -> {
                    return left.prependLte(prefix).unify(
                            l -> left(branch(l, right)),
                            lm -> left(lm.map((l, m) -> branch(l, m, right))));
                });
            } else {
                assert children.size() == 3;
                return children.mapFirst3((left, middle, right) -> {
                    return left.prependLte(prefix)
                            .mapLeft(l -> branch(l, middle, right))
                            .mapRight(lm -> t(lm.map(FingerTree::branch), branch(middle, right)));
                });
            }
        }

        @Override
        Tuple2, FingerTree> split0(int beforeLeaf) {
            assert Lists.isValidPosition(beforeLeaf, getLeafCount());
            if(beforeLeaf == 0) {
                return t(empty(), this);
            } else {
                return split0(beforeLeaf, children);
            }
        }

        private Tuple2, FingerTree> split0(
                int beforeLeaf, LL> nodes) {
            assert beforeLeaf > 0;
            FingerTree head = nodes.head();
            int headSize = head.getLeafCount();
            if(beforeLeaf <= headSize) {
                return head.split0(beforeLeaf)
                        .map((l, r) -> t(l, concat(cons(r, nodes.tail()))));
            } else {
                return split0(beforeLeaf - headSize, nodes.tail())
                        .map((l, r) -> t(head.join(l), r));
            }
        }

        @Override
        BiIndex locate0(
                BiFunction> navigate,
                int position) {
            assert navigate.apply(summary, position).isLeft();
            return locate0(navigate, position, children);
        }

        private BiIndex locate0(
                BiFunction> navigate,
                int position,
                LL> nodes) {
            NonEmptyFingerTree head = nodes.head();
            return navigate.apply(head.getSummary(), position).unify(
                    posInl -> head.locate0(navigate, posInl),
                    posInr -> locate0(navigate, posInr, nodes.tail())
                                  .adjustMajor(head.getLeafCount()));
        }
    }

    public static  FingerTree empty(
            ToSemigroup statisticsProvider) {
        return new Empty<>(statisticsProvider);
    }

    public static  FingerTree mkTree(List items) {
        return mkTree(items, new ToSemigroup() {
            @Override public Void apply(T t) { return null; }
            @Override public Void reduce(Void left, Void right) { return null; }
        });
    }

    public static  FingerTree mkTree(
            List items,
            ToSemigroup summaryProvider) {

        if(items.isEmpty()) {
            return new Empty<>(summaryProvider);
        }

        ArrayList> trees = new ArrayList<>(items.size());
        for(T item: items) {
            trees.add(new Leaf(summaryProvider, item));
        }

        while(trees.size() > 1) {
            final int n = trees.size();
            int src = 0;
            int tgt = 0;
            while(src < n) {
                if(n - src >= 5 || n - src == 3) {
                    NonEmptyFingerTree t1 = trees.get(src++);
                    NonEmptyFingerTree t2 = trees.get(src++);
                    NonEmptyFingerTree t3 = trees.get(src++);
                    Branch t = branch(t1, t2, t3);
                    trees.set(tgt++, t);
                } else { // (n - i) is 4 or 2
                    NonEmptyFingerTree t1 = trees.get(src++);
                    NonEmptyFingerTree t2 = trees.get(src++);
                    Branch t = branch(t1, t2);
                    trees.set(tgt++, t);
                }
            }
            trees.subList(tgt, n).clear();
        }

        return trees.get(0);
    }

    private static  Branch branch(NonEmptyFingerTree left, NonEmptyFingerTree right) {
        return branch(LL.of(left, right));
    }

    private static  Branch branch(
            NonEmptyFingerTree left,
            NonEmptyFingerTree middle,
            NonEmptyFingerTree right) {
        return branch(LL.of(left, middle, right));
    }

    private static  Branch branch(Cons> children) {
        return new Branch<>(children);
    }

    private static  FingerTree concat(
            Cons> nodes) {
        FingerTree head = nodes.head();
        return nodes.tail().fold(
                head,
                FingerTree::join);
    }

    final ToSemigroup semigroup;

    private FingerTree(ToSemigroup semigroup) {
        this.semigroup = semigroup;
    }

    public abstract int getDepth();
    public abstract int getLeafCount();
    public abstract Optional getSummaryOpt();
    public abstract Either, NonEmptyFingerTree> caseEmpty();

    public final boolean isEmpty() {
        return getDepth() == 0;
    }

    public S getSummary(S whenEmpty) {
        return getSummaryOpt().orElse(whenEmpty);
    }

    public T getLeaf(int index) {
        Lists.checkIndex(index, getLeafCount());
        return getLeaf0(index);
    }

    abstract T getLeaf0(int index);

    public Tuple2 get(
            ToIntFunction metric,
            int index) {
        return caseEmpty().unify(
                emptyTree -> { throw new IndexOutOfBoundsException("empty tree"); },
                neTree -> {
                    int size = metric.applyAsInt(neTree.getSummary());
                    Lists.checkIndex(index, size);
                    BiIndex location = locateProgressively(metric, index);
                    return t(getLeaf(location.major), location);
                });
    }

    public  E get(
            ToIntFunction metric,
            int index,
            BiFunction leafAccessor) {
        return locateProgressively(metric, index)
                .map((major, minor) -> leafAccessor.apply(getLeaf(major), minor));
    }

    public NonEmptyFingerTree updateLeaf(int index, T data) {
        Lists.checkIndex(index, getLeafCount());
        return updateLeaf0(index, data);
    }

    abstract NonEmptyFingerTree updateLeaf0(int index, T data);

    public BiIndex locate(
            BiFunction> navigate,
            int position) {

        return caseEmpty().unify(
                emptyTree -> { throw new IndexOutOfBoundsException("no leafs to locate in"); },
                neTree -> { throw new AssertionError("This method must be overridden in non-empty tree"); });
    }

    public BiIndex locateProgressively(
            ToIntFunction metric,
            int position) {

        return caseEmpty().unify(
                emptyTree -> { throw new IndexOutOfBoundsException("no leafs to locate in"); },
                neTree -> { throw new AssertionError("This method must be overridden in non-empty tree"); });
    }

    public BiIndex locateRegressively(
            ToIntFunction metric,
            int position) {

        return caseEmpty().unify(
                emptyTree -> { throw new IndexOutOfBoundsException("no leafs to locate in"); },
                neTree -> { throw new AssertionError("This method must be overridden in non-empty tree"); });
    }

    public abstract  R fold(
            R acc,
            BiFunction reduction);

    public  R foldBetween(
            R acc,
            BiFunction reduction,
            int startLeaf,
            int endLeaf) {
        Lists.checkRange(startLeaf, endLeaf, getLeafCount());
        if(startLeaf == endLeaf) {
            return acc;
        } else {
            return foldBetween0(acc, reduction, startLeaf, endLeaf);
        }
    }

    abstract  R foldBetween0(
            R acc,
            BiFunction reduction,
            int startLeaf, int endLeaf);

    public  R foldBetween(
            R acc,
            BiFunction reduction,
            ToIntFunction metric,
            int startPosition,
            int endPosition,
            TetraFunction rangeReduction) {
        Lists.checkRange(startPosition, endPosition, measure(metric));
        if(startPosition == endPosition) {
            return acc;
        } else {
            return foldBetween0(
                    acc, reduction, metric, startPosition, endPosition, rangeReduction);
        }
    }

    abstract  R foldBetween0(
            R acc,
            BiFunction reduction,
            ToIntFunction metric,
            int startPosition,
            int endPosition,
            TetraFunction rangeReduction);

    public Optional getSummaryBetween(int startLeaf, int endLeaf) {
        Lists.checkRange(startLeaf, endLeaf, getLeafCount());
        return startLeaf == endLeaf
            ? Optional.empty()
            : Optional.of(getSummaryBetween0(startLeaf, endLeaf));
    }

    abstract S getSummaryBetween0(
            int startLeaf,
            int endLeaf);

    public Optional getSummaryBetween(
            ToIntFunction metric,
            int startPosition,
            int endPosition,
            TriFunction subSummary) {
        Lists.checkRange(startPosition, endPosition, measure(metric));
        return startPosition == endPosition
            ? Optional.empty()
            : Optional.of(getSummaryBetween0(metric, startPosition, endPosition, subSummary));
    }

    abstract S getSummaryBetween0(
            ToIntFunction metric,
            int startPosition,
            int endPosition,
            TriFunction subSummary);

    public Tuple2, FingerTree> split(int beforeLeaf) {
        Lists.checkPosition(beforeLeaf, getLeafCount());
        return split0(beforeLeaf);
    }

    abstract Tuple2, FingerTree> split0(int beforeLeaf);

    public FingerTree removeLeafs(int fromLeaf, int toLeaf) {
        Lists.checkRange(fromLeaf, toLeaf, getLeafCount());
        if(fromLeaf == toLeaf) {
            return this;
        } else if(fromLeaf == 0 && toLeaf == getLeafCount()) {
            return empty();
        } else {
            FingerTree left = split0(fromLeaf)._1;
            FingerTree right = split0(toLeaf)._2;
            return left.join(right);
        }
    }

    public FingerTree insertLeaf(int position, T data) {
        Lists.checkPosition(position, getLeafCount());
        return split0(position)
                .map((l, r) -> l.join(leaf(data)).join(r));
    }

    public abstract FingerTree join(FingerTree rightTree);

    public NonEmptyFingerTree append(T data) {
        return leaf(data).prependTree(this);
    }

    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 Empty empty() { return new Empty<>(semigroup); } Leaf leaf(T data) { return new Leaf<>(semigroup, data); } final int measure(ToIntFunction metric) { return getSummaryOpt().map(metric::applyAsInt).orElse(0); } }