|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.20; |
| 4 | + |
| 5 | +/** |
| 6 | + * TODO: |
| 7 | + * - optimizations |
| 8 | + * - changeset |
| 9 | + * - add tests |
| 10 | + * - add docs |
| 11 | + * - add base impl link here |
| 12 | + * |
| 13 | + * @dev Library for managing a max heap data structure. |
| 14 | + * |
| 15 | + * A max heap is a complete binary tree where each node has a value greater than or equal to its children. |
| 16 | + * The root node contains the maximum value in the heap. |
| 17 | + * |
| 18 | + * This library provides functions to insert, update, and remove elements from the max heap, as well as to |
| 19 | + * retrieve the maximum element (peek) and check the validity of the heap. |
| 20 | + * |
| 21 | + * The max heap has the following properties: |
| 22 | + * |
| 23 | + * - Insertion: O(log n) |
| 24 | + * - Deletion of maximum element: O(log n) |
| 25 | + * - Retrieval of maximum element (peek): O(1) |
| 26 | + * - Update of an element: O(log n) |
| 27 | + * |
| 28 | + * The max heap is implemented using two mappings: |
| 29 | + * - `tree`: Maps the position in the heap to the item ID. |
| 30 | + * - `items`: Maps the item ID to its corresponding `Node` struct, which contains the value and heap index. |
| 31 | + * |
| 32 | + * Example usage: |
| 33 | + * |
| 34 | + * ```solidity |
| 35 | + * contract Example { |
| 36 | + * using MaxHeap for MaxHeap.MaxHeap; |
| 37 | + * |
| 38 | + * MaxHeap.MaxHeap private heap; |
| 39 | + * |
| 40 | + * function addItem(uint256 itemId, uint256 value) public { |
| 41 | + * heap.insert(itemId, value); |
| 42 | + * } |
| 43 | + * |
| 44 | + * function removeMax() public returns (uint256, uint256) { |
| 45 | + * return heap.pop(); |
| 46 | + * } |
| 47 | + * |
| 48 | + * function getMax() public view returns (uint256, uint256) { |
| 49 | + * return heap.peek(); |
| 50 | + * } |
| 51 | + * } |
| 52 | + * ``` |
| 53 | + */ |
| 54 | +library MaxHeap { |
| 55 | + /** |
| 56 | + * @dev The position doesn't have a _parent as it's the root. |
| 57 | + */ |
| 58 | + error InvalidPositionZero(); |
| 59 | + |
| 60 | + struct Node { |
| 61 | + uint256 value; |
| 62 | + uint256 heapIndex; |
| 63 | + } |
| 64 | + |
| 65 | + struct MaxHeap { |
| 66 | + mapping(uint256 => uint256) tree; |
| 67 | + mapping(uint256 => Node) items; |
| 68 | + uint256 size; |
| 69 | + } |
| 70 | + |
| 71 | + function _parent(uint256 pos) private pure returns (uint256) { |
| 72 | + if (pos == 0) revert InvalidPositionZero(); |
| 73 | + return (pos - 1) / 2; |
| 74 | + } |
| 75 | + |
| 76 | + function _swap(MaxHeap storage heap, uint256 fpos, uint256 spos) private { |
| 77 | + (heap.tree[fpos], heap.tree[spos]) = (heap.tree[spos], heap.tree[fpos]); |
| 78 | + (heap.items[heap.tree[fpos]].heapIndex, heap.items[heap.tree[spos]].heapIndex) = (fpos, spos); |
| 79 | + } |
| 80 | + |
| 81 | + function heapify(MaxHeap storage heap, uint256 pos) internal { |
| 82 | + if (pos >= (heap.size / 2) && pos <= heap.size) return; |
| 83 | + |
| 84 | + uint256 left = 2 * pos + 1; |
| 85 | + uint256 right = left + 1; |
| 86 | + |
| 87 | + uint256 leftValue = left < heap.size ? heap.items[heap.tree[left]].value : 0; |
| 88 | + uint256 rightValue = right < heap.size ? heap.items[heap.tree[right]].value : 0; |
| 89 | + uint256 posValue = heap.items[heap.tree[pos]].value; |
| 90 | + |
| 91 | + if (posValue < leftValue || posValue < rightValue) { |
| 92 | + if (leftValue > rightValue) { |
| 93 | + _swap(heap, pos, left); |
| 94 | + heapify(heap, left); |
| 95 | + } else { |
| 96 | + _swap(heap, pos, right); |
| 97 | + heapify(heap, right); |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + function insert(MaxHeap storage heap, uint256 itemId, uint256 value) internal { |
| 103 | + heap.tree[heap.size] = itemId; |
| 104 | + heap.items[itemId] = Node({value: value, heapIndex: heap.size}); |
| 105 | + |
| 106 | + uint256 current = heap.size; |
| 107 | + uint256 parentOfCurrent = _parent(current); |
| 108 | + |
| 109 | + while (current != 0 && heap.items[heap.tree[current]].value > heap.items[heap.tree[parentOfCurrent]].value) { |
| 110 | + uint256 parentOfCurrent = _parent(current); |
| 111 | + _swap(heap, current, parentOfCurrent); |
| 112 | + current = parentOfCurrent; |
| 113 | + parentOfCurrent = _parent(current); |
| 114 | + } |
| 115 | + heap.size++; |
| 116 | + } |
| 117 | + |
| 118 | + function update(MaxHeap storage heap, uint256 itemId, uint256 newValue) internal { |
| 119 | + // Check that itemId exists in heap |
| 120 | + // TODO: update return with revert? |
| 121 | + if (heap.items[itemId].heapIndex >= heap.size) return; |
| 122 | + |
| 123 | + uint256 position = heap.items[itemId].heapIndex; |
| 124 | + uint256 oldValue = heap.items[itemId].value; |
| 125 | + |
| 126 | + heap.items[itemId].value = newValue; |
| 127 | + |
| 128 | + if (newValue > oldValue) { |
| 129 | + while ( |
| 130 | + position != 0 && heap.items[heap.tree[position]].value > heap.items[heap.tree[_parent(position)]].value |
| 131 | + ) { |
| 132 | + uint256 parentOfPosition = _parent(position); |
| 133 | + _swap(heap, position, parentOfPosition); |
| 134 | + position = parentOfPosition; |
| 135 | + } |
| 136 | + } else heapify(heap, position); |
| 137 | + } |
| 138 | + |
| 139 | + function pop(MaxHeap storage heap) internal returns (uint256, uint256) { |
| 140 | + // TODO: should it revert if empty? |
| 141 | + |
| 142 | + uint256 popped = heap.tree[0]; |
| 143 | + uint256 returnValue = heap.items[popped].value; |
| 144 | + |
| 145 | + delete heap.items[popped]; |
| 146 | + |
| 147 | + heap.tree[0] = heap.tree[--heap.size]; |
| 148 | + |
| 149 | + heap.items[heap.tree[0]].heapIndex = 0; |
| 150 | + |
| 151 | + delete heap.tree[heap.size]; |
| 152 | + |
| 153 | + heapify(heap, 0); |
| 154 | + |
| 155 | + return (popped, returnValue); |
| 156 | + } |
| 157 | + |
| 158 | + function peek(MaxHeap storage heap) internal view returns (uint256, uint256) { |
| 159 | + // TODO: should it revert if empty? |
| 160 | + return (heap.tree[0], heap.items[heap.tree[0]].value); |
| 161 | + } |
| 162 | +} |
0 commit comments