PersistentMap/PersistentMap/BTreeFunctions.cs
Linus Björnstam 7af48306fe Optimize the binary search by using unsafe
The jit was not able to tell that bounds checks could be elided.

Added an extra whistle for value types to allow for inlined comparisons.
2026-04-23 16:45:18 +02:00

850 lines
31 KiB
C#

using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace PersistentMap
{
public static class BTreeFunctions
{
// ---------------------------------------------------------
// Public API
// ---------------------------------------------------------
public static bool TryGetValue<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out V value)
where TStrategy : IKeyStrategy<K>
{
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
Node<K> current = root;
while (true)
{
if (current.IsLeaf)
{
var leaf = current.AsLeaf<V>();
int index = FindIndex(leaf, key, keyPrefix, strategy);
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
{
value = leaf.Values[index];
return true;
}
value = default!;
return false;
}
else
{
var internalNode = current.AsInternal();
int index = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
current = internalNode.Children[index]!;
}
}
}
public static Node<K> Set<K, V>(Node<K> root, K key, V value, IKeyStrategy<K> strategy, OwnerId owner, out bool countChanged)
{
root = root.EnsureEditable(owner);
var splitResult = InsertRecursive(root, key, value, strategy, owner, out countChanged);
if (splitResult != null)
{
var newRoot = strategy.UsesPrefixes
? new PrefixInternalNode<K>(owner)
: new InternalNode<K>(owner);
newRoot.Keys[0] = splitResult.Separator;
newRoot.Children[0] = root;
newRoot.Children[1] = splitResult.NewNode;
newRoot.SetCount(1);
if (strategy.UsesPrefixes)
{
newRoot.AllPrefixes[0] = strategy.GetPrefix(splitResult.Separator);
}
return newRoot;
}
return root;
}
private static SplitResult<K>? InsertRecursive<K, V>(Node<K> node, K key, V value, IKeyStrategy<K> strategy, OwnerId owner, out bool added)
{
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
if (node.IsLeaf)
{
var leaf = node.AsLeaf<V>();
int index = FindIndex(leaf, key, keyPrefix, strategy);
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
{
leaf.Values[index] = value;
added = false;
return null;
}
added = true;
if (leaf.Header.Count < LeafNode<K, V>.Capacity)
{
InsertIntoLeaf(leaf, index, key, value, strategy);
return null;
}
else
{
return SplitLeaf(leaf, index, key, value, strategy, owner);
}
}
else
{
var internalNode = node.AsInternal();
int index = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
var child = internalNode.Children[index]!.EnsureEditable(owner);
internalNode.Children[index] = child;
var split = InsertRecursive(child, key, value, strategy, owner, out added);
if (split != null)
{
if (internalNode.Header.Count < InternalNode<K>.Capacity - 1)
{
InsertIntoInternal(internalNode, index, split.Separator, split.NewNode, strategy);
return null;
}
else
{
return SplitInternal(internalNode, index, split.Separator, split.NewNode, strategy, owner);
}
}
return null;
}
}
public static Node<K> Remove<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, OwnerId owner, out bool countChanged)
where TStrategy : IKeyStrategy<K>
{
root = root.EnsureEditable(owner);
bool rebalanceNeeded = RemoveRecursive<K, V, TStrategy>(root, key, strategy, owner, out countChanged);
if (rebalanceNeeded)
{
if (!root.IsLeaf)
{
var internalRoot = root.AsInternal();
if (internalRoot.Header.Count == 0)
{
return internalRoot.Children[0]!;
}
}
}
return root;
}
private static bool RemoveRecursive<K, V, TStrategy>(Node<K> node, K key, TStrategy strategy, OwnerId owner, out bool removed)
where TStrategy : IKeyStrategy<K>
{
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
if (node.IsLeaf)
{
var leaf = node.AsLeaf<V>();
int index = FindIndex(leaf, key, keyPrefix, strategy);
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0)
{
RemoveFromLeaf(leaf, index, strategy);
removed = true;
return leaf.Header.Count < LeafNode<K, V>.MergeThreshold;
}
removed = false;
return false;
}
else
{
var internalNode = node.AsInternal();
int index = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
var child = internalNode.Children[index]!.EnsureEditable(owner);
internalNode.Children[index] = child;
bool childUnderflow = RemoveRecursive<K, V, TStrategy>(child, key, strategy, owner, out removed);
if (removed && childUnderflow)
{
return HandleUnderflow<K, V, TStrategy>(internalNode, index, strategy, owner);
}
return false;
}
}
// ---------------------------------------------------------
// Internal Helpers: Search
// ---------------------------------------------------------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int FindIndex<K, TStrategy>(Node<K> node, K key, long keyPrefix, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
if (typeof(K) == typeof(int))
{
Span<K> keys = node.GetKeys();
ref K firstKeyRef = ref MemoryMarshal.GetReference(keys);
ref int firstIntRef = ref Unsafe.As<K, int>(ref firstKeyRef);
ReadOnlySpan<int> intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length);
int intKey = Unsafe.As<K, int>(ref key);
return IntScanner.FindFirstGreaterOrEqual(intKeys, intKey);
}
if (strategy.UsesPrefixes)
{
int index = PrefixScanner.FindFirstGreaterOrEqual(node.Prefixes, keyPrefix);
return RefineSearch(index, node.GetKeys(), key, strategy);
}
return FallbackSearchKeys(node.GetKeys(), key, strategy);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int FindRoutingIndex<K, TStrategy>(InternalNode<K> node, K key, long keyPrefix, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
if (!strategy.UsesPrefixes)
{
return FallbackRoutingKeys(node.GetKeys(), key, strategy);
}
int index = PrefixScanner.FindFirstGreaterOrEqual(node.Prefixes, keyPrefix);
return RefineRouting(index, node.GetKeys(), key, strategy);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int RefineSearch<K, TStrategy>(int startIndex, ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int i = startIndex;
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
return i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int RefineRouting<K, TStrategy>(int startIndex, ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int i = startIndex;
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
return i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FallbackSearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
return strategy.UseBinarySearch
? BinarySearchKeys(keys, key, strategy)
: LinearSearchKeys(keys, key, strategy);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FallbackRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
return strategy.UseBinarySearch
? BinaryRoutingKeys(keys, key, strategy)
: LinearRoutingKeys(keys, key, strategy);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int LinearSearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int i = 0;
while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++;
return i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int LinearRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int i = 0;
while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++;
return i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int BinarySearchKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int low = 0;
int high = keys.Length - 1;
ref K keysRef = ref MemoryMarshal.GetReference(keys);
while (low <= high)
{
int mid = low + ((high - low) >> 1);
K midKey = Unsafe.Add(ref keysRef, mid);
int cmp = strategy.Compare(midKey, key);
if (cmp == 0) return mid;
if (cmp < 0) low = mid + 1;
else high = mid - 1;
}
return low;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int BinaryRoutingKeys<K, TStrategy>(ReadOnlySpan<K> keys, K key, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int low = 0;
int high = keys.Length - 1;
ref K keysRef = ref MemoryMarshal.GetReference(keys);
while (low <= high)
{
int mid = low + ((high - low) >> 1);
K midKey = Unsafe.Add(ref keysRef, mid);
int cmp = strategy.Compare(midKey, key);
if (cmp <= 0) low = mid + 1;
else high = mid - 1;
}
return low;
}
// ---------------------------------------------------------
// Insertion Logic
// ---------------------------------------------------------
private class SplitResult<K>
{
public Node<K> NewNode;
public K Separator;
public SplitResult(Node<K> newNode, K separator)
{
NewNode = newNode;
Separator = separator;
}
}
private static void InsertIntoLeaf<K, V, TStrategy>(LeafNode<K, V> leaf, int index, K key, V value, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int count = leaf.Header.Count;
if (index < count)
{
int moveCount = count - index;
leaf.Keys.AsSpan(index, moveCount).CopyTo(leaf.Keys.AsSpan(index + 1));
leaf.Values.AsSpan(index, moveCount).CopyTo(leaf.Values.AsSpan(index + 1));
if (strategy.UsesPrefixes)
{
leaf.AllPrefixes.Slice(index, moveCount).CopyTo(leaf.AllPrefixes.Slice(index + 1));
}
}
leaf.Keys[index] = key;
leaf.Values[index] = value;
if (strategy.UsesPrefixes)
{
leaf.AllPrefixes[index] = strategy.GetPrefix(key);
}
leaf.SetCount(count + 1);
}
private static SplitResult<K> SplitLeaf<K, V, TStrategy>(LeafNode<K, V> left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner)
where TStrategy : IKeyStrategy<K>
{
var right = new LeafNode<K, V>(owner, strategy.UsesPrefixes);
int totalCount = left.Header.Count;
int splitPoint = (insertIndex == totalCount) ? totalCount : (insertIndex == 0 ? 0 : totalCount / 2);
int moveCount = totalCount - splitPoint;
if (moveCount > 0)
{
left.Keys.AsSpan(splitPoint, moveCount).CopyTo(right.Keys.AsSpan(0));
left.Values.AsSpan(splitPoint, moveCount).CopyTo(right.Values.AsSpan(0));
if (strategy.UsesPrefixes)
{
left.AllPrefixes.Slice(splitPoint, moveCount).CopyTo(right.AllPrefixes);
}
}
left.SetCount(splitPoint);
right.SetCount(moveCount);
if (insertIndex < splitPoint || (splitPoint == 0 && insertIndex == 0))
{
InsertIntoLeaf(left, insertIndex, key, value, strategy);
}
else
{
InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy);
}
return new SplitResult<K>(right, right.Keys[0]);
}
private static void InsertIntoInternal<K, TStrategy>(InternalNode<K> node, int index, K separator, Node<K> newChild, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int count = node.Header.Count;
if (index < count)
{
int moveCount = count - index;
Span<K> keysSpan = node.Keys;
keysSpan.Slice(index, moveCount).CopyTo(keysSpan.Slice(index + 1));
Span<Node<K>> childrenSpan = node.Children;
childrenSpan.Slice(index + 1, moveCount).CopyTo(childrenSpan.Slice(index + 2));
if (strategy.UsesPrefixes)
{
node.AllPrefixes.Slice(index, moveCount).CopyTo(node.AllPrefixes.Slice(index + 1));
}
}
node.Keys[index] = separator;
node.Children[index + 1] = newChild;
if (strategy.UsesPrefixes)
{
node.AllPrefixes[index] = strategy.GetPrefix(separator);
}
node.SetCount(count + 1);
}
private static SplitResult<K> SplitInternal<K, TStrategy>(InternalNode<K> left, int insertIndex, K separator, Node<K> newChild, TStrategy strategy, OwnerId owner)
where TStrategy : IKeyStrategy<K>
{
var right = strategy.UsesPrefixes
? new PrefixInternalNode<K>(owner)
: new InternalNode<K>(owner);
int count = left.Header.Count;
int splitPoint = count / 2;
K upKey = left.Keys[splitPoint];
int moveCount = count - splitPoint - 1;
if (moveCount > 0)
{
Span<K> leftKeys = left.Keys;
Span<K> rightKeys = right.Keys;
leftKeys.Slice(splitPoint + 1, moveCount).CopyTo(rightKeys);
Span<Node<K>> leftChildren = left.Children;
Span<Node<K>> rightChildren = right.Children;
leftChildren.Slice(splitPoint + 1, moveCount + 1).CopyTo(rightChildren);
if (strategy.UsesPrefixes)
{
left.AllPrefixes.Slice(splitPoint + 1, moveCount).CopyTo(right.AllPrefixes);
}
}
left.SetCount(splitPoint);
right.SetCount(moveCount);
if (insertIndex <= splitPoint)
{
InsertIntoInternal(left, insertIndex, separator, newChild, strategy);
}
else
{
InsertIntoInternal(right, insertIndex - (splitPoint + 1), separator, newChild, strategy);
}
return new SplitResult<K>(right, upKey);
}
// ---------------------------------------------------------
// Removal Logic
// ---------------------------------------------------------
private static void RemoveFromLeaf<K, V, TStrategy>(LeafNode<K, V> leaf, int index, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
int count = leaf.Header.Count;
int moveCount = count - index - 1;
if (moveCount > 0)
{
leaf.Keys.AsSpan(index + 1, moveCount).CopyTo(leaf.Keys.AsSpan(index));
leaf.Values.AsSpan(index + 1, moveCount).CopyTo(leaf.Values.AsSpan(index));
if (strategy.UsesPrefixes)
{
leaf.AllPrefixes.Slice(index + 1, moveCount).CopyTo(leaf.AllPrefixes.Slice(index));
}
}
leaf.SetCount(count - 1);
}
private static bool HandleUnderflow<K, V, TStrategy>(InternalNode<K> parent, int childIndex, TStrategy strategy, OwnerId owner)
where TStrategy : IKeyStrategy<K>
{
if (childIndex < parent.Header.Count)
{
var rightSibling = parent.Children[childIndex + 1]!.EnsureEditable(owner);
parent.Children[childIndex + 1] = rightSibling;
var leftChild = parent.Children[childIndex]!;
if (CanBorrow(rightSibling))
{
RotateLeft<K, V, TStrategy>(parent, childIndex, leftChild, rightSibling, strategy);
return false;
}
else
{
Merge<K, V, TStrategy>(parent, childIndex, leftChild, rightSibling, strategy);
return parent.Header.Count < LeafNode<K, V>.MergeThreshold;
}
}
else if (childIndex > 0)
{
var leftSibling = parent.Children[childIndex - 1]!.EnsureEditable(owner);
parent.Children[childIndex - 1] = leftSibling;
var rightChild = parent.Children[childIndex]!;
if (CanBorrow(leftSibling))
{
RotateRight<K, V, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
return false;
}
else
{
Merge<K, V, TStrategy>(parent, childIndex - 1, leftSibling, rightChild, strategy);
return parent.Header.Count < LeafNode<K, V>.MergeThreshold;
}
}
return true;
}
private static bool CanBorrow<K>(Node<K> node)
{
return node.Header.Count > 8 + 1;
}
private static void Merge<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
if (left.IsLeaf)
{
var leftLeaf = left.AsLeaf<V>();
var rightLeaf = right.AsLeaf<V>();
int lCount = leftLeaf.Header.Count;
int rCount = rightLeaf.Header.Count;
rightLeaf.Keys.AsSpan(0, rCount).CopyTo(leftLeaf.Keys.AsSpan(lCount));
rightLeaf.Values.AsSpan(0, rCount).CopyTo(leftLeaf.Values.AsSpan(lCount));
if (strategy.UsesPrefixes)
{
rightLeaf.AllPrefixes.Slice(0, rCount).CopyTo(leftLeaf.AllPrefixes.Slice(lCount));
}
leftLeaf.SetCount(lCount + rCount);
}
else
{
var leftInternal = left.AsInternal();
var rightInternal = right.AsInternal();
K separator = parent.Keys[separatorIndex];
int lCount = leftInternal.Header.Count;
leftInternal.Keys[lCount] = separator;
if (strategy.UsesPrefixes)
{
leftInternal.AllPrefixes[lCount] = strategy.GetPrefix(separator);
}
int rCount = rightInternal.Header.Count;
Span<K> rightKeys = rightInternal.Keys;
Span<K> leftKeys = leftInternal.Keys;
rightKeys.Slice(0, rCount).CopyTo(leftKeys.Slice(lCount + 1));
if (strategy.UsesPrefixes)
{
rightInternal.AllPrefixes.Slice(0, rCount).CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1));
}
Span<Node<K>> rightChildren = rightInternal.Children;
Span<Node<K>> leftChildren = leftInternal.Children;
rightChildren.Slice(0, rCount + 1).CopyTo(leftChildren.Slice(lCount + 1));
leftInternal.SetCount(lCount + 1 + rCount);
}
int pCount = parent.Header.Count;
int moveCount = pCount - separatorIndex - 1;
if (moveCount > 0)
{
Span<K> parentKeys = parent.Keys;
parentKeys.Slice(separatorIndex + 1, moveCount).CopyTo(parentKeys.Slice(separatorIndex));
if (strategy.UsesPrefixes)
{
parent.AllPrefixes.Slice(separatorIndex + 1, moveCount).CopyTo(parent.AllPrefixes.Slice(separatorIndex));
}
Span<Node<K>> parentChildren = parent.Children;
parentChildren.Slice(separatorIndex + 2, moveCount).CopyTo(parentChildren.Slice(separatorIndex + 1));
}
parent.SetCount(pCount - 1);
}
private static void RotateLeft<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
if (left.IsLeaf)
{
var leftLeaf = left.AsLeaf<V>();
var rightLeaf = right.AsLeaf<V>();
InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys[0], rightLeaf.Values[0], strategy);
RemoveFromLeaf(rightLeaf, 0, strategy);
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
if (strategy.UsesPrefixes)
{
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
}
}
else
{
var leftInternal = left.AsInternal();
var rightInternal = right.AsInternal();
K sep = parent.Keys[separatorIndex];
InsertIntoInternal(leftInternal, leftInternal.Header.Count, sep, rightInternal.Children[0]!, strategy);
parent.Keys[separatorIndex] = rightInternal.Keys[0];
if (strategy.UsesPrefixes)
{
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightInternal.Keys[0]);
}
int rCount = rightInternal.Header.Count;
Span<Node<K>> rightChildren = rightInternal.Children;
rightChildren.Slice(1, rCount).CopyTo(rightChildren);
if (rCount > 1)
{
Span<K> rightKeys = rightInternal.Keys;
rightKeys.Slice(1, rCount - 1).CopyTo(rightKeys);
if (strategy.UsesPrefixes)
{
rightInternal.AllPrefixes.Slice(1, rCount - 1).CopyTo(rightInternal.AllPrefixes);
}
}
rightInternal.SetCount(rCount - 1);
}
}
private static void RotateRight<K, V, TStrategy>(InternalNode<K> parent, int separatorIndex, Node<K> left, Node<K> right, TStrategy strategy)
where TStrategy : IKeyStrategy<K>
{
if (left.IsLeaf)
{
var leftLeaf = left.AsLeaf<V>();
var rightLeaf = right.AsLeaf<V>();
int last = leftLeaf.Header.Count - 1;
InsertIntoLeaf(rightLeaf, 0, leftLeaf.Keys[last], leftLeaf.Values[last], strategy);
RemoveFromLeaf(leftLeaf, last, strategy);
parent.Keys[separatorIndex] = rightLeaf.Keys[0];
if (strategy.UsesPrefixes)
{
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]);
}
}
else
{
var leftInternal = left.AsInternal();
var rightInternal = right.AsInternal();
int last = leftInternal.Header.Count - 1;
K sep = parent.Keys[separatorIndex];
InsertIntoInternal(rightInternal, 0, sep, leftInternal.Children[last + 1]!, strategy);
parent.Keys[separatorIndex] = leftInternal.Keys[last];
if (strategy.UsesPrefixes)
{
parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]);
}
leftInternal.SetCount(last);
}
}
public static bool TryGetMin<K, V>(Node<K> root, out K key, out V value)
{
var current = root;
while (!current.IsLeaf)
{
current = current.AsInternal().Children[0]!;
}
var leaf = current.AsLeaf<V>();
if (leaf.Header.Count == 0)
{
key = default!;
value = default!;
return false;
}
key = leaf.Keys[0];
value = leaf.Values[0];
return true;
}
public static bool TryGetMax<K, V>(Node<K> root, out K key, out V value)
{
var current = root;
while (!current.IsLeaf)
{
var internalNode = current.AsInternal();
current = internalNode.Children[internalNode.Header.Count]!;
}
var leaf = current.AsLeaf<V>();
if (leaf.Header.Count == 0)
{
key = default!;
value = default!;
return false;
}
int last = leaf.Header.Count - 1;
key = leaf.Keys[last];
value = leaf.Values[last];
return true;
}
public static bool TryGetSuccessor<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out K nextKey, out V nextValue)
where TStrategy : IKeyStrategy<K>
{
InternalNode<K>[] path = new InternalNode<K>[32];
int[] indices = new int[32];
int depth = 0;
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
var current = root;
while (!current.IsLeaf)
{
var internalNode = current.AsInternal();
int idx = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
path[depth] = internalNode;
indices[depth] = idx;
depth++;
current = internalNode.Children[idx]!;
}
var leaf = current.AsLeaf<V>();
int index = FindIndex(leaf, key, keyPrefix, strategy);
if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) index++;
if (index < leaf.Header.Count)
{
nextKey = leaf.Keys[index];
nextValue = leaf.Values[index];
return true;
}
for (int i = depth - 1; i >= 0; i--)
{
if (indices[i] < path[i].Header.Count)
{
current = path[i].Children[indices[i] + 1]!;
while (!current.IsLeaf)
{
current = current.AsInternal().Children[0]!;
}
var targetLeaf = current.AsLeaf<V>();
nextKey = targetLeaf.Keys[0];
nextValue = targetLeaf.Values[0];
return true;
}
}
nextKey = default!;
nextValue = default!;
return false;
}
public static bool TryGetPredecessor<K, V, TStrategy>(Node<K> root, K key, TStrategy strategy, out K prevKey, out V prevValue)
where TStrategy : IKeyStrategy<K>
{
InternalNode<K>[] path = new InternalNode<K>[32];
int[] indices = new int[32];
int depth = 0;
long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0;
var current = root;
while (!current.IsLeaf)
{
var internalNode = current.AsInternal();
int idx = FindRoutingIndex(internalNode, key, keyPrefix, strategy);
path[depth] = internalNode;
indices[depth] = idx;
depth++;
current = internalNode.Children[idx]!;
}
var leaf = current.AsLeaf<V>();
int index = FindIndex(leaf, key, keyPrefix, strategy);
if (index > 0)
{
prevKey = leaf.Keys[index - 1];
prevValue = leaf.Values[index - 1];
return true;
}
for (int i = depth - 1; i >= 0; i--)
{
if (indices[i] > 0)
{
current = path[i].Children[indices[i] - 1]!;
while (!current.IsLeaf)
{
var internalNode = current.AsInternal();
current = internalNode.Children[internalNode.Header.Count]!;
}
var targetLeaf = current.AsLeaf<V>();
int last = targetLeaf.Header.Count - 1;
prevKey = targetLeaf.Keys[last];
prevValue = targetLeaf.Values[last];
return true;
}
}
prevKey = default!;
prevValue = default!;
return false;
}
}
}