Add sort in memory to Arrays library (#4846)

Co-authored-by: RenanSouza2 <renan.rodrigues.souza1@gmail.com>
Co-authored-by: Ernesto García <ernestognw@gmail.com>
This commit is contained in:
Hadrien Croubois
2024-02-06 21:49:12 +01:00
committed by GitHub
parent 036c3cbef2
commit 0a757ec463
7 changed files with 184 additions and 28 deletions

View File

@ -12,6 +12,69 @@ import {Math} from "./math/Math.sol";
library Arrays {
using StorageSlot for bytes32;
/**
* @dev Sort an array (in memory) in increasing order.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*/
function sort(uint256[] memory array) internal pure returns (uint256[] memory) {
_quickSort(array, 0, array.length);
return array;
}
/**
* @dev Performs a quick sort on an array in memory. The array is sorted in increasing order.
*
* Invariant: `i <= j <= array.length`. This is the case when initially called by {sort} and is preserved in
* subcalls.
*/
function _quickSort(uint256[] memory array, uint256 i, uint256 j) private pure {
unchecked {
// Can't overflow given `i <= j`
if (j - i < 2) return;
// Use first element as pivot
uint256 pivot = unsafeMemoryAccess(array, i);
// Position where the pivot should be at the end of the loop
uint256 index = i;
for (uint256 k = i + 1; k < j; ++k) {
// Unsafe access is safe given `k < j <= array.length`.
if (unsafeMemoryAccess(array, k) < pivot) {
// If array[k] is smaller than the pivot, we increment the index and move array[k] there.
_swap(array, ++index, k);
}
}
// Swap pivot into place
_swap(array, i, index);
_quickSort(array, i, index); // Sort the left side of the pivot
_quickSort(array, index + 1, j); // Sort the right side of the pivot
}
}
/**
* @dev Swaps the elements at positions `i` and `j` in the `arr` array.
*/
function _swap(uint256[] memory arr, uint256 i, uint256 j) private pure {
assembly {
let start := add(arr, 0x20) // Pointer to the first element of the array
let pos_i := add(start, mul(i, 0x20))
let pos_j := add(start, mul(j, 0x20))
let val_i := mload(pos_i)
let val_j := mload(pos_j)
mstore(pos_i, val_j)
mstore(pos_j, val_i)
}
}
/**
* @dev Searches a sorted `array` and returns the first index that contains
* a value greater or equal to `element`. If no such index exists (i.e. all
@ -238,7 +301,7 @@ library Arrays {
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
@ -249,7 +312,18 @@ library Arrays {
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
function unsafeMemoryAccess(bytes32[] memory arr, uint256 pos) internal pure returns (bytes32 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}