Skip to content

Commit

Permalink
feat(arrays): add binary search predicates, tests, update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Dec 24, 2019
1 parent 2ef6a12 commit b8f421e
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 6 deletions.
29 changes: 29 additions & 0 deletions packages/arrays/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This project is part of the
- [Installation](#installation)
- [Dependencies](#dependencies)
- [API](#api)
- [Binary search result predicates](#binary-search-result-predicates)
- [Authors](#authors)
- [License](#license)

Expand Down Expand Up @@ -59,6 +60,34 @@ yarn add @thi.ng/arrays
- [swap()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swap.ts)
- [swizzle()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swizzle.ts)

### Binary search result predicates

The following predicates can be used to perform predecessor / successor
queries using `binarySearch()`.

- `bsLT()` - Returns index of last item less than search value or -1 if
no such values exist
- `bsLE()` - Similar to `bsLT()`, but for less-than-equals queries
- `bsGT()` - Returns index of first item greater than search value or -1
if no such values exist
- `bsGE()` - Similar to `bsGT()`, but for less-than-equals queries
- `bsEQ()` - Merely syntax sugar, casting any non-found result indices to -1

```ts
const src = [10, 20, 30, 40];

bsLT(binarySearch(src, 25))
// 1

// greater-than queries also require the array length

bsGT(binarySearch(src, 25), src.length)
// 2

bsGT(binarySearch(src, 40), src.length)
// -1
```

## Authors

Karsten Schmidt
Expand Down
28 changes: 28 additions & 0 deletions packages/arrays/README.tpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,34 @@ ${docLink}
- [swap()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swap.ts)
- [swizzle()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swizzle.ts)

### Binary search result predicates

The following predicates can be used to perform predecessor / successor
queries using `binarySearch()`.

- `bsLT()` - Returns index of last item less than search value or -1 if
no such values exist
- `bsLE()` - Similar to `bsLT()`, but for less-than-equals queries
- `bsGT()` - Returns index of first item greater than search value or -1
if no such values exist
- `bsGE()` - Similar to `bsGT()`, but for less-than-equals queries
- `bsEQ()` - Merely syntax sugar, casting any non-found result indices to -1

```ts
const src = [10, 20, 30, 40];

bsLT(binarySearch(src, 25))
// 1

// greater-than queries also require the array length

bsGT(binarySearch(src, 25), src.length)
// 2

bsGT(binarySearch(src, 40), src.length)
// -1
```

## Authors

${authors}
Expand Down
79 changes: 73 additions & 6 deletions packages/arrays/src/binary-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ import { compare, compareNumAsc } from "@thi.ng/compare";
* @param x - search value
* @param key - key function
* @param cmp - comparator
* @param low - min index
* @param high - max index
*/
export const binarySearch = <A, B>(
buf: ArrayLike<A>,
x: A,
key: Fn<A, B> = (x) => <any>x,
cmp: Comparator<B> = compare
cmp: Comparator<B> = compare,
low = 0,
high = buf.length - 1
) => {
const kx = key(x);
let low = 0;
let high = buf.length - 1;
while (low <= high) {
const mid = (low + high) >>> 1;
const c = cmp(key(buf[mid]), kx);
Expand All @@ -60,14 +62,16 @@ export const binarySearch = <A, B>(
* @param buf - array
* @param x - search value
* @param cmp - comparator
* @param low - min index
* @param high - max index
*/
export const binarySearchNumeric = (
buf: ArrayLike<number>,
x: number,
cmp: Comparator<number> = compareNumAsc
cmp: Comparator<number> = compareNumAsc,
low = 0,
high = buf.length - 1
) => {
let low = 0;
let high = buf.length - 1;
while (low <= high) {
const mid = (low + high) >>> 1;
const c = cmp(buf[mid], x);
Expand All @@ -81,3 +85,66 @@ export const binarySearchNumeric = (
}
return -low - 1;
};

/**
* {@link binarySearch} result index classifier for predecessor queries.
* Returns index of last item less than search value or -1 if no such
* values exist.
*
* @example
* ```ts
* bsLT(binarySearch([10, 20, 30, 40], 25))
* // 1
* ```
*
* @param i - binarySearch result index
*/
export const bsLT = (i: number) => (i < 0 ? -i - 2 : i - 1);

/**
* Similar to {@link bsLT}, but for less-than-equals queries.
*
* @param i - binarySearch result index
*/
export const bsLE = (i: number) => (i < 0 ? -i - 2 : i);

/**
* {@link binarySearch} result index classifier for successor queries.
* Returns index of first item greater than search value or -1 if no
* such values exist.
*
* @example
* ```ts
* src = [10, 20, 30, 40];
*
* bsGT(binarySearch(src, 25), src.length)
* // 2
*
* bsGT(binarySearch(src, 40), src.length)
* // -1
* ```
*
* @param i - binarySearch result index
* @param n - array length
*/
export const bsGT = (i: number, n: number) => (
(i = i < 0 ? -i - 1 : i + 1), i < n ? i : -1
);

/**
* Similar to {@link bsGT}, but for greater-than-equals queries.
*
* @param i - binarySearch result index
* @param n - array length
*/
export const bsGE = (i: number, n: number) => (
(i = i < 0 ? -i - 1 : i), i < n ? i : -1
);

/**
* {@link binarySearch} result index classifier for equals queries.
* Merely syntax sugar, casting any non-found result indices to -1.
*
* @param i - binarySearch result index
*/
export const bsEQ = (i: number) => (i < 0 ? -1 : i);
37 changes: 37 additions & 0 deletions packages/arrays/test/binary-search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FnO } from "@thi.ng/api";
import * as assert from "assert";
import {
binarySearch,
bsEQ,
bsGE,
bsGT,
bsLE,
bsLT
} from "../src";

const src = [10, 20, 30, 40];
const tests = [5, 10, 15, 20, 25, 45];

const checkPred = (pred: FnO<number, number>, res: number[]) => {
for (let i = tests.length; --i >= 0; ) {
assert.equal(pred(binarySearch(src, tests[i]), src.length), res[i]);
}
};

describe("binarySearch", () => {
it("lt", () => {
checkPred(bsLT, [-1, -1, 0, 0, 1, 3]);
});
it("le", () => {
checkPred(bsLE, [-1, 0, 0, 1, 1, 3]);
});
it("gt", () => {
checkPred(bsGT, [0, 1, 1, 2, 2, -1]);
});
it("ge", () => {
checkPred(bsGE, [0, 0, 1, 1, 2, -1]);
});
it("eq", () => {
checkPred(bsEQ, [-1, 0, -1, 1, -1, -1]);
});
});

0 comments on commit b8f421e

Please sign in to comment.