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

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