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

javaslang.collection.HashMap Maven / Gradle / Ivy

There is a newer version: 2.1.0-alpha
Show newest version
/*     / \____  _    _  ____   ______  / \ ____  __    _______
 *    /  /    \/ \  / \/    \ /  /\__\/  //    \/  \  //  /\__\   JΛVΛSLΛNG
 *  _/  /  /\  \  \/  /  /\  \\__\\  \  //  /\  \ /\\/ \ /__\ \   Copyright 2014-2016 Javaslang, http://javaslang.io
 * /___/\_/  \_/\____/\_/  \_/\__\/__/\__\_/  \_//  \__/\_____/   Licensed under the Apache License, Version 2.0
 */
package javaslang.collection;

import javaslang.Kind2;
import javaslang.Tuple;
import javaslang.Tuple2;
import javaslang.control.Option;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.*;
import java.util.stream.Collector;

/**
 * An immutable {@code HashMap} implementation based on a
 * Hash array mapped trie (HAMT).
 *
 * @author Ruslan Sennov, Patryk Najda, Daniel Dietrich
 * @since 2.0.0
 */
public final class HashMap implements Kind2, K, V>, Map, Serializable {

    private static final long serialVersionUID = 1L;

    private static final HashMap EMPTY = new HashMap<>(HashArrayMappedTrie.empty());

    private final HashArrayMappedTrie trie;

    private HashMap(HashArrayMappedTrie trie) {
        this.trie = trie;
    }

    /**
     * Returns a {@link java.util.stream.Collector} which may be used in conjunction with
     * {@link java.util.stream.Stream#collect(java.util.stream.Collector)} to obtain a {@link javaslang.collection.HashMap}.
     *
     * @param  The key type
     * @param  The value type
     * @return A {@link javaslang.collection.HashMap} Collector.
     */
    public static  Collector, ArrayList>, HashMap> collector() {
        final Supplier>> supplier = ArrayList::new;
        final BiConsumer>, Tuple2> accumulator = ArrayList::add;
        final BinaryOperator>> combiner = (left, right) -> {
            left.addAll(right);
            return left;
        };
        final Function>, HashMap> finisher = HashMap::ofEntries;
        return Collector.of(supplier, accumulator, combiner, finisher);
    }

    @SuppressWarnings("unchecked")
    public static  HashMap empty() {
        return (HashMap) EMPTY;
    }

    /**
     * Narrows a widened {@code HashMap} to {@code HashMap}
     * by performing a type safe-cast. This is eligible because immutable/read-only
     * collections are covariant.
     *
     * @param hashMap A {@code HashMap}.
     * @param      Key type
     * @param      Value type
     * @return the given {@code hashMap} instance as narrowed type {@code HashMap}.
     */
    @SuppressWarnings("unchecked")
    public static  HashMap narrow(HashMap hashMap) {
        return (HashMap) hashMap;
    }

    /**
     * Returns a singleton {@code HashMap}, i.e. a {@code HashMap} of one element.
     *
     * @param entry A map entry.
     * @param    The key type
     * @param    The value type
     * @return A new Map containing the given entry
     */
    public static  HashMap of(Tuple2 entry) {
        return new HashMap<>(HashArrayMappedTrie. empty().put(entry._1, entry._2));
    }

    /**
     * Returns a {@code HashMap}, from a source java.util.Map.
     *
     * @param map A map entry.
     * @param  The key type
     * @param  The value type
     * @return A new Map containing the given map
     */
    public static  HashMap ofAll(java.util.Map map) {
        Objects.requireNonNull(map, "map is null");
        HashArrayMappedTrie tree = HashArrayMappedTrie.empty();
        for (java.util.Map.Entry entry : map.entrySet()) {
            tree = tree.put(entry.getKey(), entry.getValue());
        }
        return wrap(tree);
    }

    /**
     * Returns a singleton {@code HashMap}, i.e. a {@code HashMap} of one element.
     *
     * @param key   A singleton map key.
     * @param value A singleton map value.
     * @param    The key type
     * @param    The value type
     * @return A new Map containing the given entry
     */
    public static  HashMap of(K key, V value) {
        return new HashMap<>(HashArrayMappedTrie. empty().put(key, value));
    }

    /**
     * Creates a HashMap of the given list of key-value pairs.
     *
     * @param pairs A list of key-value pairs
     * @param    The key type
     * @param    The value type
     * @return A new Map containing the given entries
     */
    @SuppressWarnings("unchecked")
    public static  HashMap of(Object... pairs) {
        Objects.requireNonNull(pairs, "pairs is null");
        if ((pairs.length & 1) != 0) {
            throw new IllegalArgumentException("Odd length of key-value pairs list");
        }
        HashArrayMappedTrie trie = HashArrayMappedTrie.empty();
        for (int i = 0; i < pairs.length; i += 2) {
            trie = trie.put((K) pairs[i], (V) pairs[i + 1]);
        }
        return wrap(trie);
    }

    /**
     * Returns an HashMap containing {@code n} values of a given Function {@code f}
     * over a range of integer values from 0 to {@code n - 1}.
     *
     * @param  The key type
     * @param  The value type
     * @param n   The number of elements in the HashMap
     * @param f   The Function computing element values
     * @return An HashMap consisting of elements {@code f(0),f(1), ..., f(n - 1)}
     * @throws NullPointerException if {@code f} is null
     */
    @SuppressWarnings("unchecked")
    public static  HashMap tabulate(int n, Function> f) {
        Objects.requireNonNull(f, "f is null");
        return ofEntries(Collections.tabulate(n, (Function>) f));
    }

    /**
     * Returns an HashMap containing {@code n} values supplied by a given Supplier {@code s}.
     *
     * @param  The key type
     * @param  The value type
     * @param n   The number of elements in the HashMap
     * @param s   The Supplier computing element values
     * @return An HashMap of size {@code n}, where each element contains the result supplied by {@code s}.
     * @throws NullPointerException if {@code s} is null
     */
    @SuppressWarnings("unchecked")
    public static  HashMap fill(int n, Supplier> s) {
        Objects.requireNonNull(s, "s is null");
        return ofEntries(Collections.fill(n, (Supplier>) s));
    }

    /**
     * Creates a HashMap of the given entries.
     *
     * @param entries Map entries
     * @param      The key type
     * @param      The value type
     * @return A new Map containing the given entries
     */
    @SafeVarargs
    public static  HashMap ofEntries(java.util.Map.Entry... entries) {
        Objects.requireNonNull(entries, "entries is null");
        HashArrayMappedTrie trie = HashArrayMappedTrie.empty();
        for (java.util.Map.Entry entry : entries) {
            trie = trie.put(entry.getKey(), entry.getValue());
        }
        return wrap(trie);
    }

    /**
     * Creates a HashMap of the given entries.
     *
     * @param entries Map entries
     * @param      The key type
     * @param      The value type
     * @return A new Map containing the given entries
     */
    @SafeVarargs
    public static  HashMap ofEntries(Tuple2... entries) {
        Objects.requireNonNull(entries, "entries is null");
        HashArrayMappedTrie trie = HashArrayMappedTrie.empty();
        for (Tuple2 entry : entries) {
            trie = trie.put(entry._1, entry._2);
        }
        return wrap(trie);
    }

    /**
     * Creates a HashMap of the given entries.
     *
     * @param entries Map entries
     * @param      The key type
     * @param      The value type
     * @return A new Map containing the given entries
     */
    @SuppressWarnings("unchecked")
    public static  HashMap ofEntries(Iterable> entries) {
        Objects.requireNonNull(entries, "entries is null");
        if (entries instanceof HashMap) {
            return (HashMap) entries;
        } else {
            HashArrayMappedTrie trie = HashArrayMappedTrie.empty();
            for (Tuple2 entry : entries) {
                trie = trie.put(entry._1, entry._2);
            }
            return wrap(trie);
        }
    }

    @Override
    public  HashMap bimap(Function keyMapper, Function valueMapper) {
        Objects.requireNonNull(keyMapper, "keyMapper is null");
        Objects.requireNonNull(valueMapper, "valueMapper is null");
        final Iterator> entries = iterator().map(entry -> Tuple.of(keyMapper.apply(entry._1), valueMapper.apply(entry._2)));
        return HashMap.ofEntries(entries);
    }

    @Override
    public boolean containsKey(K key) {
        return trie.containsKey(key);
    }

    @Override
    public HashMap distinct() {
        return Maps.distinct(this);
    }

    @Override
    public HashMap distinctBy(Comparator> comparator) {
        return Maps.distinctBy(this, this::createFromEntries, comparator);
    }

    @Override
    public  HashMap distinctBy(Function, ? extends U> keyExtractor) {
        return Maps.distinctBy(this, this::createFromEntries, keyExtractor);
    }

    @Override
    public HashMap drop(long n) {
        return Maps.drop(this, this::createFromEntries, HashMap::empty, n);
    }

    @Override
    public HashMap dropRight(long n) {
        return Maps.dropRight(this, this::createFromEntries, HashMap::empty, n);
    }

    @Override
    public HashMap dropUntil(Predicate> predicate) {
        return Maps.dropUntil(this, this::createFromEntries, predicate);
    }

    @Override
    public HashMap dropWhile(Predicate> predicate) {
        return Maps.dropWhile(this, this::createFromEntries, predicate);
    }

    @Override
    public HashMap filter(Predicate> predicate) {
        return Maps.filter(this, this::createFromEntries, predicate);
    }

    @Override
    public  HashMap flatMap(BiFunction>> mapper) {
        Objects.requireNonNull(mapper, "mapper is null");
        return foldLeft(HashMap. empty(), (acc, entry) -> {
            for (Tuple2 mappedEntry : mapper.apply(entry._1, entry._2)) {
                acc = acc.put(mappedEntry);
            }
            return acc;
        });
    }

    @Override
    public Option get(K key) {
        return trie.get(key);
    }

    @Override
    public  Map> groupBy(Function, ? extends C> classifier) {
        return Maps.groupBy(this, this::createFromEntries, classifier);
    }

    @Override
    public Iterator> grouped(long size) {
        return Maps.grouped(this, this::createFromEntries, size);
    }

    @Override
    public Tuple2 head() {
        if (isEmpty()) {
            throw new NoSuchElementException("head of empty HashMap");
        } else {
            return iterator().next();
        }
    }

    @Override
    public HashMap init() {
        if (trie.isEmpty()) {
            throw new UnsupportedOperationException("init of empty HashMap");
        } else {
            return remove(last()._1);
        }
    }

    @Override
    public Option> initOption() {
        return Maps.initOption(this);
    }

    @Override
    public boolean isEmpty() {
        return trie.isEmpty();
    }

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

    @Override
    public Set keySet() {
        return HashSet.ofAll(iterator().map(Tuple2::_1));
    }

    @Override
    public  HashMap map(BiFunction> mapper) {
        Objects.requireNonNull(mapper, "mapper is null");
        return foldLeft(HashMap.empty(), (acc, entry) -> acc.put(entry.map(mapper)));
    }

    @Override
    public  HashMap mapValues(Function valueMapper) {
        Objects.requireNonNull(valueMapper, "valueMapper is null");
        return map((k, v) -> Tuple.of(k, valueMapper.apply(v)));
    }

    @Override
    public HashMap merge(Map that) {
        return Maps.merge(this, this::createFromEntries, that);
    }

    @Override
    public  HashMap merge(Map that,
                                             BiFunction collisionResolution) {
        return Maps.merge(this, this::createFromEntries, that, collisionResolution);
    }

    @Override
    public Tuple2, HashMap> partition(Predicate> predicate) {
        return Maps.partition(this, this::createFromEntries, predicate);
    }

    @Override
    public HashMap peek(Consumer> action) {
        return Maps.peek(this, action);
    }

    @Override
    public HashMap put(K key, V value) {
        return new HashMap<>(trie.put(key, value));
    }

    @Override
    public HashMap put(Tuple2 entry) {
        return Maps.put(this, entry);
    }

    @Override
    public HashMap remove(K key) {
        final HashArrayMappedTrie result = trie.remove(key);
        return result.size() == trie.size() ? this : wrap(result);
    }

    @Override
    public HashMap removeAll(Iterable keys) {
        Objects.requireNonNull(keys, "keys is null");
        HashArrayMappedTrie result = trie;
        for (K key : keys) {
            result = result.remove(key);
        }
        return result.size() == trie.size() ? this : wrap(result);
    }

    @Override
    public HashMap replace(Tuple2 currentElement, Tuple2 newElement) {
        return Maps.replace(this, currentElement, newElement);
    }

    @Override
    public HashMap replaceAll(Tuple2 currentElement, Tuple2 newElement) {
        return Maps.replaceAll(this, currentElement, newElement);
    }

    @Override
    public HashMap retainAll(Iterable> elements) {
        Objects.requireNonNull(elements, "elements is null");
        HashArrayMappedTrie tree = HashArrayMappedTrie.empty();
        for (Tuple2 entry : elements) {
            if (contains(entry)) {
                tree = tree.put(entry._1, entry._2);
            }
        }
        return wrap(tree);
    }

    @Override
    public HashMap scan(
            Tuple2 zero,
            BiFunction, ? super Tuple2, ? extends Tuple2> operation) {
        return Maps.scan(this, HashMap::empty, zero, operation);
    }

    @Override
    public int size() {
        return trie.size();
    }

    @Override
    public Iterator> sliding(long size) {
        return Maps.sliding(this, this::createFromEntries, size);
    }

    @Override
    public Iterator> sliding(long size, long step) {
        return Maps.sliding(this, this::createFromEntries, size, step);
    }

    @Override
    public Tuple2, HashMap> span(Predicate> predicate) {
        return Maps.span(this, this::createFromEntries, predicate);
    }

    @Override
    public HashMap tail() {
        if (trie.isEmpty()) {
            throw new UnsupportedOperationException("tail of empty HashMap");
        } else {
            return remove(head()._1);
        }
    }

    @Override
    public Option> tailOption() {
        return Maps.tailOption(this);
    }

    @Override
    public HashMap take(long n) {
        return Maps.take(this, this::createFromEntries, n);
    }

    @Override
    public HashMap takeRight(long n) {
        return Maps.takeRight(this, this::createFromEntries, n);
    }

    @Override
    public HashMap takeUntil(Predicate> predicate) {
        return Maps.takeUntil(this, this::createFromEntries, predicate);
    }

    @Override
    public HashMap takeWhile(Predicate> predicate) {
        return Maps.takeWhile(this, this::createFromEntries, predicate);
    }

    @Override
    public java.util.HashMap toJavaMap() {
        return toJavaMap(java.util.HashMap::new, t -> t);
    }

    @Override
    public Seq values() {
        return map(Tuple2::_2);
    }

    @Override
    public int hashCode() {
        return trie.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof HashMap) {
            final HashMap that = (HashMap) o;
            return this.trie.equals(that.trie);
        } else {
            return false;
        }
    }

    private Object readResolve() {
        return isEmpty() ? EMPTY : this;
    }

    @Override
    public String stringPrefix() {
        return "HashMap";
    }

    @Override
    public String toString() {
        return mkString(stringPrefix() + "(", ", ", ")");
    }

    private static  HashMap wrap(HashArrayMappedTrie trie) {
        return trie.isEmpty() ? empty() : new HashMap<>(trie);
    }

    // We need this method to narrow the argument of `ofEntries`.
    // If this method is static with type args , the jdk fails to infer types at the call site.
    private HashMap createFromEntries(Iterable> tuples) {
        return HashMap.ofEntries(tuples);
    }
}