diff --git a/src/Base.ts b/src/Base.ts index a9cc514..946ae24 100644 --- a/src/Base.ts +++ b/src/Base.ts @@ -10,7 +10,7 @@ export class Base { *tree.print() *``` */ - print ():void { + print (): void { Base.print(this) } @@ -26,9 +26,103 @@ export class Base { *const index = tree.bufferIndexOf(haystack, needle) *``` */ - protected _bufferIndexOf (array: Buffer[], element: Buffer):number { + protected _bufferIndexOf ( + array: Buffer[], + element: Buffer, + isSorted: boolean = false + ): number { + if (isSorted) { + return this.binarySearch(array, element, Buffer.compare) + } + + const eqChecker = (buffer1, buffer2) => buffer1.equals(buffer2) + return this.linearSearch(array, element, eqChecker) + } + + /** + * binarySearch + * @desc Returns the first index of which given item is found in array using binary search. + * @param {Buffer[]} array - Array of items. + * @param {Buffer} element - Item to find. + * @param {Function} compareFunction + * @return {Number} - Index number + * + * @example + * ```js + *const index = MerkleTree.binarySearch(array, element, Buffer.compare) + *``` + */ + static binarySearch ( + array: Buffer[], + element: Buffer, + compareFunction: (a: unknown, b: unknown) => number + ): number { + let start = 0 + let end = array.length - 1 + + // Iterate while start not meets end + while (start <= end) { + // Find the mid index + const mid = Math.floor((start + end) / 2) + + // Check if the mid value is greater than, equal to, or less than search element. + const ordering = compareFunction(array[mid], element) + + // If element is present at mid, start iterating for searching first appearance. + if (ordering === 0) { + // Linear reverse iteration until the first matching item index is found. + for (let i = mid - 1; i >= 0; i--) { + if (compareFunction(array[i], element) === 0) continue + return i + 1 + } + return 0 + } /* Else look in left or right half accordingly */ else if (ordering < 0) { + start = mid + 1 + } else { + end = mid - 1 + } + } + + return -1 + } + + /** + * binarySearch + * @desc Returns the first index of which given item is found in array using binary search. + * @param {Buffer[]} array - Array of items. + * @param {Buffer} element - Item to find. + * @param {Function} compareFunction + * @return {Number} - Index number + * + * @example + * ```js + *const index = tree.binarySearch(array, element, Buffer.compare) + *``` + */ + binarySearch ( + array: Buffer[], + element: Buffer, + compareFunction: (a: unknown, b: unknown) => number + ): number { + return Base.binarySearch(array, element, compareFunction) + } + + /** + * linearSearch + * @desc Returns the first index of which given item is found in array using linear search. + * @param {Buffer[]} array - Array of items. + * @param {Buffer} element - Item to find. + * @param {Function} eqChecker + * @return {Number} - Index number + * + * @example + * ```js + *const index = MerkleTree.linearSearch(array, element, (a, b) => a === b) + *``` + */ + static linearSearch (array: Buffer[], element: Buffer, eqChecker: (a: unknown, b: unknown) => boolean):number { for (let i = 0; i < array.length; i++) { - if (element.equals(array[i])) { + if (eqChecker(array[i], element)) { return i } } @@ -36,6 +130,23 @@ export class Base { return -1 } + /** + * linearSearch + * @desc Returns the first index of which given item is found in array using linear search. + * @param {Buffer[]} array - Array of items. + * @param {Buffer} element - Item to find. + * @param {Function} eqChecker + * @return {Number} - Index number + * + * @example + * ```js + *const index = tree.linearSearch(array, element, (a, b) => a === b) + *``` + */ + linearSearch (array: Buffer[], element: Buffer, eqChecker: (a: unknown, b: unknown) => boolean):number { + return Base.linearSearch(array, element, eqChecker) + } + /** * bufferify * @desc Returns a buffer type for the given value. @@ -47,7 +158,7 @@ export class Base { *const buf = MerkleTree.bufferify('0x1234') *``` */ - static bufferify (value: any):Buffer { + static bufferify (value: any): Buffer { if (!Buffer.isBuffer(value)) { // crypto-js support if (typeof value === 'object' && value.words) { @@ -81,8 +192,8 @@ export class Base { *console.log(MerkleTree.isHexString('0x1234')) *``` */ - static isHexString (v: string):boolean { - return (typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v)) + static isHexString (v: string): boolean { + return typeof v === 'string' && /^(0x)?[0-9A-Fa-f]*$/.test(v) } /** @@ -95,7 +206,7 @@ export class Base { *MerkleTree.print(tree) *``` */ - static print (tree: any):void { + static print (tree: any): void { console.log(tree.toString()) } @@ -109,7 +220,7 @@ export class Base { *const hexStr = tree.bufferToHex(Buffer.from('A')) *``` */ - bufferToHex (value: Buffer, withPrefix: boolean = true):string { + bufferToHex (value: Buffer, withPrefix: boolean = true): string { return Base.bufferToHex(value, withPrefix) } @@ -123,8 +234,10 @@ export class Base { *const hexStr = MerkleTree.bufferToHex(Buffer.from('A')) *``` */ - static bufferToHex (value: Buffer, withPrefix: boolean = true):string { - return `${withPrefix ? '0x' : ''}${(value || Buffer.alloc(0)).toString('hex')}` + static bufferToHex (value: Buffer, withPrefix: boolean = true): string { + return `${withPrefix ? '0x' : ''}${(value || Buffer.alloc(0)).toString( + 'hex' + )}` } /** @@ -138,7 +251,7 @@ export class Base { *const buf = tree.bufferify('0x1234') *``` */ - bufferify (value: any):Buffer { + bufferify (value: any): Buffer { return Base.bufferify(value) } @@ -153,7 +266,7 @@ export class Base { *const fn = tree.bufferifyFn((value) => sha256(value)) *``` */ - bufferifyFn (f: any):any { + bufferifyFn (f: any): any { return (value: any): Buffer => { const v = f(value) if (Buffer.isBuffer(v)) { @@ -173,7 +286,12 @@ export class Base { } // crypto-js support - return Buffer.from(f(CryptoJS.enc.Hex.parse(value.toString('hex'))).toString(CryptoJS.enc.Hex), 'hex') + return Buffer.from( + f(CryptoJS.enc.Hex.parse(value.toString('hex'))).toString( + CryptoJS.enc.Hex + ), + 'hex' + ) } } @@ -188,7 +306,7 @@ export class Base { *console.log(MerkleTree.isHexString('0x1234')) *``` */ - protected _isHexString (value: string):boolean { + protected _isHexString (value: string): boolean { return Base.isHexString(value) } @@ -198,7 +316,7 @@ export class Base { * @param {Number} value * @return {Number} */ - protected _log2 (n: number):number { + protected _log2 (n: number): number { return n === 1 ? 0 : 1 + this._log2((n / 2) | 0) } @@ -215,7 +333,7 @@ export class Base { *console.log(zipped) // [ [ 'a', 'A' ], [ 'b', 'B' ] ] *``` */ - protected _zip (a: any[], b: any[]):any[][] { + protected _zip (a: any[], b: any[]): any[][] { return a.map((e, i) => [e, b[i]]) } } diff --git a/src/MerkleTree.ts b/src/MerkleTree.ts index 7580ec3..a0c2327 100644 --- a/src/MerkleTree.ts +++ b/src/MerkleTree.ts @@ -256,7 +256,7 @@ export class MerkleTree extends Base { } } - return this.leaves.filter(leaf => this._bufferIndexOf(values, leaf) !== -1) + return this.leaves.filter(leaf => this._bufferIndexOf(values, leaf, this.sortLeaves) !== -1) } return this.leaves @@ -518,13 +518,7 @@ export class MerkleTree extends Base { const proof = [] if (!Number.isInteger(index)) { - index = -1 - - for (let i = 0; i < this.leaves.length; i++) { - if (Buffer.compare(leaf, this.leaves[i]) === 0) { - index = i - } - } + index = this._bufferIndexOf(this.leaves, leaf, this.sortLeaves) } if (index <= -1) { @@ -870,7 +864,7 @@ export class MerkleTree extends Base { els = els.sort(Buffer.compare) } - let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) + let ids = els.map((el) => this._bufferIndexOf(this.leaves, el, this.sortLeaves)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) if (!ids.every((idx) => idx !== -1)) { throw new Error('Element does not exist in Merkle tree') } @@ -993,7 +987,7 @@ export class MerkleTree extends Base { if (leaves.every(Number.isInteger)) { ids = [...leaves].sort((a, b) => a === b ? 0 : a > b ? 1 : -1) // Indices where passed } else { - ids = leaves.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) + ids = leaves.map((el) => this._bufferIndexOf(this.leaves, el, this.sortLeaves)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1) } if (!ids.every((idx: number) => idx !== -1)) { diff --git a/test/Base.test.js b/test/Base.test.js index 85f0997..2e61571 100644 --- a/test/Base.test.js +++ b/test/Base.test.js @@ -20,3 +20,17 @@ test('bufferifyFn', t => { t.deepEqual(fn('0x123'), Buffer.from('123', 'hex')) t.deepEqual(fn('XYZ'), Buffer.from('XYZ')) }) + +test('binarySearch', t => { + t.plan(3) + + const base = new Base() + const compareFunction = (a, b) => { + if (a === b) return 0 + else if (a < b) return -1 + else return 1 + } + t.equal(base.binarySearch([2, 2, 3, 3, 3, 4, 4, 4, 4], 3, compareFunction), 2) + t.equal(base.binarySearch([3, 3, 3, 3, 3, 4, 4, 4, 4], 3, compareFunction), 0) + t.equal(base.binarySearch([2, 2, 3, 3, 3, 4, 4, 4, 4], 1, compareFunction), -1) +})