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

io.lacuna.bifurcan.IntMap Maven / Gradle / Ivy

package io.lacuna.bifurcan;

import io.lacuna.bifurcan.nodes.IntMapNodes;
import io.lacuna.bifurcan.nodes.IntMapNodes.Node;
import io.lacuna.bifurcan.utils.Iterators;

import java.util.*;
import java.util.Map;
import java.util.function.*;

/**
 * A map which has integer keys, which is an combination of Okasaki and Gill's
 * Fast Mergeable Integer Maps with the memory layout
 * suggested by Steindorfer and Vinju used in {@link io.lacuna.bifurcan.Map}, with which it shares the same broad performance
 * characteristics.
 * 

* This collection keeps the keys in sorted order, and can thought of as either a map of integers or a sparse vector. * It provides {@code slice()}, {@code floor()}, and {@code ceil()} methods which allow for lookups and filtering on * its keys. * * @author ztellman */ public class IntMap implements ISortedMap, Cloneable { static final ToIntFunction HASH = n -> (int) (n ^ (n >>> 32)); private static final Object DEFAULT_VALUE = new Object(); final Object editor; private Node neg, pos; /** * @param m another map * @return a forked copy of the map */ public static IntMap from(IMap m) { if (m instanceof IntMap) { return (IntMap) m.forked(); } else { return from(m.entries()); } } /** * @param m a {@code java.util.Map} * @return a forked copy of the map */ public static IntMap from(Map m) { return from(m.entrySet()); } /** * @param collection a collection of {@code java.util.map.Entry} objects * @return an {@code IntMap} representing the entries in the collection */ public static IntMap from(Collection> collection) { IntMap map = new IntMap().linear(); for (Map.Entry entry : collection) { map = map.put((long) entry.getKey(), entry.getValue()); } return map.forked(); } /** * @param list a list of {@code IEntry} objects * @return an {@code IntMap} representing the entries in the list */ public static IntMap from(IList> list) { IntMap map = new IntMap().linear(); for (IEntry entry : list) { map = map.put((long) entry.key(), entry.value()); } return map.forked(); } public IntMap() { this.neg = Node.NEG_EMPTY; this.pos = Node.POS_EMPTY; this.editor = null; } private IntMap(Node neg, Node pos, boolean linear) { this.neg = neg; this.pos = pos; this.editor = linear ? new Object() : null; } /// @Override public ToIntFunction keyHash() { return HASH; } @Override public BiPredicate keyEquality() { return Long::equals; } /** * @param min the inclusive minimum key value * @param max the inclusive maximum key value * @return a map representing all entries within [{@code} min, {@code} max] */ public IntMap slice(long min, long max) { Node negPrime = neg.slice(editor, min, max); Node posPrime = pos.slice(editor, min, max); return new IntMap( negPrime == null ? Node.NEG_EMPTY : negPrime, posPrime == null ? Node.POS_EMPTY : posPrime, isLinear()); } @Override public IntMap slice(Long min, Long max) { return slice((long) min, (long) max); } @Override public IntMap merge(IMap b, BinaryOperator mergeFn) { if (b instanceof IntMap) { IntMap m = (IntMap) b; return new IntMap( IntMapNodes.merge(new Object(), neg, m.neg, mergeFn), IntMapNodes.merge(new Object(), pos, m.pos, mergeFn), isLinear()); } else { return (IntMap) Maps.merge(this.clone(), b, mergeFn); } } @Override public IntMap difference(IMap b) { if (b instanceof IntMap) { IntMap m = (IntMap) b; Node negPrime = IntMapNodes.difference(new Object(), neg, m.neg); Node posPrime = IntMapNodes.difference(new Object(), pos, m.pos); return new IntMap(negPrime == null ? Node.NEG_EMPTY : negPrime, posPrime == null ? Node.POS_EMPTY : posPrime, isLinear()); } else { return (IntMap) Maps.difference(this.clone(), b.keys()); } } @Override public IntMap intersection(IMap b) { if (b instanceof IntMap) { IntMap m = (IntMap) b; Node negPrime = IntMapNodes.intersection(new Object(), neg, m.neg); Node posPrime = IntMapNodes.intersection(new Object(), pos, m.pos); return new IntMap(negPrime == null ? Node.NEG_EMPTY : negPrime, posPrime == null ? Node.POS_EMPTY : posPrime, isLinear()); } else { IntMap result = (IntMap) Maps.intersection(new IntMap().linear(), this, b.keys()); return isLinear() ? result : result.forked(); } } /** * @param key a primitive {@code long} key * @param value a value * @return an updated {@code IntMap} with {@code value} under {@code key} */ public IntMap put(long key, V value) { return put(key, value, (BinaryOperator) Maps.MERGE_LAST_WRITE_WINS); } public IntMap put(long key, V value, Object editor) { return put(key, value, (BinaryOperator) Maps.MERGE_LAST_WRITE_WINS, editor); } /** * @param key a primitive {@code long} key * @param value a value * @param merge a function which will be invoked if there is a pre-existing value under {@code key}, with the current * value as the first argument and new value as the second, to determine the combined result * @return an updated map */ public IntMap put(long key, V value, BinaryOperator merge) { return put(key, value, merge, isLinear() ? editor : new Object()); } public IntMap put(long key, V value, BinaryOperator merge, Object editor) { if (key < 0) { Node negPrime = neg.put(editor, key, value, merge); if (neg == negPrime) { return this; } else if (isLinear()) { neg = negPrime; return this; } else { return new IntMap(negPrime, pos, false); } } else { Node posPrime = pos.put(editor, key, value, merge); if (pos == posPrime) { return this; } else if (isLinear()) { pos = posPrime; return this; } else { return new IntMap<>(neg, posPrime, false); } } } @Override public IntMap put(Long key, V value) { return put(key, value, (BinaryOperator) Maps.MERGE_LAST_WRITE_WINS); } @Override public IntMap put(Long key, V value, BinaryOperator merge) { return put((long) key, value, merge); } /** * @return an updated map that does not contain {@code key} */ public IntMap remove(long key) { return remove(key, isLinear() ? editor : new Object()); } public IntMap remove(long key, Object editor) { if (key < 0) { Node negPrime = neg.remove(editor, key); if (neg == negPrime) { return this; } else if (isLinear()) { neg = negPrime; return this; } else { return new IntMap(negPrime, pos, false); } } else { Node posPrime = pos.remove(editor, key); if (pos == posPrime) { return this; } else if (isLinear()) { pos = posPrime; return this; } else { return new IntMap<>(neg, posPrime, false); } } } @Override public IntMap remove(Long key) { return remove((long) key); } @Override public IntMap mapValues(BiFunction f) { Object editor = new Object(); return new IntMap<>(neg.mapVals(editor, f), pos.mapVals(editor, f), isLinear()); } public Optional get(long key) { Object o = (key < 0 ? neg : pos).get(key, DEFAULT_VALUE); return o == DEFAULT_VALUE ? Optional.empty() : Optional.of((V) o); } public V get(long key, V defaultValue) { return (V) (key < 0 ? neg : pos).get(key, defaultValue); } @Override public V get(Long key, V defaultValue) { return get((long) key, defaultValue); } @Override public IMap update(Long key, UnaryOperator update) { return update((long) key, update); } public IntMap update(long key, UnaryOperator update) { return put(key, update.apply(get(key, null)), isLinear() ? editor : new Object()); } public IntMap update(long key, UnaryOperator update, Object editor) { return put(key, update.apply(get(key, null)), editor); } public boolean contains(long key) { return (key < 0 ? neg : pos).get(key, DEFAULT_VALUE) != DEFAULT_VALUE; } @Override public boolean contains(Long key) { return contains((long) key); } @Override public long indexOf(Long key) { return indexOf((long) key); } public long indexOf(long key) { return key < 0 ? neg.indexOf(key) : neg.size() + pos.indexOf(key); } @Override public IEntry nth(long index) { return index < neg.size() ? neg.nth(index) : pos.nth(index - neg.size()); } @Override public Iterator> iterator() { return Iterators.concat(neg.iterator(), pos.iterator()); } /** * @return the entry whose key is either equal to {@code key}, or just below it. If {@code key} is less than the * minimum value in the map, returns {@code null}. */ public IEntry floor(long key) { if (key < 0) { return neg.floor(key); } else { IEntry entry = pos.floor(key); if (entry != null) { return entry; } else { return neg.size() > 0 ? neg.nth(neg.size() - 1) : null; } } } @Override public IEntry floor(Long key) { return floor((long) key); } /** * @return the entry whose key is either equal to {@code key}, or just above it. If {@code key} is greater than the * maximum value in the map, returns {@code null}. */ public IEntry ceil(long key) { if (key >= 0) { return pos.ceil(key); } else { IEntry entry = neg.ceil(key); if (entry != null) { return entry; } else { return pos.size() > 0 ? pos.nth(0) : null; } } } @Override public IEntry ceil(Long key) { return ceil((long) key); } @Override public long size() { return neg.size() + pos.size(); } @Override public boolean isLinear() { return editor != null; } @Override public IntMap forked() { return isLinear() ? new IntMap(neg, pos, false) : this; } @Override public IntMap linear() { return isLinear() ? this : new IntMap(neg, pos, true); } @Override public List> split(int parts) { List> result = new List>().linear(); parts = Math.max(1, Math.min((int) size(), parts)); if (parts == 1 || size() == 0) { return result.addLast(this).forked(); } int estParts = (int) Math.min(parts - 1, Math.max(1, (neg.size() / (double) size()) * parts)); int negParts = pos.size() == 0 ? parts : (neg.size == 0 ? 0 : estParts); int posParts = parts - negParts; if (negParts > 0) { IntMapNodes.split(new Object(), neg, neg.size() / negParts) .stream() .map(n -> new IntMap(n, Node.POS_EMPTY, isLinear())) .forEach(m -> result.addLast((IntMap) m)); } if (posParts > 0) { IntMapNodes.split(new Object(), pos, pos.size() / posParts) .stream() .map(n -> new IntMap(Node.NEG_EMPTY, n, false)) .forEach(m -> result.addLast((IntMap) m)); } return result.forked(); } @Override public int hashCode() { return (int) Maps.hash(this); } @Override public boolean equals(IMap o, BiPredicate valEquals) { if (o instanceof IntMap) { IntMap m = (IntMap) o; return neg.equals(m.neg, valEquals) && pos.equals(m.pos, valEquals); } else { return Maps.equals(this, o, valEquals); } } @Override public boolean equals(Object obj) { if (obj instanceof IMap) { return equals((IMap) obj, Objects::equals); } else { return false; } } @Override public String toString() { return Maps.toString(this); } @Override public IntMap clone() { return new IntMap<>(neg, pos, isLinear()); } }