nano-binary-search 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +137 -0
- package/index.js +12 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Eugene Lazutkin
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# nano-binary-search [![NPM version][npm-img]][npm-url]
|
|
2
|
+
|
|
3
|
+
[npm-img]: https://img.shields.io/npm/v/nano-binary-search.svg
|
|
4
|
+
[npm-url]: https://npmjs.org/package/nano-binary-search
|
|
5
|
+
|
|
6
|
+
This is a nano binary search implementation. It is a tiny single file with no dependencies.
|
|
7
|
+
The only reason I wrote it because I wrote it countless times before, I think it is perfect now
|
|
8
|
+
because it is done right and fits JavaScript — it is ripe for code reuse.
|
|
9
|
+
|
|
10
|
+
## Why?
|
|
11
|
+
|
|
12
|
+
Why do I think it is done right? Because it supports important invariants with
|
|
13
|
+
[splice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice).
|
|
14
|
+
|
|
15
|
+
### No need to worry about inserting values
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import binarySearch from 'nano-binary-search';
|
|
19
|
+
|
|
20
|
+
const sortedArray = [];
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < 100; ++i) {
|
|
23
|
+
const value = Math.floor(Math.random() * 1000);
|
|
24
|
+
|
|
25
|
+
// THIS IS THE IMPORTANT INVARIANT:
|
|
26
|
+
// - works with `splice()` seamlessly to add values
|
|
27
|
+
const index = binarySearch(sortedArray, x => x < value);
|
|
28
|
+
sortedArray.splice(index, 0, value);
|
|
29
|
+
// `sortedArray` is always sorted
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
What if the array is empty? That's fine. What if the value is outside the range of the array?
|
|
34
|
+
That's fine too.
|
|
35
|
+
|
|
36
|
+
### No need to worry about removing values
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import binarySearch from 'nano-binary-search';
|
|
40
|
+
|
|
41
|
+
const sortedArray = [1, 3, 3, 4];
|
|
42
|
+
|
|
43
|
+
// THIS IS THE IMPORTANT INVARIANT:
|
|
44
|
+
// - works with `splice()` seamlessly to remove equal values
|
|
45
|
+
const lowerIndex = binarySearch(sortedArray, x => x < 3),
|
|
46
|
+
upperIndex = binarySearch(sortedArray, x => x <= 3, lowerIndex);
|
|
47
|
+
sortedArray.splice(lowerIndex, upperIndex - lowerIndex);
|
|
48
|
+
// again, `sortedArray` is always sorted
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
What if there is no such value in the array? That's fine. It still works.
|
|
52
|
+
|
|
53
|
+
### API that makes sense
|
|
54
|
+
|
|
55
|
+
There is no need to pass in a function and a comparison value every time.
|
|
56
|
+
In the modern JavaScript/TypeScript, it is easier to get the comparison value straight from the closure
|
|
57
|
+
like in the examples.
|
|
58
|
+
|
|
59
|
+
Do you want to search a sub-array? Just pass in indices.
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
`binarySearch(sortedArray, lessFn, l = 0, r = sortedArray.length)`:
|
|
64
|
+
|
|
65
|
+
* Inputs:
|
|
66
|
+
* `sortedArray` — sorted array of some values. We don't care about values in this array.
|
|
67
|
+
It is up to `lessFn` to compare them. The array should be sorted in a compatible way with `lessFn`.
|
|
68
|
+
* `lessFn` — function that takes three argument and returns a truthy value if the first argument
|
|
69
|
+
(a value from array) is less than our value, whatever it is. The second value is its index,
|
|
70
|
+
and the third is the `sortedArray`.
|
|
71
|
+
* The function interface is modeled on callback function of array methods.
|
|
72
|
+
* The signature: `lessFn(value, index, array) => boolean`.
|
|
73
|
+
* `l` — left index. This index is inclusive. Defaults to 0.
|
|
74
|
+
* `r` — right index. This index is exclusive. Defaults to `sortedArray.length`.
|
|
75
|
+
|
|
76
|
+
The function return an index, where we can safely insert the searched value with `splice()`:
|
|
77
|
+
|
|
78
|
+
* if we used `<` operator, the index will point to the first value that is greater or equal than the searched value.
|
|
79
|
+
* if we used `<=` operator, the index will point to the first value that is greater than the searched value.
|
|
80
|
+
|
|
81
|
+
That's all Folks!
|
|
82
|
+
|
|
83
|
+
## Q & A
|
|
84
|
+
|
|
85
|
+
**Is it fast?**
|
|
86
|
+
|
|
87
|
+
Yes.
|
|
88
|
+
|
|
89
|
+
The only reasonable way to make it faster is to take its code and inline `lessFn()`. The other idea is
|
|
90
|
+
to inline `binarySearch()` itself in your code. That's about all.
|
|
91
|
+
|
|
92
|
+
**What if I want to take into account the index of the searched value?**
|
|
93
|
+
|
|
94
|
+
That's why `lessFn(value, index, array)` has extra arguments.
|
|
95
|
+
|
|
96
|
+
**What if the array uses a custom comparator for sorting, while this binary search uses a `less` function?**
|
|
97
|
+
|
|
98
|
+
Simple:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
let compareFn; // some complex function defined elsewhere
|
|
102
|
+
sortedArray.sort(compareFn);
|
|
103
|
+
|
|
104
|
+
let value; // some search value defined elsewhere
|
|
105
|
+
|
|
106
|
+
const lessFn = x => compareFn(x, value) < 0,
|
|
107
|
+
index = binarySearch(sortedArray, lessFn);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Why doesn't it use a comparator function for searching?**
|
|
111
|
+
|
|
112
|
+
We don't need to compare values in the array for equality. A simple `less` function is enough.
|
|
113
|
+
In many cases it is easier to implement just a `less` function.
|
|
114
|
+
|
|
115
|
+
For example (two argument version for simplicity):
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
const stringLessFn = (a, b) => a < b;
|
|
119
|
+
|
|
120
|
+
// comparator #1 (two comparisons)
|
|
121
|
+
const stringCompareFn1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
122
|
+
|
|
123
|
+
// comparator #2: smarter (a method call)
|
|
124
|
+
const stringCompareFn2 = (a, b) => a.localeCompare(b);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**I still have questions!**
|
|
128
|
+
|
|
129
|
+
Look at the code of `index.js` and `tests/` for more details. Go to the GitHub repository and ask.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
This project is licensed under the BSD-3-Clause license.
|
|
134
|
+
|
|
135
|
+
## Release history
|
|
136
|
+
|
|
137
|
+
- 1.0.0 *Initial release*
|
package/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
export const binarySearch = (sortedArray, lessFn, l = 0, r = sortedArray.length) => {
|
|
4
|
+
while (l < r) {
|
|
5
|
+
const m = l + Math.floor((r - l) / 2);
|
|
6
|
+
if (lessFn(sortedArray[m], m, sortedArray)) l = m + 1;
|
|
7
|
+
else r = m;
|
|
8
|
+
}
|
|
9
|
+
return r;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default binarySearch;
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nano-binary-search",
|
|
3
|
+
"description": "Binary search for JavaScript done right.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "tape6 --flags FO"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/uhop/nano-binary-search.git"
|
|
13
|
+
},
|
|
14
|
+
"funding": {
|
|
15
|
+
"type": "github",
|
|
16
|
+
"url": "https://github.com/sponsors/uhop"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/uhop/nano-binary-search/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/uhop/nano-binary-search#readme",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"binary search",
|
|
24
|
+
"algorithm"
|
|
25
|
+
],
|
|
26
|
+
"author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com/)",
|
|
27
|
+
"license": "BSD-3-Clause",
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"tape6": {
|
|
34
|
+
"tests": [
|
|
35
|
+
"tests/test-*.*js"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tape-six": "^0.9.6"
|
|
40
|
+
}
|
|
41
|
+
}
|