forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Arrays library with unit tests (OpenZeppelin#1209) (OpenZeppelin#…
…1375) * Add Arrays library with unit tests (OpenZeppelin#1209) * prepared due to snapshot token requirements * add library with method to find upper bound * add unit test for basic and edge cases * Imporove documentation for Arrays library Simplify Arrays.test.js to use short arrays as test date * Added comment for uint256 mid variable. * Explaned why uint256 mid variable calculated as Math.average is safe to use as index of array. (cherry picked from commit f7e53d9)
- Loading branch information
Showing
3 changed files
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
pragma solidity ^0.4.24; | ||
|
||
import "../utils/Arrays.sol"; | ||
|
||
contract ArraysImpl { | ||
|
||
using Arrays for uint256[]; | ||
|
||
uint256[] private array; | ||
|
||
constructor(uint256[] _array) public { | ||
array = _array; | ||
} | ||
|
||
function findUpperBound(uint256 _element) external view returns (uint256) { | ||
return array.findUpperBound(_element); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
pragma solidity ^0.4.23; | ||
|
||
import "../math/Math.sol"; | ||
|
||
|
||
/** | ||
* @title Arrays | ||
* @dev Utility library of inline array functions | ||
*/ | ||
library Arrays { | ||
|
||
/** | ||
* @dev Upper bound search function which is kind of binary search algoritm. It searches sorted | ||
* array to find index of the element value. If element is found then returns it's index otherwise | ||
* it returns index of first element which is grater than searched value. If searched element is | ||
* bigger than any array element function then returns first index after last element (i.e. all | ||
* values inside the array are smaller than the target). Complexity O(log n). | ||
* @param array The array sorted in ascending order. | ||
* @param element The element's value to be find. | ||
* @return The calculated index value. Returns 0 for empty array. | ||
*/ | ||
function findUpperBound( | ||
uint256[] storage array, | ||
uint256 element | ||
) | ||
internal | ||
view | ||
returns (uint256) | ||
{ | ||
if (array.length == 0) { | ||
return 0; | ||
} | ||
|
||
uint256 low = 0; | ||
uint256 high = array.length; | ||
|
||
while (low < high) { | ||
uint256 mid = Math.average(low, high); | ||
|
||
// Note that mid will always be strictly less than high (i.e. it will be a valid array index) | ||
// because Math.average rounds down (it does integer division with truncation). | ||
if (array[mid] > element) { | ||
high = mid; | ||
} else { | ||
low = mid + 1; | ||
} | ||
} | ||
|
||
// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. | ||
if (low > 0 && array[low - 1] == element) { | ||
return low - 1; | ||
} else { | ||
return low; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
const ArraysImpl = artifacts.require('ArraysImpl'); | ||
|
||
const BigNumber = web3.BigNumber; | ||
|
||
require('chai') | ||
.use(require('chai-bignumber')(BigNumber)) | ||
.should(); | ||
|
||
contract('Arrays', function () { | ||
context('Even number of elements', function () { | ||
const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; | ||
|
||
beforeEach(async function () { | ||
this.arrays = await ArraysImpl.new(EVEN_ELEMENTS_ARRAY); | ||
}); | ||
|
||
it('should return correct index for the basic case', async function () { | ||
(await this.arrays.findUpperBound(16)).should.be.bignumber.equal(5); | ||
}); | ||
|
||
it('should return 0 for the first element', async function () { | ||
(await this.arrays.findUpperBound(11)).should.be.bignumber.equal(0); | ||
}); | ||
|
||
it('should return index of the last element', async function () { | ||
(await this.arrays.findUpperBound(20)).should.be.bignumber.equal(9); | ||
}); | ||
|
||
it('should return first index after last element if searched value is over the upper boundary', async function () { | ||
(await this.arrays.findUpperBound(32)).should.be.bignumber.equal(10); | ||
}); | ||
|
||
it('should return 0 for the element under the lower boundary', async function () { | ||
(await this.arrays.findUpperBound(2)).should.be.bignumber.equal(0); | ||
}); | ||
}); | ||
|
||
context('Odd number of elements', function () { | ||
const ODD_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]; | ||
|
||
beforeEach(async function () { | ||
this.arrays = await ArraysImpl.new(ODD_ELEMENTS_ARRAY); | ||
}); | ||
|
||
it('should return correct index for the basic case', async function () { | ||
(await this.arrays.findUpperBound(16)).should.be.bignumber.equal(5); | ||
}); | ||
|
||
it('should return 0 for the first element', async function () { | ||
(await this.arrays.findUpperBound(11)).should.be.bignumber.equal(0); | ||
}); | ||
|
||
it('should return index of the last element', async function () { | ||
(await this.arrays.findUpperBound(21)).should.be.bignumber.equal(10); | ||
}); | ||
|
||
it('should return first index after last element if searched value is over the upper boundary', async function () { | ||
(await this.arrays.findUpperBound(32)).should.be.bignumber.equal(11); | ||
}); | ||
|
||
it('should return 0 for the element under the lower boundary', async function () { | ||
(await this.arrays.findUpperBound(2)).should.be.bignumber.equal(0); | ||
}); | ||
}); | ||
|
||
context('Array with gap', function () { | ||
const WITH_GAP_ARRAY = [11, 12, 13, 14, 15, 20, 21, 22, 23, 24]; | ||
|
||
beforeEach(async function () { | ||
this.arrays = await ArraysImpl.new(WITH_GAP_ARRAY); | ||
}); | ||
|
||
it('should return index of first element in next filled range', async function () { | ||
(await this.arrays.findUpperBound(17)).should.be.bignumber.equal(5); | ||
}); | ||
}); | ||
|
||
context('Empty array', function () { | ||
beforeEach(async function () { | ||
this.arrays = await ArraysImpl.new([]); | ||
}); | ||
|
||
it('should always return 0 for empty array', async function () { | ||
(await this.arrays.findUpperBound(10)).should.be.bignumber.equal(0); | ||
}); | ||
}); | ||
}); |