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

com.shapesecurity.functional.data.HashTable Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright 2014 Shape Security, Inc.
 *
 * 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 com.shapesecurity.functional.data;

import com.shapesecurity.functional.Effect;
import com.shapesecurity.functional.F;
import com.shapesecurity.functional.F2;
import com.shapesecurity.functional.Pair;
import com.shapesecurity.functional.Unit;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Consumer;

/**
 * An immutable hash trie tree implementation.
 *
 * @param  Key type
 * @param  Value type
 */
@CheckReturnValue
public abstract class HashTable implements Iterable> {
    private final static Hasher EQUALITY_HASHER = new Hasher() {
        @Override
        public int hash(@Nonnull Object data) {
            return data.hashCode();
        }

        @Override
        public boolean eq(@Nonnull Object o, @Nonnull Object b) {
            return o.equals(b);
        }
    };

    public final static Hasher IDENTITY_HASHER = new Hasher() {
        @Override
        public int hash(@Nonnull Object data) {
            return System.identityHashCode(data);
        }

        @Override
        public boolean eq(@Nonnull Object o, @Nonnull Object b) {
            return o == b;
        }
    };

    @Nonnull
    public final Hasher hasher;
    public final int length;

    protected HashTable(@Nonnull Hasher hasher, int length) {
        super();
        this.hasher = hasher;
        this.length = length;
    }

    @SuppressWarnings("unchecked")
    @Nonnull
    public static  Hasher equalityHasher() {
        return (Hasher) EQUALITY_HASHER;
    }

    @SuppressWarnings("unchecked")
    @Nonnull
    @Deprecated
    public static  Hasher defaultHasher() {
        return HashTable.equalityHasher();
    }

    @SuppressWarnings("unchecked")
    @Nonnull
    public static  Hasher identityHasher() {
        return (Hasher) IDENTITY_HASHER;
    }

    @Nonnull
    public static  HashTable empty(@Nonnull Hasher hasher) {
        return new Empty<>(hasher);
    }

    @Nonnull
    public static  HashTable emptyUsingEquality() {
        return empty(HashTable.equalityHasher());
    }

    @Nonnull
    public static  HashTable emptyUsingIdentity() {
        return empty(HashTable.identityHasher());
    }

    @Nonnull
    @Deprecated
    public static  HashTable empty() {
        return HashTable.emptyUsingEquality();
    }

    @Nonnull
    @Deprecated
    public static  HashTable emptyP() {
        return HashTable.emptyUsingIdentity();
    }

    @Nonnull
    public static  HashTable fromUsingEquality(@Nonnull Map map) {
        return HashTable.emptyUsingEquality().putAllFrom(map);
    }

    @Nonnull
    public static  HashTable fromUsingIdentity(@Nonnull IdentityHashMap map) {
        return HashTable.emptyUsingIdentity().putAllFrom(map);
    }

    @Nonnull
    public static  HashTable from(@Nonnull Hasher hasher, @Nonnull Map map) {
        return HashTable.empty(hasher).putAllFrom(map);
    }

    @Nonnull
    public final HashMap toHashMap() {
        if (!this.hasher.equals(HashTable.equalityHasher())) {
            throw new UnsupportedOperationException("HashTable::toHashMap requires an equality hasher.");
        }
        HashMap map = new HashMap<>();
        for (Pair pair : this) {
            map.put(pair.left, pair.right);
        }
        return map;
    }

    @Nonnull
    public final IdentityHashMap toIdentityHashMap() {
        if (!this.hasher.equals(HashTable.identityHasher())) {
            throw new UnsupportedOperationException("HashTable::toIdentityHashMap requires an identity hasher.");
        }
        IdentityHashMap map = new IdentityHashMap<>();
        for (Pair pair : this) {
            map.put(pair.left, pair.right);
        }
        return map;
    }

    @Nonnull
    public static  HashTable fromUsingEquality(@Nonnull Iterable> list) {
        return HashTable.emptyUsingEquality().putAll(list);
    }

    @Nonnull
    public static  HashTable fromUsingIdentity(@Nonnull Iterable> list) {
        return HashTable.emptyUsingIdentity().putAll(list);
    }

    @Nonnull
    public static  HashTable from(@Nonnull Hasher hasher, @Nonnull Iterable> list) {
        return HashTable.empty(hasher).putAll(list);
    }

    @Nonnull
    public final HashTable put(@Nonnull K key, @Nonnull V value) {
        return this.put(key, value, this.hasher.hash(key));
    }

    @Nonnull
    public final HashTable put(@Nonnull Pair pair) {
        return this.put(pair.left, pair.right);
    }

    @Nonnull
    public final HashTable putAll(@Nonnull Iterable> pairs) {
        HashTable table = this;
        for (Pair pair : pairs) {
            table = table.put(pair.left, pair.right);
        }
        return table;
    }

    @Nonnull
    public final HashTable remove(@Nonnull K key) {
        return this.remove(key, this.hasher.hash(key)).orJust(this);
    }

    @Nonnull
    protected abstract HashTable put(@Nonnull K key, @Nonnull V value, int hash);

    @Nonnull
    protected abstract Maybe> remove(@Nonnull K key, int hash);

    @Nonnull
    public final Maybe get(@Nonnull K key) {
        return this.get(key, this.hasher.hash(key));
    }

    @Nonnull
    protected abstract Maybe get(@Nonnull K key, int hash);

    @SuppressWarnings("unchecked")
    @Nonnull
    public final HashTable merge(@Nonnull HashTable tree) {
        return this.merge(tree, (a, b) -> b);
    }

    @Nonnull
    public final HashTable putAllFrom(@Nonnull Map map) {
        HashTable table = this;
        for (Map.Entry entry : map.entrySet()) {
            table = table.put(entry.getKey(), entry.getValue());
        }
        return table;
    }

    @Nonnull
    public abstract HashTable merge(@Nonnull HashTable tree, @Nonnull F2 merger);

    @Nonnull
    public abstract  A foldLeft(@Nonnull F2, A> f, @Nonnull A init);

    @Nonnull
    public abstract  A foldRight(@Nonnull F2, A, A> f, @Nonnull A init);

    @Nonnull
    public ImmutableList> entries() {
        //noinspection unchecked
        Pair[] pairs = ((Pair[]) new Pair[this.length]);
        int[] i = new int[1];
        this.forEach(x -> pairs[i[0]++] = x);
        return ImmutableList.from(pairs);
    }

    @Nonnull
    public ImmutableList> orderedEntries(Comparator comparator) {
        ArrayList> entries = new ArrayList<>(this.length);
        for (Pair entry : this) {
            entries.add(entry);
        }
        entries.sort((e1, e2) -> comparator.compare(e1.left, e2.left));
        return ImmutableList.from(entries);
    }

    public final void foreach(@Nonnull Effect> e) {
        this.forEach(e::e);
    }

    public abstract void forEach(@Nonnull Consumer> e);

    @Nonnull
    public abstract Maybe> find(@Nonnull F, Boolean> f);

    @Nonnull
    public abstract  Maybe findMap(@Nonnull F, Maybe> f);

    @Nonnull
    public final HashTable filter(F, Boolean> f) {
        return this.foldLeft((acc, pair) -> f.apply(pair) ? acc.put(pair.left, pair.right) : acc, empty(this.hasher));
    }

    public abstract  HashTable map(@Nonnull F f);

    public boolean containsKey(@Nonnull K key) {
        return this.containsKey(key, this.hasher.hash(key));
    }

    public abstract boolean containsKey(@Nonnull K key, int hash);

    public boolean containsValue(@Nonnull V value) {
        return this.find(p -> p.right == value).isJust();
    }

    @Nonnull
    public ImmutableSet keys() {
        return new ImmutableSet<>(this.map(F.constant(Unit.unit)));
    }

    /**
     * An empty hash table.
     *
     * @param  Key type
     * @param  Value type
     */
    private final static class Empty extends HashTable {
        protected Empty(@Nonnull Hasher hasher) {
            super(hasher, 0);
        }

        @Nonnull
        @Override
        protected HashTable put(@Nonnull K key, @Nonnull V value, int hash) {
            return new Leaf<>(this.hasher, ImmutableList.of(new Pair<>(key, value)), hash, 1);
        }

        @Nonnull
        @Override
        protected Maybe> remove(@Nonnull K key, int hash) {
            return Maybe.empty();
        }

        @Nonnull
        @Override
        protected Maybe get(@Nonnull K key, int hash) {
            return Maybe.empty();
        }

        @Nonnull
        @Override
        public HashTable merge(@Nonnull HashTable tree, @Nonnull F2 merger) {
            return tree;
        }

        @Nonnull
        @Override
        public  A foldLeft(@Nonnull F2, A> f, @Nonnull A init) {
            return init;
        }

        @Nonnull
        @Override
        public  A foldRight(@Nonnull F2, A, A> f, @Nonnull A init) {
            return init;
        }

        @Override
        public Iterator> iterator() {
            return new Iterator>() {
                @Override
                public boolean hasNext() {
                    return false;
                }

                @Override
                public Pair next() {
                    throw new NoSuchElementException();
                }
            };
        }

        @Override
        public void forEach(@Nonnull Consumer> e) {

        }

        @Nonnull
        @Override
        public Maybe> find(@Nonnull F, Boolean> f) {
            return Maybe.empty();
        }

        @Nonnull
        @Override
        public  Maybe findMap(@Nonnull F, Maybe> f) {
            return Maybe.empty();
        }

        @Override
        public  HashTable map(@Nonnull F f) {
            return empty(this.hasher);
        }

        @Override
        public boolean containsKey(@Nonnull K key, int hash) {
            return false;
        }
    }

    /**
     * A leaf node that contains a list of pairs where all the keys have exactly the same hash
     * code.
     *
     * @param  Key type
     * @param  Value type
     */
    private final static class Leaf extends HashTable {
        @Nonnull
        private final ImmutableList> dataList;
        public int baseHash;

        protected Leaf(@Nonnull Hasher hasher, @Nonnull ImmutableList> dataList, int baseHash, int length) {
            super(hasher, length);
            this.dataList = dataList;
            this.baseHash = baseHash;
        }

        @Nonnull
        @Override
        protected HashTable put(@Nonnull final K key, @Nonnull final V value, final int hash) {
            if (hash == this.baseHash) {
                Pair>> result = this.dataList.mapAccumL((found, kvPair) -> {
                    if (found) {
                        return new Pair<>(true, kvPair);
                    }
                    if (Leaf.this.hasher.eq(kvPair.left, key)) {
                        return new Pair<>(true, new Pair<>(key, value));
                    }
                    return new Pair<>(false, kvPair);
                }, false);
                if (result.left) {
                    return new Leaf<>(this.hasher, result.right, hash, this.length);
                }
                return new Leaf<>(this.hasher, this.dataList.cons(new Pair<>(key, value)), hash, this.length + 1);
            }
            return this.toFork().put(key, value, hash);
        }

        @Nonnull
        @Override
        protected Maybe> remove(@Nonnull final K key, int hash) {
            if (this.baseHash != hash) {
                return Maybe.empty();
            }
            Pair>> result = this.dataList.foldRight((i, p) -> {
                if (p.left) {
                    return new Pair<>(true, p.right.cons(i));
                }
                if (Leaf.this.hasher.eq(i.left, key)) {
                    return new Pair<>(true, p.right);
                }
                return new Pair<>(false, p.right.cons(i));
            }, new Pair<>(false, ImmutableList.empty()));
            if (result.left) {
                if (this.length == 1) {
                    return Maybe.of(empty(this.hasher));
                }
                return Maybe.of(new Leaf<>(this.hasher, result.right, this.baseHash, this.length - 1));
            }
            return Maybe.empty();
        }

        @SuppressWarnings("unchecked")
        private Fork toFork() {
            int subHash = this.baseHash & 31;
            HashTable[] children = new HashTable[32];
            children[subHash] = new Leaf<>(this.hasher, this.dataList, this.baseHash >>> 5, this.length);
            return new Fork<>(this.hasher, children, this.length);
        }

        @Nonnull
        @Override
        protected Maybe get(@Nonnull final K key, final int hash) {
            if (this.baseHash != hash) {
                return Maybe.empty();
            }
            Maybe> pairMaybe = this.dataList.find(kvPair -> Leaf.this.hasher.eq(kvPair.left, key));
            return pairMaybe.map(p -> p.right);
        }

        @SuppressWarnings("unchecked")
        @Nonnull
        @Override
        public HashTable merge(@Nonnull HashTable tree, @Nonnull final F2 merger) {
            if (tree instanceof Empty) {
                return this;
            } else if (tree instanceof Leaf) {
                final Leaf leaf = (Leaf) tree;
                if (leaf.baseHash == this.baseHash) {
                    final Pair[] pairs = this.dataList.toArray(new Pair[this.dataList.length]);
                    ImmutableList> right = leaf.dataList.foldLeft(
                            (@Nonnull ImmutableList> result, @Nonnull Pair kvPair) -> {
                                for (int i = 0; i < pairs.length; i++) {
                                    if (Leaf.this.hasher.eq(pairs[i].left, kvPair.left)) {
                                        pairs[i] = new Pair<>(pairs[i].left, merger.apply(pairs[i].right, kvPair.right));
                                        return result;
                                    }
                                }
                                return result.cons(kvPair);
                            }, ImmutableList.empty());
                    ImmutableList> newList = ImmutableList.from(pairs).append(right);
                    return new Leaf<>(this.hasher, newList, this.baseHash, newList.length);
                }
            }
            return this.toFork().merge(tree, merger);
        }

        @Nonnull
        public  A foldLeft(@Nonnull F2, A> f, @Nonnull A init) {
            return this.dataList.foldLeft(f, init);
        }

        @Nonnull
        @Override
        public  A foldRight(@Nonnull F2, A, A> f, @Nonnull A init) {
            return this.dataList.foldRight(f, init);
        }

        @Override
        public Iterator> iterator() {
            return dataList.iterator();
        }

        @Override
        public void forEach(@Nonnull Consumer> e) {
            this.dataList.forEach(e);
        }

        @Nonnull
        @Override
        public Maybe> find(@Nonnull F, Boolean> f) {
            return this.dataList.find(f);
        }

        @Nonnull
        @Override
        public  Maybe findMap(@Nonnull F, Maybe> f) {
            return this.dataList.findMap(f);
        }

        @Override
        public  Leaf map(@Nonnull F f) {
            return new Leaf<>(this.hasher, this.dataList.map(pair -> pair.mapRight(f)), this.baseHash, this.length);
        }

        @Override
        public boolean containsKey(@Nonnull K key, int hash) {
            return hash == this.baseHash
                    && this.dataList.exists(kvPair -> Leaf.this.hasher.eq(kvPair.left, key));
        }
    }

    private final static class Fork extends HashTable {
        @Nonnull
        private final HashTable[] children;

        private Fork(@Nonnull Hasher hasher, @Nonnull HashTable[] children, int length) {
            super(hasher, length);
            this.children = children;
        }

        @Nonnull
        @Override
        protected HashTable put(@Nonnull K key, @Nonnull V value, int hash) {
            int subHash = hash & 31;
            HashTable[] cloned = Fork.this.children.clone();
            if (cloned[subHash] == null) {
                cloned[subHash] = new Leaf<>(Fork.this.hasher, ImmutableList.empty(), hash >>> 5, 0);
            }
            //noinspection UnnecessaryLocalVariable
            int oldLength = cloned[subHash].length;
            cloned[subHash] = cloned[subHash].put(key, value, hash >>> 5);
            return new Fork<>(this.hasher, cloned, this.length - oldLength + cloned[subHash].length);
        }

        @Nonnull
        @Override
        protected Maybe> remove(@Nonnull K key, int hash) {
            final int subHash = hash & 31;
            if (this.children[subHash] == null) {
                return Maybe.empty();
            }
            Maybe> removed = this.children[subHash].remove(key, hash >>> 5);
            return removed.map(newChild -> {
                if (Fork.this.length == 1) {
                    return new Empty<>(Fork.this.hasher);
                }
                HashTable[] cloned = Fork.this.children.clone();
                cloned[subHash] = newChild;
                return new Fork<>(Fork.this.hasher, cloned, Fork.this.length - 1);
            });
        }

        @Nonnull
        @Override
        protected Maybe get(@Nonnull K key, int hash) {
            int subHash = hash & 31;
            if (this.children[subHash] == null) {
                return Maybe.empty();
            }
            return this.children[subHash].get(key, hash >>> 5);
        }

        @Nonnull
        @Override
        public Fork merge(@Nonnull HashTable tree, @Nonnull F2 merger) {
            if (tree instanceof Empty) {
                return this;
            } else if (tree instanceof Leaf) {
                Leaf leaf = (Leaf) tree;
                return this.mergeFork(leaf.toFork(), merger);
            }
            return this.mergeFork(((Fork) tree), merger);
        }

        @Nonnull
        private Fork mergeFork(@Nonnull Fork tree, @Nonnull F2 merger) {
            // Mutable array.
            HashTable[] cloned = Fork.this.children.clone();
            int count = 0;
            for (int i = 0; i < cloned.length; i++) {
                if (cloned[i] == null) {
                    cloned[i] = tree.children[i];
                } else if (tree.children[i] != null) {
                    cloned[i] = cloned[i].merge(tree.children[i], merger);
                }
                if (cloned[i] != null) {
                    count += cloned[i].length;
                }
            }
            return new Fork<>(this.hasher, cloned, count);
        }

        @Nonnull
        @Override
        public  A foldLeft(@Nonnull F2, A> f, @Nonnull A init) {
            for (@Nullable HashTable child : this.children) {
                if (child != null) {
                    init = child.foldLeft(f, init);
                }
            }
            return init;
        }

        @Nonnull
        @Override
        public  A foldRight(@Nonnull F2, A, A> f, @Nonnull A init) {
            for (int i = this.children.length - 1; i >= 0; i--) {
                if (this.children[i] == null) {
                    continue;
                }
                init = this.children[i].foldRight(f, init);
            }
            return init;
        }

        public Iterator> iterator() {
            return new Iterator>() {
                @SuppressWarnings("unchecked")
                private final HashTable[] stack = new HashTable[Fork.this.length];
                private Iterator> currentIterator = null;
                int i = 0;

                {
                    this.stack[this.i++] = Fork.this;
                }

                private void updateState() {
                    if (currentIterator != null && currentIterator.hasNext()) {
                        return;
                    } else {
                        currentIterator = null;
                    }
                    while (this.i > 0) {
                        this.i--;
                        HashTable curr = this.stack[this.i];
                        if (curr instanceof Fork) {
                            Fork fork = (Fork) curr;
                            for (HashTable child : fork.children) {
                                if (child != null && child.length > 0) {
                                    this.stack[this.i] = child;
                                    this.i++;
                                }
                            }
                        } else if (curr instanceof Leaf) {
                            currentIterator = ((Leaf) curr).dataList.iterator();
                            if (currentIterator.hasNext()) {
                                return;
                            } else {
                                currentIterator = null;
                            }
                        }
                    }
                }

                @Override
                public boolean hasNext() {
                    updateState();
                    return currentIterator != null;
                }

                @Override
                public Pair next() {
                    updateState();
                    if (currentIterator == null) {
                        throw new NoSuchElementException();
                    }
                    return currentIterator.next();
                }
            };
        }

        @Override
        public void forEach(@Nonnull Consumer> e) {
            for (@Nullable HashTable child : this.children) {
                if (child != null) {
                    child.forEach(e);
                }
            }
        }

        @Nonnull
        @Override
        public Maybe> find(@Nonnull F, Boolean> f) {
            HashTable[] children = this.children;
            for (HashTable child : children) {
                if (child != null) {
                    Maybe> p = child.find(f);
                    if (p.isJust()) {
                        return p;
                    }
                }
            }
            return Maybe.empty();
        }

        @Nonnull
        @Override
        public  Maybe findMap(@Nonnull F, Maybe> f) {
            HashTable[] children = this.children;
            for (HashTable child : children) {
                if (child != null) {
                    Maybe p = child.findMap(f);
                    if (p.isJust()) {
                        return p;
                    }
                }
            }
            return Maybe.empty();
        }

        @SuppressWarnings("unchecked")
        @Override
        public  Fork map(@Nonnull F f) {
            HashTable[] clone = new HashTable[this.children.length];
            for (int i = 0; i < clone.length; i++) {
                if (this.children[i] != null) {
                    clone[i] = this.children[i].map(f);
                }
            }
            return new Fork<>(this.hasher, clone, this.length);
        }

        @Override
        public boolean containsKey(@Nonnull K key, int hash) {
            int subHash = hash & 31;
            return this.children[subHash] != null && this.children[subHash].containsKey(key, hash >>> 5);
        }
    }
}