Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix comparator bisector #250

Merged
merged 2 commits into from Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/bisector.js
@@ -1,14 +1,22 @@
import ascending from "./ascending.js";
import descending from "./descending.js";

export default function bisector(f) {
let delta = f;
let compare1 = f;
let compare2 = f;
let compare1, compare2, delta;

// If an accessor is specified, promote it to a comparator. In this case we
// can test whether the search value is (self-) comparable. We can’t do this
// for a comparator (except for specific, known comparators) because we can’t
// tell if the comparator is symmetric, and an asymmetric comparator can’t be
// used to test whether a single value is comparable.
if (f.length !== 2) {
delta = (d, x) => f(d) - x;
compare1 = ascending;
compare2 = (d, x) => ascending(f(d), x);
delta = (d, x) => f(d) - x;
} else {
compare1 = f === ascending || f === descending ? f : zero;
compare2 = f;
delta = f;
}

function left(a, x, lo = 0, hi = a.length) {
Expand Down Expand Up @@ -42,3 +50,7 @@ export default function bisector(f) {

return {left, center, right};
}

function zero() {
return 0;
}
6 changes: 6 additions & 0 deletions test/bisect-test.js
Expand Up @@ -151,6 +151,12 @@ it("bisectLeft(array, value, lo, hi) keeps non-comparable values to the right",
assert.strictEqual(bisectLeft(values, NaN), 5);
});

it("bisectLeft(array, value, lo, hi) keeps comparable values to the left", () => {
const values = [null, undefined, NaN];
assert.strictEqual(bisectLeft(values, 1), 0);
assert.strictEqual(bisectLeft(values, 2), 0);
});

it("bisectRight(array, value, lo, hi) keeps non-comparable values to the right", () => {
const values = [1, 2, null, undefined];
assert.strictEqual(bisectRight(values, 1), 1);
Expand Down
35 changes: 35 additions & 0 deletions test/bisector-test.js
Expand Up @@ -151,6 +151,37 @@ it("bisector(comparator).right(array, value) handles large sparse d3", () => {
assert.strictEqual(bisectRight(boxes, box(6), i - 5, i), i - 0);
});

it("bisector(comparator).left(array, value) supports an asymmetric (object, value) comparator", () => {
const boxes = [1, 2, 3].map(box);
const bisectLeft = bisector(ascendingBoxValue).left;
assert.strictEqual(bisectLeft(boxes, 1), 0);
assert.strictEqual(bisectLeft(boxes, 2), 1);
assert.strictEqual(bisectLeft(boxes, 3), 2);
});

// This is not possible because the bisector has no way of knowing whether the
// given comparator is symmetric or asymmetric, and if the comparator is
// asymmetric it cannot be used to test the search value for orderability.
it.skip("bisector(comparator).left(array, value) keeps non-comparable values to the right", () => {
const boxes = [1, 2, null, undefined, NaN].map(box);
const bisectLeft = bisector(ascendingBox).left;
assert.strictEqual(bisectLeft(boxes, box(1)), 0);
assert.strictEqual(bisectLeft(boxes, box(2)), 1);
assert.strictEqual(bisectLeft(boxes, box(null)), 5);
assert.strictEqual(bisectLeft(boxes, box(undefined)), 5);
assert.strictEqual(bisectLeft(boxes, box(NaN)), 5);
});

it("bisector(accessor).left(array, value) keeps non-comparable values to the right", () => {
const boxes = [1, 2, null, undefined, NaN].map(box);
const bisectLeft = bisector(unbox).left;
assert.strictEqual(bisectLeft(boxes, 1), 0);
assert.strictEqual(bisectLeft(boxes, 2), 1);
assert.strictEqual(bisectLeft(boxes, null), 5);
assert.strictEqual(bisectLeft(boxes, undefined), 5);
assert.strictEqual(bisectLeft(boxes, NaN), 5);
});

it("bisector(accessor).left(array, value) returns the index of an exact match", () => {
const boxes = [1, 2, 3].map(box);
const bisectLeft = bisector(unbox).left;
Expand Down Expand Up @@ -346,3 +377,7 @@ function unbox(box) {
function ascendingBox(a, b) {
return ascending(a.value, b.value);
}

function ascendingBoxValue(a, value) {
return ascending(a.value, value);
}