internal override Node?InsertRange(int branchingFactor, bool isAppend, int index, IEnumerable <T> collection) { int pageIndex = FindLowerBound(_offsets, _nodeCount, index); int previousCount = _nodes[pageIndex].Count; Node?lastImpactedChild = _nodes[pageIndex].InsertRange(branchingFactor, isAppend, index - _offsets[pageIndex], collection); if (lastImpactedChild == null) { int insertionCount = _nodes[pageIndex].Count - previousCount; for (int i = pageIndex + 1; i < _nodeCount; i++) { _offsets[i] += insertionCount; } _count += insertionCount; return(null); } for (int i = pageIndex + 1; i < _nodeCount; i++) { _offsets[i] = _offsets[i - 1] + _nodes[i - 1].Count; } _count = _offsets[_nodeCount - 1] + _nodes[_nodeCount - 1].Count; pageIndex++; IndexNode insertionNode = this; Node? lastIndexNode = null; for (Node?item = _nodes[pageIndex - 1].NextNode; true; item = item.NextNode) { Debug.Assert(item != null, "Assertion failed: item != null"); Debug.Assert(pageIndex >= 0 && pageIndex <= insertionNode._nodes.Length, "Assertion failed: pageIndex >= 0 && pageIndex <= insertionNode._nodes.Length"); IndexNode?newLastIndex = insertionNode.InsertIndex(branchingFactor, isAppend, pageIndex, item); if (newLastIndex != null) { // this insertion resulted in a split, so at minimum 'pageIndex' must be updated if (lastIndexNode != null && insertionNode != lastIndexNode) { // We were not inserting into the last node (an earlier split in the InsertRange operation // resulted in insertions prior to the last node) // // When we reach this point, a previous index insertion caused a split which did not change // the insertion node. Afterwards, we continued inserting and have now reached a point where // the page is splitting a second time. It is impossible for this split to be caused by an // insertion in the first half of the list. The primary difference between this situation // and similar code in LeafNode is the first insertion index - for LeafNode it is possible // to start inserting at index 0, but for IndexNode the first possible insertion is index 1. Debug.Assert(pageIndex >= insertionNode._nodeCount, $"Assertion failed: {nameof(pageIndex)} >= {nameof(insertionNode)}._nodeCount"); pageIndex = pageIndex + 1 - insertionNode._nodeCount; insertionNode = newLastIndex; } else if (pageIndex < insertionNode._nodeCount) { // The split resulted in a new last node, but no change in the insertion node. pageIndex++; lastIndexNode = newLastIndex; } else { // The split resulted in a new last node which becomes the new insertion node. pageIndex = pageIndex + 1 - insertionNode._nodeCount; lastIndexNode = newLastIndex; insertionNode = newLastIndex; } } else { pageIndex++; } if (item == lastImpactedChild) { break; } } return(lastIndexNode); }
private IndexNode InsertIndex(int branchingFactor, bool isAppend, int index, Node node) { if (_nodeCount < _nodes.Length) { if (index < _nodeCount) { Array.Copy(_nodes, index, _nodes, index + 1, _nodeCount - index); Array.Copy(_offsets, index, _offsets, index + 1, _nodeCount - index); } else { _offsets[_nodeCount] = Count; } _nodes[index] = node; _nodeCount++; int delta = node.Count; for (int i = index + 1; i < _nodeCount; i++) { _offsets[i] += delta; } _count += delta; return(null); } if (isAppend) { // optimize the case of adding at the end of the overall list IndexNode result = new IndexNode(branchingFactor); result._nodes[0] = node; result._nodeCount = 1; result._count = node.Count; _next = result; return(result); } else { // split the node IndexNode splitNode = new IndexNode(branchingFactor); int splitPoint = _nodeCount / 2; bool forceNext = false; if ((_nodeCount + 1) / 2 > splitPoint && index > splitPoint) { // When splitting a node with an odd branching factor, prior to insertion one split node will // have (b-1)/2 nodes and the other will have (b+1)/2 nodes. Since the minimum number of nodes // after insertion is (b+1)/2, the split point uniquely determines the insertion point. This // block handles the case where the insertion point is index (b+1)/2 by forcing it to the first // node of the next page instead of adding it (where it fits) at the end of the first page. splitPoint++; forceNext = true; } Array.Copy(_nodes, splitPoint, splitNode._nodes, 0, _nodeCount - splitPoint); Array.Copy(_offsets, splitPoint, splitNode._offsets, 0, _nodeCount - splitPoint); Array.Clear(_nodes, splitPoint, _nodeCount - splitPoint); Array.Clear(_offsets, splitPoint, _nodeCount - splitPoint); splitNode._nodeCount = _nodeCount - splitPoint; int adjustment = splitNode._offsets[0]; for (int i = 0; i < splitNode._nodeCount; i++) { splitNode._offsets[i] -= adjustment; } splitNode._count = _count - adjustment; _nodeCount = splitPoint; _count = adjustment; // insert the new element into the correct half if (!forceNext && index <= splitPoint) { InsertIndex(branchingFactor, false, index, node); } else { splitNode.InsertIndex(branchingFactor, false, index - splitPoint, node); } splitNode._next = _next; _next = splitNode; return(splitNode); } }