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