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

org.fxmisc.wellbehaved.event.internal.PrefixTree Maven / Gradle / Ivy

There is a newer version: 1.11
Show newest version
package org.fxmisc.wellbehaved.event.internal;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * Prefix tree (Trie) with an additional property that no data is stored in
 * internal nodes.
 *
 * @param  type of "strings" used to index values
 * @param  type of values (data) indexed by this trie
 */
public abstract class PrefixTree {

    public static interface Ops {
        boolean isPrefixOf(K k1, K k2);
        K commonPrefix(K k1, K k2);
        V promote(V v, K oldKey, K newKey);
        V squash(V v1, V v2);
    }

    private static class Empty extends PrefixTree {

        public Empty(Ops ops) {
            super(ops);
        }

        @Override
        public Stream> entries() {
            return Stream.empty();
        }

        @Override
        public PrefixTree insert(K key, V value, BiFunction combine) {
            return insertInside(key, value, combine);
        }

        @Override
        PrefixTree insertInside(K key, V value, BiFunction combine) {
            return new Data<>(ops, key, value);
        }

        @Override
        public  PrefixTree map(
                Function f,
                Ops ops) {
            return new Empty<>(ops);
        }

    }

    private static abstract class NonEmpty extends PrefixTree {
        public NonEmpty(Ops ops) {
            super(ops);
        }

        abstract K getPrefix();
        abstract Data collapse();

        @Override
        public abstract  NonEmpty map(Function f, Ops ops);

        @Override
        abstract NonEmpty insertInside(K key, V value, BiFunction combine);

        @Override
        public PrefixTree insert(K key, V value, BiFunction combine) {
            if(ops.isPrefixOf(key, getPrefix())) { // key is a prefix of this tree
                return new Data<>(ops, key, value).insertInside(collapse(), flip(combine));
            } else if(ops.isPrefixOf(getPrefix(), key)) { // key is inside this tree
                return insertInside(key, value, combine);
            } else {
                return new Branch<>(ops, this, new Data<>(ops, key, value));
            }
        }
    }

    private static class Branch extends NonEmpty {
        private final K prefix;
        private final List> subTrees;

        Branch(Ops ops, K prefix, List> subTrees) {
            super(ops);

            assert Objects.equals(prefix, subTrees.stream().map(NonEmpty::getPrefix).reduce(ops::commonPrefix).get());
            assert subTrees.stream().noneMatch(tree -> Objects.equals(tree.getPrefix(), prefix));

            this.prefix = prefix;
            this.subTrees = subTrees;
        }

        private Branch(Ops ops, NonEmpty t1, NonEmpty t2) {
            this(ops, ops.commonPrefix(t1.getPrefix(), t2.getPrefix()), Arrays.asList(t1, t2));
        }

        @Override
        K getPrefix() {
            return prefix;
        }

        @Override
        public Stream> entries() {
            return subTrees.stream().flatMap(tree -> tree.entries());
        }

        @Override
        Data collapse() {
            return subTrees.stream()
                    .map(tree -> tree.collapse().promote(prefix))
                    .reduce(Data::squash).get();
        }

        @Override
        NonEmpty insertInside(K key, V value, BiFunction combine) {
            assert ops.isPrefixOf(prefix, key);

            if(Objects.equals(key, prefix)) {
                return new Data<>(ops, key, value).insertInside(collapse(), flip(combine));
            }

            // try to find a sub-tree that has common prefix with key longer than this branch's prefix
            for(int i = 0; i < subTrees.size(); ++i) {
                NonEmpty st = subTrees.get(i);
                K commonPrefix = ops.commonPrefix(key, st.getPrefix());
                if(!Objects.equals(commonPrefix, prefix)) {
                    if(Objects.equals(commonPrefix, st.getPrefix())) {
                        // st contains key, insert inside st
                        return replaceBranch(i, st.insertInside(key, value, combine));
                    } else if(Objects.equals(commonPrefix, key)) {
                        // st is under key, insert st inside Data(key, value)
                        return replaceBranch(i, new Data<>(ops, key, value).insertInside(st.collapse(), flip(combine)));
                    } else {
                        return replaceBranch(i, new Branch<>(ops, st, new Data<>(ops, key, value)));
                    }
                }
            }

            // no branch intersects key, adjoin Data(key, value) to this branch
            List> branches = new ArrayList<>(subTrees.size() + 1);
            branches.addAll(subTrees);
            branches.add(new Data<>(ops, key, value));
            return new Branch<>(ops, prefix, branches);
        }

        private Branch replaceBranch(int i, NonEmpty replacement) {
            assert ops.isPrefixOf(prefix, replacement.getPrefix());
            assert ops.isPrefixOf(replacement.getPrefix(), subTrees.get(i).getPrefix());

            ArrayList> branches = new ArrayList<>(subTrees);
            branches.set(i, replacement);
            return new Branch<>(ops, prefix, branches);
        }

        @Override
        public  NonEmpty map(
                Function f,
                Ops ops) {
            List> mapped = new ArrayList<>(subTrees.size());
            for(NonEmpty tree: subTrees) {
                mapped.add(tree.map(f, ops));
            }
            return new Branch<>(ops, prefix, mapped);
        }
    }

    private static class Data extends NonEmpty {
        private final K key;
        private final V value;

        Data(Ops ops, K key, V value) {
            super(ops);
            this.key = key;
            this.value = value;
        }

        @Override
        K getPrefix() {
            return key;
        }

        @Override
        public Stream> entries() {
            return Stream.of(new SimpleEntry<>(key, value));
        }

        @Override
        Data collapse() {
            return this;
        }

        @Override
        NonEmpty insertInside(K key, V value, BiFunction combine) {
            assert ops.isPrefixOf(this.key, key);
            return new Data<>(
                    this.ops,
                    this.key,
                    combine.apply(this.value, ops.promote(value, key, this.key)));
        }

        NonEmpty insertInside(NonEmpty tree, BiFunction combine) {
            Data d = tree.collapse();
            return insertInside(d.key, d.value, combine);
        }

        Data promote(K key) {
            assert ops.isPrefixOf(key, this.key);
            return new Data<>(ops, key, ops.promote(value, this.key, key));
        }

        Data squash(Data that) {
            assert Objects.equals(this.key, that.key);
            return new Data<>(ops, key, ops.squash(this.value, that.value));
        }

        @Override
        public  Data map(
                Function f,
                Ops ops) {
            return new Data<>(ops, key, f.apply(value));
        }
    }

    public static  PrefixTree empty(Ops ops) {
        return new Empty<>(ops);
    }

    private static  BiFunction flip(BiFunction f) {
        return (a, b) -> f.apply(b, a);
    }


    final Ops ops;

    private PrefixTree(Ops ops) {
        this.ops = ops;
    }

    public abstract Stream> entries();
    public abstract PrefixTree insert(K key, V value, BiFunction combine);
    public abstract  PrefixTree map(Function f, Ops ops);

    public final PrefixTree map(Function f) {
        return map(f, ops);
    }

    abstract PrefixTree insertInside(K key, V value, BiFunction combine);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy