io.lacuna.bifurcan.nodes.MapNodes Maven / Gradle / Ivy
package io.lacuna.bifurcan.nodes;
import io.lacuna.bifurcan.*;
import io.lacuna.bifurcan.utils.ArrayVector;
import io.lacuna.bifurcan.utils.Bits;
import io.lacuna.bifurcan.utils.Iterators;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import static io.lacuna.bifurcan.nodes.MapNodes.Node.SHIFT_INCREMENT;
import static io.lacuna.bifurcan.nodes.Util.*;
import static java.lang.Integer.bitCount;
import static java.lang.System.arraycopy;
/**
* This is an implementation based on the one described in https://michael.steindorfer.name/publications/oopsla15.pdf.
*
* It adds in support for transient/linear updates, and allows for empty buffer space between the nodes and nodes
* to minimize allocations when a node is repeatedly updated in-place.
*
* @author ztellman
*/
public class MapNodes {
interface INode {
INode put(int shift, Object editor, int hash, K key, V value, BiPredicate equals, BinaryOperator merge);
INode remove(int shift, Object editor, int hash, K key, BiPredicate equals);
INode mapVals(Object editor, BiFunction f);
int hash(int idx);
long size();
IEntry nth(long idx);
long indexOf(int shift, int hash, K key, BiPredicate equals);
Iterable> entries();
boolean equals(INode n, BiPredicate keyEquals, BiPredicate valEquals);
}
public static class Node implements INode {
public static final Node EMPTY = new Node(new Object());
public static final int SHIFT_INCREMENT = 5;
public int datamap = 0;
public int nodemap = 0;
public int[] hashes;
public Object[] content;
Object editor;
long size;
// constructor
public Node() {
}
private Node(Object editor) {
this.editor = editor;
this.hashes = new int[2];
this.content = new Object[4];
}
// lookup
@Override
public long indexOf(int shift, int hash, K key, BiPredicate equals) {
int mask = hashMask(hash, shift);
if (isEntry(mask)) {
int idx = entryIndex(mask);
return /*hash == hashes[idx] &&*/ equals.test(key, (K) content[idx << 1]) ? idx : -1;
} else if (isNode(mask)) {
long idx = node(mask).indexOf(shift + SHIFT_INCREMENT, hash, key, equals);
if (idx == -1) {
return -1;
} else {
int nodeIdx = nodeIndex(mask);
idx += bitCount(datamap);
for (int i = 0; i < nodeIdx; i++) {
idx += ((INode) content[content.length - (i + 1)]).size();
}
return idx;
}
} else {
return -1;
}
}
@Override
public IEntry nth(long idx) {
// see if the entry is local to this node
int numEntries = bitCount(datamap);
if (idx < numEntries) {
int contentIdx = (int) (idx << 1);
return new Maps.Entry<>((K) content[contentIdx], (V) content[contentIdx + 1]);
}
// see if the entry is local to our children
if (idx < size) {
idx -= numEntries;
for (INode node : nodes()) {
if (idx < node.size()) {
return node.nth(idx);
}
idx -= node.size();
}
}
throw new IndexOutOfBoundsException();
}
@Override
public int hash(int idx) {
return hashes[idx];
}
// updates
private boolean mergeEntry(int shift, int mask, int hash, K key, V value, BiPredicate equals, BinaryOperator merge) {
int idx = entryIndex(mask);
// there's a match
boolean collision = hash == hashes[idx];
if (collision && equals.test(key, (K) content[idx << 1])) {
idx = (idx << 1) + 1;
content[idx] = merge.apply((V) content[idx], value);
return false;
// collision, put them both in a node together
} else {
K currKey = (K) content[idx << 1];
V currValue = (V) content[(idx << 1) + 1];
INode node;
if (collision) {
node = new Collision<>(hash, currKey, currValue, key, value);
} else {
node = new Node(editor)
.put(shift + SHIFT_INCREMENT, editor, hashes[idx], currKey, currValue, equals, merge)
.put(shift + SHIFT_INCREMENT, editor, hash, key, value, equals, merge);
}
removeEntry(mask).putNode(mask, node);
return true;
}
}
@Override
public Node put(int shift, Object editor, int hash, K key, V value, BiPredicate equals, BinaryOperator merge) {
if (editor != this.editor) {
return clone(editor).put(shift, editor, hash, key, value, equals, merge);
}
Node n = this;
int currShift = shift;
boolean increment;
for (; ; ) {
int mask = hashMask(hash, currShift);
// overwrite potential collision
if (n.isEntry(mask)) {
increment = n.mergeEntry(currShift, mask, hash, key, value, equals, merge);
break;
// we have to go deeper
} else if (n.isNode(mask)) {
INode child = n.node(mask);
// since we're not changing anything at this level, just head down
if (child instanceof Node && ((Node) child).editor == editor) {
n = (Node) child;
currShift += SHIFT_INCREMENT;
// we need to maintain the stack, sadly
} else {
long prevSize = child.size();
INode nodePrime = child.put(currShift + SHIFT_INCREMENT, editor, hash, key, value, equals, merge);
increment = nodePrime.size() != prevSize;
n.setNode(mask, nodePrime, increment ? 1 : 0);
break;
}
// no existing entry
} else {
n.putEntry(mask, hash, key, value);
increment = true;
break;
}
}
// we've descended, and need to update the sizes of our parents
if (n != this && increment) {
Node currNode = this;
currShift = shift;
while (currNode != n) {
currNode.size++;
currNode = (Node) currNode.node(hashMask(hash, currShift));
currShift += SHIFT_INCREMENT;
}
}
return this;
}
@Override
public INode remove(int shift, Object editor, int hash, K key, BiPredicate equals) {
int mask = hashMask(hash, shift);
// there's a potential match
if (isEntry(mask)) {
int idx = entryIndex(mask);
// there is a match
if (hashes[idx] == hash && equals.test(key, (K) content[idx << 1])) {
return (this.editor == editor ? this : clone(editor)).removeEntry(mask).collapse(shift);
// nope
} else {
return this;
}
// we must go deeper
} else if (isNode(mask)) {
INode node = node(mask);
long prevSize = node.size();
INode nodePrime = node.remove(shift + SHIFT_INCREMENT, editor, hash, key, equals);
Node n = this.editor == editor ? this : clone(editor);
switch ((int) nodePrime.size()) {
case 0:
return n.removeNode(mask, prevSize).collapse(shift);
case 1:
IEntry e = nodePrime.nth(0);
return n.removeNode(mask, prevSize).putEntry(mask, nodePrime.hash(0), e.key(), e.value());
default:
return n.setNode(mask, nodePrime, nodePrime.size() - prevSize).collapse(shift);
}
// no such thing
} else {
return this;
}
}
public Node mapVals(Object editor, BiFunction f) {
Node n = clone(editor);
for (int i = bitCount(n.datamap) - 1; i >= 0; i--) {
int idx = i << 1;
n.content[idx + 1] = f.apply((K) n.content[idx], (V) n.content[idx + 1]);
}
for (int i = content.length - bitCount(n.nodemap); i < content.length; i++) {
n.content[i] = ((INode) n.content[i]).mapVals(editor, f);
}
return n;
}
// iteration
public Iterator> iterator() {
return new Iterator>() {
final Node[] stack = new Node[7];
final byte[] cursors = new byte[14];
int depth = 0;
{
stack[0] = Node.this;
cursors[1] = (byte) bitCount(Node.this.nodemap);
}
Object[] content = Node.this.content;
int idx = 0;
int limit = bitCount(Node.this.datamap) << 1;
private boolean nextNode() {
while (depth >= 0) {
int pos = depth << 1;
int idx = cursors[pos];
int limit = cursors[pos + 1];
if (idx < limit) {
Node curr = stack[depth];
INode next = (INode) curr.content[curr.content.length - 1 - idx];
cursors[pos]++;
if (next instanceof Node) {
Node n = (Node) next;
if (n.nodemap != 0) {
stack[++depth] = n;
cursors[pos + 2] = 0;
cursors[pos + 3] = (byte) bitCount(n.nodemap);
}
if (n.datamap != 0) {
this.content = n.content;
this.idx = 0;
this.limit = bitCount(n.datamap) << 1;
return true;
}
} else {
Collision c = (Collision) next;
this.content = c.entries;
this.idx = 0;
this.limit = c.entries.length;
return true;
}
} else {
depth--;
}
}
return false;
}
@Override
public boolean hasNext() {
return idx < limit || nextNode();
}
@Override
public IEntry next() {
if (idx >= limit) {
if (!nextNode()) {
throw new NoSuchElementException();
}
}
IEntry e = new Maps.Entry<>((K) content[idx], (V) content[idx + 1]);
idx += 2;
return e;
}
};
}
@Override
public Iterable> entries() {
return () ->
Iterators.range(bitCount(datamap),
i -> {
int idx = (int) (i << 1);
return new Maps.Entry<>((K) content[idx], (V) content[idx + 1]);
});
}
// misc
@Override
public long size() {
return size;
}
public boolean equals(INode o, BiPredicate keyEquals, BiPredicate valEquals) {
if (this == o) {
return true;
}
if (o instanceof Node) {
Node n = (Node) o;
if (n.size == size && n.datamap == datamap && n.nodemap == nodemap) {
Iterator> ea = entries().iterator();
Iterator> eb = n.entries().iterator();
while (ea.hasNext()) {
if (!ea.next().equals(eb.next(), keyEquals, valEquals)) {
return false;
}
}
Iterator> na = nodes().iterator();
Iterator> nb = n.nodes().iterator();
while (na.hasNext()) {
if (!na.next().equals(nb.next(), keyEquals, valEquals)) {
return false;
}
}
return true;
}
}
return false;
}
/////
private Node clone(Object editor) {
Node node = new Node<>();
node.datamap = datamap;
node.nodemap = nodemap;
node.hashes = hashes.clone();
node.content = content.clone();
node.editor = editor;
node.size = size;
return node;
}
private Iterable> nodes() {
return () -> Iterators.range(0, bitCount(nodemap), i -> (INode) content[content.length - 1 - (int) i]);
}
private INode collapse(int shift) {
return (shift > 0
&& datamap == 0
&& Bits.isPowerOfTwo(nodemap)
&& node(nodemap) instanceof Collision)
? node(nodemap)
: this;
}
private void grow() {
if (content.length == 64) {
return;
}
Object[] c = new Object[content.length << 1];
int[] h = new int[hashes.length << 1];
int numNodes = bitCount(nodemap);
int numEntries = bitCount(datamap);
arraycopy(content, 0, c, 0, numEntries << 1);
arraycopy(content, content.length - numNodes, c, c.length - numNodes, numNodes);
arraycopy(hashes, 0, h, 0, numEntries);
this.hashes = h;
this.content = c;
}
Node putEntry(int mask, int hash, K key, V value) {
int numEntries = bitCount(datamap);
int count = (numEntries << 1) + bitCount(nodemap);
if ((count + 2) > content.length) {
grow();
}
final int idx = entryIndex(mask);
final int entryIdx = idx << 1;
if (idx != numEntries) {
arraycopy(content, entryIdx, content, entryIdx + 2, (numEntries - idx) << 1);
arraycopy(hashes, idx, hashes, idx + 1, numEntries - idx);
}
datamap |= mask;
size++;
hashes[idx] = hash;
content[entryIdx] = key;
content[entryIdx + 1] = value;
return this;
}
Node removeEntry(final int mask) {
// shrink?
final int idx = entryIndex(mask);
final int numEntries = bitCount(datamap);
if (idx != numEntries - 1) {
arraycopy(content, (idx + 1) << 1, content, idx << 1, (numEntries - 1 - idx) << 1);
arraycopy(hashes, idx + 1, hashes, idx, numEntries - 1 - idx);
}
datamap &= ~mask;
size--;
int entryIdx = (numEntries - 1) << 1;
content[entryIdx] = null;
content[entryIdx + 1] = null;
return this;
}
Node setNode(int mask, INode node, long sizeDelta) {
content[content.length - 1 - nodeIndex(mask)] = node;
size += sizeDelta;
return this;
}
Node putNode(final int mask, INode node) {
if (node.size() == 1) {
IEntry e = node.nth(0);
return putEntry(mask, node.hash(0), e.key(), e.value());
} else {
int count = (bitCount(datamap) << 1) + bitCount(nodemap);
if ((count + 1) > content.length) {
grow();
}
int idx = nodeIndex(mask);
int numNodes = bitCount(nodemap);
if (numNodes > 0) {
arraycopy(content, content.length - numNodes, content, content.length - 1 - numNodes, numNodes - idx);
}
nodemap |= mask;
size += node.size();
content[content.length - 1 - idx] = node;
return this;
}
}
Node removeNode(final int mask, long nodeSize) {
// shrink?
int idx = nodeIndex(mask);
int numNodes = bitCount(nodemap);
size -= nodeSize;
arraycopy(content, content.length - numNodes, content, content.length + 1 - numNodes, numNodes - 1 - idx);
nodemap &= ~mask;
content[content.length - numNodes] = null;
return this;
}
private int entryIndex(int mask) {
return compressedIndex(datamap, mask);
}
private int nodeIndex(int mask) {
return compressedIndex(nodemap, mask);
}
private INode node(int mask) {
return (INode) content[content.length - 1 - nodeIndex(mask)];
}
private boolean isEntry(int mask) {
return (datamap & mask) != 0;
}
private boolean isNode(int mask) {
return (nodemap & mask) != 0;
}
}
public static class Collision implements INode {
public final int hash;
public final Object[] entries;
// constructors
public Collision(int hash, K k1, V v1, K k2, V v2) {
this(hash, new Object[]{k1, v1, k2, v2});
}
private Collision(int hash, Object[] entries) {
this.hash = hash;
this.entries = entries;
}
// lookup
public boolean contains(int hash, K key, BiPredicate equals) {
return get(hash, key, equals, DEFAULT_VALUE) != DEFAULT_VALUE;
}
public Object get(int hash, K key, BiPredicate equals, Object defaultValue) {
if (hash != this.hash) {
return defaultValue;
}
int idx = indexOf(key, equals);
if (idx < 0) {
return defaultValue;
} else {
return entries[idx + 1];
}
}
@Override
public int hash(int idx) {
return hash;
}
@Override
public IEntry nth(long idx) {
int i = (int) idx << 1;
return new Maps.Entry<>((K) entries[i], (V) entries[i + 1]);
}
@Override
public long indexOf(int shift, int hash, K key, BiPredicate equals) {
if (this.hash == hash) {
for (int i = 0; i < entries.length; i += 2) {
if (equals.test(key, (K) entries[i])) {
return i >> 1;
}
}
}
return -1;
}
// update
@Override
public INode put(int shift, Object editor, int hash, K key, V value, BiPredicate equals, BinaryOperator merge) {
if (hash != this.hash) {
return new Node(editor)
.putNode(hashMask(this.hash, shift), this)
.put(shift, editor, hash, key, value, equals, merge);
} else {
int idx = indexOf(key, equals);
return idx < 0
? new Collision(hash, ArrayVector.append(entries, key, value))
: new Collision(hash, ArrayVector.set(entries, idx, key, merge.apply((V) entries[idx + 1], value)));
}
}
@Override
public INode remove(int shift, Object editor, int hash, K key, BiPredicate equals) {
if (hash != this.hash) {
return this;
} else {
int idx = indexOf(key, equals);
return idx < 0 ? this : new Collision(hash, ArrayVector.remove(entries, idx, 2));
}
}
public Collision mapVals(Object editor, BiFunction f) {
Collision c = new Collision(hash, entries.clone());
for (int i = 0; i < entries.length; i += 2) {
c.entries[i + 1] = f.apply((K) c.entries[i], (V) c.entries[i + 1]);
}
return c;
}
// iteration
public Iterable> entries() {
return () ->
Iterators.range(entries.length >> 1,
i -> {
int idx = (int) (i << 1);
return new Maps.Entry<>((K) entries[idx], (V) entries[idx + 1]);
});
}
// misc
public long size() {
return entries.length >> 1;
}
@Override
public boolean equals(INode o, BiPredicate keyEquals, BiPredicate valEquals) {
if (this == o) {
return true;
}
if (o instanceof Collision) {
Collision c = (Collision) o;
if (c.size() == size()) {
Iterator> it = entries().iterator();
while (it.hasNext()) {
IEntry e = it.next();
int idx = c.indexOf(e.key(), keyEquals);
if (idx < 0 || !valEquals.test(e.value(), (V) entries[idx + 1])) {
return false;
}
}
return true;
}
}
return false;
}
///
private int indexOf(K key, BiPredicate equals) {
for (int i = 0; i < entries.length; i += 2) {
if (equals.test(key, (K) entries[i])) {
return i;
}
}
return -1;
}
}
///
private static int hashMask(int hash, int shift) {
return 1 << ((hash >>> shift) & 31);
}
public static boolean contains(Node node, int shift, int hash, K key, BiPredicate equals) {
return get(node, shift, hash, key, equals, DEFAULT_VALUE) != DEFAULT_VALUE;
}
public static Object get(Node node, int shift, int hash, K key, BiPredicate equals, Object defaultValue) {
Object currNode = node;
while (!(currNode instanceof Collision)) {
Node n = (Node) currNode;
int mask = hashMask(hash, shift);
// there's a potential matching entry
if (n.isEntry(mask)) {
int idx = n.entryIndex(mask) << 1;
return /*n.hashes[idx] == hash &&*/ equals.test(key, (K) n.content[idx])
? n.content[idx + 1]
: defaultValue;
// we must go deeper
} else if (n.isNode(mask)) {
currNode = n.node(mask);
shift += SHIFT_INCREMENT;
// no such thing
} else {
return defaultValue;
}
}
Collision c = (Collision) currNode;
return c.get(hash, key, equals, defaultValue);
}
/// Set operations
public static INode mergeNodes(int shift, Object editor, INode a, INode b, BiPredicate equals, BinaryOperator merge) {
Collision ca, cb;
Node na, nb;
// Node / Node
if (a instanceof Node && b instanceof Node) {
return merge(shift, editor, (Node) a, (Node) b, equals, merge);
// Node / Collision
} else if (a instanceof Node && b instanceof Collision) {
na = (Node) a;
cb = (Collision) b;
for (IEntry e : cb.entries()) {
na = na.put(shift, editor, cb.hash, e.key(), e.value(), equals, merge);
}
return na;
// Collision / Node
} else if (a instanceof Collision && b instanceof Node) {
BinaryOperator inverted = (x, y) -> merge.apply(y, x);
ca = (Collision) a;
nb = (Node) b;
for (IEntry e : ca.entries()) {
nb = nb.put(shift, editor, ca.hash, e.key(), e.value(), equals, inverted);
}
return nb;
// Collision / Collision
} else {
cb = (Collision) b;
for (IEntry e : cb.entries()) {
a = a.put(shift, editor, cb.hash, e.key(), e.value(), equals, merge);
}
return a;
}
}
public static Node merge(int shift, Object editor, Node a, Node b, BiPredicate equals, BinaryOperator merge) {
Node result = new Node(editor);
PrimitiveIterator.OfInt masks = Util.masks(a.datamap | a.nodemap | b.datamap | b.nodemap);
while (masks.hasNext()) {
int mask = masks.nextInt();
int state = mergeState(mask, a.nodemap, a.datamap, b.nodemap, b.datamap);
int idx;
switch (state) {
case NODE_NONE:
case NONE_NODE:
result = transferNode(mask, state == NODE_NONE ? a : b, result);
break;
case ENTRY_NONE:
case NONE_ENTRY:
result = transferEntry(mask, state == ENTRY_NONE ? a : b, result);
break;
case ENTRY_ENTRY:
result = transferEntry(mask, a, result);
idx = b.entryIndex(mask);
result = (Node) result.put(shift, editor, b.hash(idx), (K) b.content[idx << 1], (V) b.content[(idx << 1) + 1], equals, merge);
break;
case NODE_NODE:
result = result.putNode(mask, mergeNodes(shift + 5, editor, a.node(mask), b.node(mask), equals, merge));
break;
case NODE_ENTRY:
idx = b.entryIndex(mask);
result = (Node) result
.putNode(mask, a.node(mask))
.put(shift, editor, b.hash(idx), (K) b.content[idx << 1], (V) b.content[(idx << 1) + 1], equals, merge);
break;
case ENTRY_NODE:
idx = a.entryIndex(mask);
result = (Node) result
.putNode(mask, b.node(mask))
.put(shift, editor, a.hash(idx), (K) a.content[idx << 1], (V) a.content[(idx << 1) + 1], equals, (x, y) -> merge.apply(y, x));
break;
case NONE_NONE:
break;
}
}
return result;
}
public static INode diffNodes(int shift, Object editor, INode a, INode b, BiPredicate equals) {
Collision ca, cb;
Node na, nb;
// Node / Node
if (a instanceof Node && b instanceof Node) {
return difference(shift, editor, (Node) a, (Node) b, equals);
// Node / Collision
} else if (a instanceof Node && b instanceof Collision) {
cb = (Collision) b;
for (IEntry e : cb.entries()) {
a = a.remove(shift, editor, cb.hash, e.key(), equals);
}
return a.size() > 0 ? a : null;
// Collision / Node
} else if (a instanceof Collision && b instanceof Node) {
ca = (Collision) a;
nb = (Node) b;
for (IEntry e : ca.entries()) {
if (get(nb, shift, ca.hash, e.key(), equals, DEFAULT_VALUE) != DEFAULT_VALUE) {
ca = (Collision) ca.remove(shift, editor, ca.hash, e.key(), equals);
}
}
return ca.size() > 0 ? ca : null;
// Collision / Collision
} else {
ca = (Collision) a;
cb = (Collision) b;
if (ca.hash == cb.hash) {
for (IEntry e : cb.entries()) {
ca = (Collision) ca.remove(shift, editor, ca.hash, e.key(), equals);
}
}
return ca.size() > 0 ? ca : null;
}
}
public static Node difference(int shift, Object editor, Node a, Node b, BiPredicate equals) {
Node result = new Node(editor);
INode n;
int idx;
PrimitiveIterator.OfInt masks = Util.masks(a.nodemap | a.datamap | b.nodemap | b.datamap);
while (masks.hasNext()) {
int mask = masks.nextInt();
int state = mergeState(mask, a.nodemap, a.datamap, b.nodemap, b.datamap);
switch (state) {
case NODE_NONE:
result = transferNode(mask, a, result);
break;
case ENTRY_NONE:
result = transferEntry(mask, a, result);
break;
case ENTRY_ENTRY:
int ia = a.entryIndex(mask);
int ib = b.entryIndex(mask);
if (b.hashes[ib] != a.hashes[ia] || !equals.test((K) b.content[ib << 1], (K) a.content[ia << 1])) {
result = transferEntry(mask, a, result);
}
break;
case NODE_NODE:
n = diffNodes(shift + 5, editor, a.node(mask), b.node(mask), equals);
if (n != null) {
result = result.putNode(mask, n);
}
break;
case NODE_ENTRY:
idx = b.entryIndex(mask);
n = a.node(mask).remove(shift + 5, editor, b.hashes[idx], (K) b.content[idx << 1], equals);
if (n.size() > 0) {
result = result.putNode(mask, n);
}
break;
case ENTRY_NODE:
idx = a.entryIndex(mask);
if (get(b, shift, a.hashes[idx], (K) a.content[idx << 1], equals, DEFAULT_VALUE) == DEFAULT_VALUE) {
result = transferEntry(mask, a, result);
}
break;
case NONE_ENTRY:
case NONE_NODE:
case NONE_NONE:
break;
}
}
return result.size() > 0 ? result : null;
}
public static INode intersectNodes(int shift, Object editor, INode a, INode b, BiPredicate equals) {
Collision ca, cb;
Node na, nb;
// Node / Node
if (a instanceof Node && b instanceof Node) {
return intersection(shift, editor, (Node) a, (Node) b, equals);
// Node / Collision
} else if (a instanceof Node && b instanceof Collision) {
cb = (Collision) b;
na = (Node) a;
Collision result = new Collision(cb.hash, new Object[0]);
for (IEntry e : b.entries()) {
Object val = get(na, shift, cb.hash, e.key(), equals, DEFAULT_VALUE);
if (val != DEFAULT_VALUE) {
result = (Collision) result.put(shift, editor, cb.hash, e.key(), (V) val, equals, null);
}
}
return result.size() > 0 ? result : null;
// Collision / Node
} else if (a instanceof Collision && b instanceof Node) {
ca = (Collision) a;
nb = (Node) b;
for (IEntry e : ca.entries()) {
if (!contains(nb, shift, ca.hash, e.key(), equals)) {
ca = (Collision) ca.remove(shift, editor, ca.hash, e.key(), equals);
}
}
return ca.size() > 0 ? ca : null;
// Collision / Collision
} else {
ca = (Collision) a;
cb = (Collision) b;
if (ca.hash != cb.hash) {
return null;
}
for (IEntry e : ca.entries()) {
if (!cb.contains(ca.hash, e.key(), equals)) {
ca = (Collision) ca.remove(shift, editor, ca.hash, e.key(), equals);
}
}
return ca.size() > 0 ? ca : null;
}
}
public static Node intersection(int shift, Object editor, Node a, Node b, BiPredicate equals) {
Node result = new Node(editor);
PrimitiveIterator.OfInt masks = Util.masks(a.nodemap | a.datamap | b.nodemap | b.datamap);
while (masks.hasNext()) {
int mask = masks.nextInt();
int state = mergeState(mask, a.nodemap, a.datamap, b.nodemap, b.datamap);
int idx, ia, ib;
switch (state) {
case ENTRY_ENTRY:
ia = a.entryIndex(mask);
ib = b.entryIndex(mask);
if (b.hashes[ib] == a.hashes[ia] && equals.test((K) b.content[ib << 1], (K) a.content[ia << 1])) {
result = transferEntry(mask, a, result);
}
break;
case NODE_NODE:
INode n = intersectNodes(shift + 5, editor, a.node(mask), b.node(mask), equals);
if (n != null) {
result = result.putNode(mask, n);
}
break;
case NODE_ENTRY:
idx = b.entryIndex(mask);
int hash = b.hashes[idx];
K key = (K) b.content[idx << 1];
Object val = get(a, shift, hash, key, equals, DEFAULT_VALUE);
if (val != DEFAULT_VALUE) {
result = result.put(shift, editor, hash, key, (V) val, equals, null);
}
break;
case ENTRY_NODE:
idx = a.entryIndex(mask);
if (get(b, shift, a.hashes[idx], (K) a.content[idx << 1], equals, DEFAULT_VALUE) != DEFAULT_VALUE) {
result = transferEntry(mask, a, result);
}
break;
case ENTRY_NONE:
case NODE_NONE:
case NONE_ENTRY:
case NONE_NODE:
case NONE_NONE:
break;
}
}
return result.size() > 0 ? result : null;
}
public static IList> split(Object editor, Node node, int targetSize) {
IList> result = new LinearList<>();
if ((node.size() >> 1) < targetSize) {
result.addLast(node);
} else {
Node acc = new Node<>(editor);
PrimitiveIterator.OfInt masks = Util.masks(node.datamap | node.nodemap);
while (masks.hasNext()) {
int mask = masks.nextInt();
if (acc.size() >= targetSize) {
result.addLast(acc);
acc = new Node<>(editor);
}
if (node.isEntry(mask)) {
acc = transferEntry(mask, node, acc);
} else if (node.isNode(mask)) {
INode child = node.node(mask);
if (child instanceof Node && child.size() >= (targetSize << 1)) {
split(editor, (Node) child, targetSize).stream()
.map(n -> new Node(editor).putNode(mask, n))
.forEach(result::addLast);
} else {
acc = acc.putNode(mask, child);
}
}
}
if (acc.size() > 0) {
result.addLast(acc);
}
}
return result;
}
private static Node transferNode(int mask, Node src, Node dst) {
return dst.putNode(mask, src.node(mask));
}
private static Node transferEntry(int mask, Node src, Node dst) {
int idx = src.entryIndex(mask);
return dst.putEntry(mask, src.hashes[idx], (K) src.content[idx << 1], (V) src.content[(idx << 1) + 1]);
}
}