Skip to content

Commit 1351b75

Browse files
authored
chore: integrate binary-search (#10)
* chore: integrate binary-search * add readme.md
1 parent e457732 commit 1351b75

File tree

6 files changed

+151
-12
lines changed

6 files changed

+151
-12
lines changed

package-lock.json

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,5 @@
3636
"borp": "^0.17.0",
3737
"tslint": "^5.20.1",
3838
"typescript": "^5.5.4"
39-
},
40-
"dependencies": {
41-
"binary-search": "^1.3.6"
4239
}
4340
}

src/binary-search/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
binary-search
2+
=============
3+
4+
Integrated from https://github.com/darkskyapp/binary-search
5+
6+
This is a really tiny, stupid, simple binary search library for Node.JS. We wrote it because existing solutions were bloated and incorrect. This version is a straight port of the Java version mentioned by Joshua Bloch in his article, [Nearly All Binary Searches and Merge Sorts are Broken](http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html).
7+
8+
You pass the function a sorted array, a value, and the sort comparator function. If the value is found in the array, it returns a nonnegative integer representing the index of the value in the array. (Note that if your array contains duplicates, the index within the run of duplicates is arbitrary.) If the value is _not_ in the array, then `-(index + 1)` is returned, where `index` is where the value should be inserted into the array to maintain sorted order.
9+
10+
You may also pass the search function optional fourth and fifth arguments, representing the minimum and maximum indices of the array to search, in case you wish to limit the section of the array searched. (These default, of course, to `0` and `length-1`, respectively.)
11+
12+
The sort comparator function should be the same one you use to sort your array. It accepts optional third and fourth arguments, the current index and the overall array: these should not be necessary except in highly unusual circumstances.
13+
14+
Thanks to [Conrad Irwin](https://github.com/ConradIrwin) and [Michael Marino](https://github.com/mgmarino) for, ironically, pointing out bugs.
15+
16+
Example
17+
-------
18+
19+
```js
20+
var bs = require("binary-search");
21+
22+
bs([1, 2, 3, 4], 3, function(element, needle) { return element - needle; });
23+
// => 2
24+
25+
bs([1, 2, 4, 5], 3, function(element, needle) { return element - needle; });
26+
// => -3
27+
```
28+
29+
License
30+
-------
31+
32+
To the extent possible by law, The Dark Sky Company, LLC has [waived all
33+
copyright and related or neighboring rights][cc0] to this library.
34+
35+
[cc0]: http://creativecommons.org/publicdomain/zero/1.0/

src/binary-search/binary-search.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @param haystack
3+
* @param needle
4+
* @param comparator Notes about comparator return value:
5+
* when a<b the comparator's returned value should be:
6+
* negative number or a value such that `+value` is a negative number
7+
* examples: `-1` or the string `"-1"`
8+
* when a>b the comparator's returned value should be:
9+
* positive number or a value such that `+value` is a positive number
10+
* examples: `1` or the string `"1"`
11+
* when a===b
12+
* any value other than the return cases for a<b and a>b
13+
* examples: undefined, NaN, 'abc'
14+
* @param low
15+
* @param high
16+
* @returns {number} returns index of found result or number < 0 if not found
17+
*/
18+
export function binarySearch<A, B>(
19+
haystack: ArrayLike<A>,
20+
needle: B,
21+
comparator: (a: A, b: B, index?: number, haystack?: ArrayLike<A>) => any,
22+
low?: number,
23+
high?: number): number {
24+
let mid;
25+
let cmp;
26+
27+
if (low === undefined) {
28+
low = 0;
29+
} else {
30+
low = low | 0;
31+
if (low < 0 || low >= haystack.length) {
32+
throw new RangeError('invalid lower bound');
33+
}
34+
}
35+
36+
if (high === undefined) {
37+
high = haystack.length - 1;
38+
} else {
39+
high = high | 0;
40+
if (high < low || high >= haystack.length) {
41+
throw new RangeError('invalid upper bound');
42+
}
43+
}
44+
45+
while (low <= high) {
46+
// The naive `low + high >>> 1` could fail for array lengths > 2**31
47+
// because `>>>` converts its operands to int32. `low + (high - low >>> 1)`
48+
// works for array lengths <= 2**32-1 which is also Javascript's max array
49+
// length.
50+
mid = low + ((high - low) >>> 1);
51+
cmp = +comparator(haystack[mid], needle, mid, haystack);
52+
53+
// Too low.
54+
if (cmp < 0.0) {
55+
low = mid + 1;
56+
} else if (cmp > 0.0) {
57+
high = mid - 1;
58+
} else {
59+
return mid;
60+
}
61+
}
62+
63+
// Key not found.
64+
return ~low;
65+
}

src/node/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as assert from 'assert';
2-
import binarySearch = require('binary-search');
2+
import { binarySearch } from '../binary-search/binary-search';
33
import { Edge } from '../edge';
44

55
/**

test/binary-search.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, it, type TestContext } from 'node:test';
2+
import { binarySearch } from '../src/binary-search/binary-search';
3+
4+
describe('binarysearch', () => {
5+
const arr = [1, 2, 2, 2, 3, 5, 9];
6+
const cmp = (a: number, b: number) => a - b;
7+
8+
it('should bail if not passed an array', (t: TestContext) => {
9+
// @ts-expect-error
10+
t.assert.throws(() => { binarySearch(undefined, 3, cmp); }, TypeError);
11+
});
12+
13+
it('should bail if not passed a comparator', (t: TestContext) => {
14+
// @ts-expect-error
15+
t.assert.throws(() => { binarySearch(arr, 3, undefined); }, TypeError);
16+
});
17+
18+
it('should return the index of an item in a sorted array', (t: TestContext) => {
19+
t.assert.strictEqual(binarySearch(arr, 3, cmp), 4);
20+
});
21+
22+
it('should return the index of where the item would go plus one, negated, if the item is not found',
23+
(t: TestContext) => {
24+
t.assert.strictEqual(binarySearch(arr, 4, cmp), -6);
25+
});
26+
27+
it('should return any valid index if an item exists multiple times in the array',
28+
(t: TestContext) => {
29+
t.assert.strictEqual(binarySearch(arr, 2, cmp), 3);
30+
});
31+
32+
it('should work even on empty arrays', (t: TestContext) => {
33+
t.assert.strictEqual(binarySearch([], 42, cmp), -1);
34+
});
35+
36+
it('should work even on arrays of doubles', (t: TestContext) => {
37+
t.assert.strictEqual(binarySearch([0.0, 0.1, 0.2, 0.3, 0.4], 0.25, cmp), -4);
38+
});
39+
40+
it('should pass the index and array parameters to the comparator', (t: TestContext) => {
41+
const indexes: number[] = [];
42+
const indexCmp = (a: number, b: number, i?: number, array?: ArrayLike<number>) => {
43+
t.assert.strictEqual(array, arr);
44+
indexes.push(i!);
45+
return cmp(a, b);
46+
};
47+
binarySearch(arr, 3, indexCmp);
48+
t.assert.deepStrictEqual(indexes, [3, 5, 4]);
49+
});
50+
});

0 commit comments

Comments
 (0)