Skip to content

Commit 4e126a7

Browse files
authored
fix comparator bisector (d3#250)
* fix comparator bisector * fix comparator bisector, again
1 parent 14efbe0 commit 4e126a7

File tree

3 files changed

+57
-4
lines changed

3 files changed

+57
-4
lines changed

src/bisector.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import ascending from "./ascending.js";
2+
import descending from "./descending.js";
23

34
export default function bisector(f) {
4-
let delta = f;
5-
let compare1 = f;
6-
let compare2 = f;
5+
let compare1, compare2, delta;
76

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

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

4351
return {left, center, right};
4452
}
53+
54+
function zero() {
55+
return 0;
56+
}

test/bisect-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ it("bisectLeft(array, value, lo, hi) keeps non-comparable values to the right",
151151
assert.strictEqual(bisectLeft(values, NaN), 5);
152152
});
153153

154+
it("bisectLeft(array, value, lo, hi) keeps comparable values to the left", () => {
155+
const values = [null, undefined, NaN];
156+
assert.strictEqual(bisectLeft(values, 1), 0);
157+
assert.strictEqual(bisectLeft(values, 2), 0);
158+
});
159+
154160
it("bisectRight(array, value, lo, hi) keeps non-comparable values to the right", () => {
155161
const values = [1, 2, null, undefined];
156162
assert.strictEqual(bisectRight(values, 1), 1);

test/bisector-test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,37 @@ it("bisector(comparator).right(array, value) handles large sparse d3", () => {
151151
assert.strictEqual(bisectRight(boxes, box(6), i - 5, i), i - 0);
152152
});
153153

154+
it("bisector(comparator).left(array, value) supports an asymmetric (object, value) comparator", () => {
155+
const boxes = [1, 2, 3].map(box);
156+
const bisectLeft = bisector(ascendingBoxValue).left;
157+
assert.strictEqual(bisectLeft(boxes, 1), 0);
158+
assert.strictEqual(bisectLeft(boxes, 2), 1);
159+
assert.strictEqual(bisectLeft(boxes, 3), 2);
160+
});
161+
162+
// This is not possible because the bisector has no way of knowing whether the
163+
// given comparator is symmetric or asymmetric, and if the comparator is
164+
// asymmetric it cannot be used to test the search value for orderability.
165+
it.skip("bisector(comparator).left(array, value) keeps non-comparable values to the right", () => {
166+
const boxes = [1, 2, null, undefined, NaN].map(box);
167+
const bisectLeft = bisector(ascendingBox).left;
168+
assert.strictEqual(bisectLeft(boxes, box(1)), 0);
169+
assert.strictEqual(bisectLeft(boxes, box(2)), 1);
170+
assert.strictEqual(bisectLeft(boxes, box(null)), 5);
171+
assert.strictEqual(bisectLeft(boxes, box(undefined)), 5);
172+
assert.strictEqual(bisectLeft(boxes, box(NaN)), 5);
173+
});
174+
175+
it("bisector(accessor).left(array, value) keeps non-comparable values to the right", () => {
176+
const boxes = [1, 2, null, undefined, NaN].map(box);
177+
const bisectLeft = bisector(unbox).left;
178+
assert.strictEqual(bisectLeft(boxes, 1), 0);
179+
assert.strictEqual(bisectLeft(boxes, 2), 1);
180+
assert.strictEqual(bisectLeft(boxes, null), 5);
181+
assert.strictEqual(bisectLeft(boxes, undefined), 5);
182+
assert.strictEqual(bisectLeft(boxes, NaN), 5);
183+
});
184+
154185
it("bisector(accessor).left(array, value) returns the index of an exact match", () => {
155186
const boxes = [1, 2, 3].map(box);
156187
const bisectLeft = bisector(unbox).left;
@@ -346,3 +377,7 @@ function unbox(box) {
346377
function ascendingBox(a, b) {
347378
return ascending(a.value, b.value);
348379
}
380+
381+
function ascendingBoxValue(a, value) {
382+
return ascending(a.value, value);
383+
}

0 commit comments

Comments
 (0)