Skip to content

Commit 5b78836

Browse files
Filmbostock
authored andcommitted
Add bisector.center.
Fixes #138.
1 parent 1f92b6c commit 5b78836

File tree

4 files changed

+99
-26
lines changed

4 files changed

+99
-26
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ Equivalent to [bisectLeft](#bisectLeft), but uses this bisector’s associated c
258258

259259
Equivalent to [bisectRight](#bisectRight), but uses this bisector’s associated comparator.
260260

261+
<a name="bisector_center" href="#bisector_center">#</a> <i>bisector</i>.<b>center</b>(<i>array</i>, <i>x</i>[, <i>lo</i>[, <i>hi</i>]]) · [Source](https://github.com/d3/d3-array/blob/master/src/bisector.js)
262+
263+
Returns the index of the closest value to *x* in the given sorted *array*. This expects that the bisector’s associated accessor returns a quantitative value, or that the bisector’s associated comparator returns a signed distance; otherwise, this method is equivalent to *bisector*.left.
264+
261265
<a name="quickselect" href="#quickselect">#</a> d3.<b>quickselect</b>(<i>array</i>, <i>k</i>, <i>left</i> = 0, <i>right</i> = <i>array</i>.length - 1, <i>compare</i> = ascending) · [Source](https://github.com/d3/d3-array/blob/master/src/quickselect.js), [Examples](https://observablehq.com/@d3/d3-quickselect)
262266

263267
See [mourner/quickselect](https://github.com/mourner/quickselect/blob/master/README.md).

src/bisector.js

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
11
import ascending from "./ascending.js";
22

3-
export default function(compare) {
4-
if (compare.length === 1) compare = ascendingComparator(compare);
5-
return {
6-
left: function(a, x, lo, hi) {
7-
if (lo == null) lo = 0;
8-
if (hi == null) hi = a.length;
9-
while (lo < hi) {
10-
var mid = lo + hi >>> 1;
11-
if (compare(a[mid], x) < 0) lo = mid + 1;
12-
else hi = mid;
13-
}
14-
return lo;
15-
},
16-
right: function(a, x, lo, hi) {
17-
if (lo == null) lo = 0;
18-
if (hi == null) hi = a.length;
19-
while (lo < hi) {
20-
var mid = lo + hi >>> 1;
21-
if (compare(a[mid], x) > 0) hi = mid;
22-
else lo = mid + 1;
23-
}
24-
return lo;
3+
export default function(f) {
4+
let delta = f;
5+
let compare = f;
6+
7+
if (f.length === 1) {
8+
delta = (d, x) => f(d) - x;
9+
compare = ascendingComparator(f);
10+
}
11+
12+
function left(a, x, lo, hi) {
13+
if (lo == null) lo = 0;
14+
if (hi == null) hi = a.length;
15+
while (lo < hi) {
16+
const mid = (lo + hi) >>> 1;
17+
if (compare(a[mid], x) < 0) lo = mid + 1;
18+
else hi = mid;
19+
}
20+
return lo;
21+
}
22+
23+
function right(a, x, lo, hi) {
24+
if (lo == null) lo = 0;
25+
if (hi == null) hi = a.length;
26+
while (lo < hi) {
27+
const mid = (lo + hi) >>> 1;
28+
if (compare(a[mid], x) > 0) hi = mid;
29+
else lo = mid + 1;
2530
}
26-
};
31+
return lo;
32+
}
33+
34+
function center(a, x, lo, hi) {
35+
if (lo == null) lo = 0;
36+
if (hi == null) hi = a.length;
37+
const i = left(a, x, lo, hi);
38+
return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
39+
}
40+
41+
return {left, center, right};
2742
}
2843

2944
function ascendingComparator(f) {
30-
return function(d, x) {
31-
return ascending(f(d), x);
32-
};
45+
return (d, x) => ascending(f(d), x);
3346
}

test/bisect-test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ tape("bisectLeft(array, value) returns the index of the first match", (test) =>
1919
test.equal(d3.bisectLeft(numbers, 3), 3);
2020
});
2121

22+
tape("bisectLeft(empty, value) returns zero", (test) => {
23+
test.equal(d3.bisectLeft([], 1), 0);
24+
});
25+
2226
tape("bisectLeft(array, value) returns the insertion point of a non-exact match", (test) => {
2327
const numbers = [1, 2, 3];
2428
test.equal(d3.bisectLeft(numbers, 0.5), 0);
@@ -87,6 +91,10 @@ tape("bisectRight(array, value) returns the index after the last match", (test)
8791
test.equal(d3.bisectRight(numbers, 3), 4);
8892
});
8993

94+
tape("bisectRight(empty, value) returns zero", (test) => {
95+
test.equal(d3.bisectRight([], 1), 0);
96+
});
97+
9098
tape("bisectRight(array, value) returns the insertion point of a non-exact match", (test) => {
9199
const numbers = [1, 2, 3];
92100
test.equal(d3.bisectRight(numbers, 0.5), 0);

test/bisector-test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ tape("bisector(comparator).left(array, value) returns the index of the first mat
1717
test.equal(bisectLeft(boxes, box(3)), 3);
1818
});
1919

20+
tape("bisector(comparator).left(empty, value) returns zero", (test) => {
21+
test.equal(d3.bisector(() => { throw new Error(); }).left([], 1), 0);
22+
});
23+
2024
tape("bisector(comparator).left(array, value) returns the insertion point of a non-exact match", (test) => {
2125
const boxes = [1, 2, 3].map(box);
2226
const bisectLeft = d3.bisector(ascendingBox).left;
@@ -92,6 +96,10 @@ tape("bisector(comparator).right(array, value) returns the index after the last
9296
test.equal(bisectRight(boxes, box(3)), 4);
9397
});
9498

99+
tape("bisector(comparator).right(empty, value) returns zero", (test) => {
100+
test.equal(d3.bisector(() => { throw new Error(); }).right([], 1), 0);
101+
});
102+
95103
tape("bisector(comparator).right(array, value) returns the insertion point of a non-exact match", (test) => {
96104
const boxes = [1, 2, 3].map(box);
97105
const bisectRight = d3.bisector(ascendingBox).right;
@@ -285,6 +293,46 @@ tape("bisector(accessor).right(array, value) handles large sparse d3", (test) =>
285293
test.equal(bisectRight(boxes, 6, i - 5, i), i - 0);
286294
});
287295

296+
tape("bisector(accessor).center(array, value) returns the closest index", (test) => {
297+
const data = [0, 1, 2, 3, 4];
298+
const bisectCenter = d3.bisector(d => +d).center;
299+
test.equal(bisectCenter(data, 2), 2);
300+
test.equal(bisectCenter(data, 2.2), 2);
301+
test.equal(bisectCenter(data, 2.6), 3);
302+
test.equal(bisectCenter(data, 3), 3);
303+
});
304+
305+
tape("bisector(comparator).center(array, value) returns the closest index", (test) => {
306+
const data = [0, 1, 2, 3, 4];
307+
const bisectCenter = d3.bisector((d, x) => +d - x).center;
308+
test.equal(bisectCenter(data, 2), 2);
309+
test.equal(bisectCenter(data, 2.2), 2);
310+
test.equal(bisectCenter(data, 2.6), 3);
311+
test.equal(bisectCenter(data, 3), 3);
312+
});
313+
314+
tape("bisector(comparator).center(empty, value) returns zero", (test) => {
315+
test.equal(d3.bisector(() => { throw new Error(); }).center([], 1), 0);
316+
});
317+
318+
tape("bisector(ascending).center(array, value) returns the left value", (test) => {
319+
const data = [0, 1, 2, 3, 4];
320+
const bisectCenter = d3.bisector(d3.ascending).center;
321+
test.equal(bisectCenter(data, 2.0), 2);
322+
test.equal(bisectCenter(data, 2.2), 3);
323+
test.equal(bisectCenter(data, 2.6), 3);
324+
test.equal(bisectCenter(data, 3.0), 3);
325+
});
326+
327+
tape("bisector(ordinalAccessor).center(array, value) returns the left value", (test) => {
328+
const data = ["aa", "bb", "cc", "dd", "ee"];
329+
const bisectCenter = d3.bisector(d => d).center;
330+
test.equal(bisectCenter(data, "cc"), 2);
331+
test.equal(bisectCenter(data, "ce"), 3);
332+
test.equal(bisectCenter(data, "cf"), 3);
333+
test.equal(bisectCenter(data, "dd"), 3);
334+
});
335+
288336
function box(value) {
289337
return {value: value};
290338
}

0 commit comments

Comments
 (0)