Skip to content

Commit

Permalink
✨ UsedIndexMap
Browse files Browse the repository at this point in the history
  • Loading branch information
Philogy committed Sep 16, 2024
1 parent 57e8960 commit ecab746
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
81 changes: 81 additions & 0 deletions src/collections/UsedIndexMap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {UintVec, VecLib} from "./Vec.sol";

struct UsedIndex {
uint256 fromIndex;
uint256 toIndex;
}

struct UsedIndexMap {
uint256 length;
UintVec usedIndicesPtrs;
}

using UsedIndexMapLib for UsedIndexMap global;

/// @author philogy <https://github.com/philogy>
library UsedIndexMapLib {
function init(UsedIndexMap memory self, uint256 length, uint256 startCapacity) internal pure {
self.length = length;
self.usedIndicesPtrs = VecLib.uint_with_cap(startCapacity);
}

function lookupIndex(UsedIndexMap memory self, uint256 index)
internal
pure
returns (bool isUsed, uint256 usedIndex, uint256 realIndex)
{
require(index < self.length, "Index out-of-bounds");

function(UintVec memory, uint256) pure returns (uint256) vecUintGet = VecLib.get;
function(UintVec memory, uint256) pure returns (UsedIndex memory) vecUsedIndexGet;
assembly {
vecUsedIndexGet := vecUintGet
}

for (usedIndex = 0; usedIndex < self.usedIndicesPtrs.length; usedIndex++) {
UsedIndex memory used = vecUsedIndexGet(self.usedIndicesPtrs, usedIndex);
if (used.fromIndex == index) {
isUsed = true;
realIndex = used.toIndex;
return (isUsed, usedIndex, realIndex);
}
}

return (false, usedIndex, index);
}

function mapIndex(UsedIndexMap memory self, uint256 index) internal pure returns (uint256 realIndex) {
(,, realIndex) = self.lookupIndex(index);
}

function useIndex(UsedIndexMap memory self, uint256 index) internal pure returns (uint256 realIndex) {
require(self.length > 0, "Nothing to use");
bool isUsed;
uint256 usedIndex;
(isUsed, usedIndex, realIndex) = self.lookupIndex(index);
(,, uint256 lastIndex) = self.lookupIndex(self.length - 1);
self.length -= 1;
if (isUsed) {
function(UsedIndex memory, uint256) pure setUsed = _set;
function(uint256, uint256) pure setWithPtr;
assembly {
setWithPtr := setUsed
}
setWithPtr(self.usedIndicesPtrs.get(usedIndex), lastIndex);
} else {
UsedIndex memory newUsed = UsedIndex({fromIndex: index, toIndex: lastIndex});
uint256 ptr;
assembly {
ptr := newUsed
}
self.usedIndicesPtrs.push(ptr);
}
}

function _set(UsedIndex memory used, uint256 newToIndex) private pure {
used.toIndex = newToIndex;
}
}
48 changes: 48 additions & 0 deletions test/collections/UsedIndexMap.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {UsedIndexMap} from "src/collections/UsedIndexMap.sol";
import {PRNG} from "src/collections/PRNG.sol";

import {console} from "forge-std/console.sol";
import {FormatLib} from "src/libraries/FormatLib.sol";

/// @author philogy <https://github.com/philogy>
contract UsedIndexMapTest is Test {
using FormatLib for *;

function setUp() public {}

uint256[] ogNums;
uint256[] nums;

function test_fuzzing_randomUse(uint256 length, PRNG memory rng) public {
length = bound(length, 1, 1000);
for (uint256 i = 0; i < length; i++) {
ogNums.push(i);
nums.push(i);
}
UsedIndexMap memory map;
map.init(length, length / 2);

uint256 iters = rng.randuint(1, length + 1);
for (uint256 i = 0; i < iters; i++) {
uint256 index = rng.randuint(nums.length);

uint256 num = nums[index];
uint256 ui = map.useIndex(index);
uint256 mappedNum = ogNums[ui];

assertEq(num, mappedNum);

uint256 lastIndex = nums.length - 1;
uint256 lastNum = nums[lastIndex];
nums.pop();

if (index != lastIndex) {
nums[index] = lastNum;
}
}
}
}

0 comments on commit ecab746

Please sign in to comment.