From 1f92b6c1e458112b5ebf94500e857406c698b1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 25 Jun 2020 10:21:31 +0200 Subject: [PATCH] d3.index, d3.indexes closes #136 --- README.md | 33 +++++++++++++++++++++++ src/group.js | 13 +++++++++ src/index.js | 2 +- test/index-test.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test/index-test.js diff --git a/README.md b/README.md index b158c1c5..7e66efbe 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,39 @@ In the near future, [*selection*.data](https://github.com/d3/d3-selection/blob/m Equivalent to [group](#group), but returns nested arrays instead of nested maps. +# d3.index(iterable, ...keys) · [Source](https://github.com/d3/d3-array/blob/master/src/group.js) + +Equivalent to [group](#group) but returns a unique value per compound key instead of an array, throwing if the key is not unique. + +For example, given the data defined above, + +```js +d3.index(data, d => d.amount) +``` + +returns + +```js +Map(4) { + "34.0" => Object {name: "jim", amount: "34.0", date: "11/12/2015"} + "120.11" => Object {name: "carl", amount: "120.11", date: "11/12/2015"} + "12.01" => Object {name: "stacy", amount: "12.01", date: "01/04/2016"} + "34.05" => Object {name: "stacy", amount: "34.05", date: "01/04/2016"} +} +``` + +On the other hand, + +```js +d3.index(data, d => d.name) +``` + +throws an error because two objects share the same name. + +# d3.indexes(iterable, ...keys) · [Source](https://github.com/d3/d3-array/blob/master/src/group.js) + +Equivalent to [index](#index), but returns nested arrays instead of nested maps. + # d3.rollup(iterable, reduce, ...keys) · [Source](https://github.com/d3/d3-array/blob/master/src/group.js), [Examples](https://observablehq.com/@d3/d3-group-d3-rollup) [Groups](#group) and reduces the specified *iterable* of values into a Map from *key* to value. For example, given some data: diff --git a/src/group.js b/src/group.js index 97cc57e6..57d3a55b 100644 --- a/src/group.js +++ b/src/group.js @@ -16,6 +16,19 @@ export function rollups(values, reduce, ...keys) { return nest(values, Array.from, reduce, keys); } +export function index(values, ...keys) { + return nest(values, identity, unique, keys); +} + +export function indexes(values, ...keys) { + return nest(values, Array.from, unique, keys); +} + +function unique(values) { + if (values.length !== 1) throw new Error("duplicate key"); + return values[0]; +} + function nest(values, map, reduce, keys) { return (function regroup(values, i) { if (i >= keys.length) return reduce(values); diff --git a/src/index.js b/src/index.js index 8f0cffe0..4937e22d 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ export {default as descending} from "./descending.js"; export {default as deviation} from "./deviation.js"; export {default as extent} from "./extent.js"; export {default as fsum, Adder} from "./fsum.js"; -export {default as group, groups, rollup, rollups} from "./group.js"; +export {default as group, groups, index, indexes, rollup, rollups} from "./group.js"; export {default as bin, default as histogram} from "./bin.js"; // Deprecated; use bin. export {default as thresholdFreedmanDiaconis} from "./threshold/freedmanDiaconis.js"; export {default as thresholdScott} from "./threshold/scott.js"; diff --git a/test/index-test.js b/test/index-test.js new file mode 100644 index 00000000..59a62fbe --- /dev/null +++ b/test/index-test.js @@ -0,0 +1,67 @@ +const tape = require("tape-await"); +const d3 = require("../"); + +const data = [ + {name: "jim", amount: 34.0, date: "11/12/2015"}, + {name: "carl", amount: 120.11, date: "11/12/2015"}, + {name: "stacy", amount: 12.01, date: "01/04/2016"}, + {name: "stacy", amount: 34.05, date: "01/04/2016"} +]; + +tape("indexes(data, ...keys) returns the expected nested arrays", (test) => { + test.deepEqual( + d3.indexes(data, d => d.amount), + [ + [34.0, {name: "jim", amount: 34.0, date: "11/12/2015"}], + [120.11, {name: "carl", amount: 120.11, date: "11/12/2015"}], + [12.01, {name: "stacy", amount: 12.01, date: "01/04/2016"}], + [34.05, {name: "stacy", amount: 34.05, date: "01/04/2016"}] + ] + ); + test.deepEqual( + d3.indexes(data, d => d.name, d => d.amount), + [ + [ + "jim", + [ + [34.0, {name: "jim", amount: 34.0, date: "11/12/2015"}] + ] + ], + [ + "carl", + [ + [120.11, {name: "carl", amount: 120.11, date: "11/12/2015"}] + ] + ], + [ + "stacy", + [ + [12.01, {name: "stacy", amount: 12.01, date: "01/04/2016"}], + [34.05, {name: "stacy", amount: 34.05, date: "01/04/2016"}] + ] + ] + ] + ); +}); + +tape("index(data, ...keys) returns the expected map", (test) => { + test.deepEqual( + entries(d3.index(data, d => d.amount), 1), + d3.indexes(data, d => d.amount) + ); + test.deepEqual( + entries(d3.index(data, d => d.name, d => d.amount), 2), + d3.indexes(data, d => d.name, d => d.amount) + ); +}); + +tape("index(data, ...keys) throws on a non-unique key", (test) => { + test.throws(() => d3.index(data, d => d.name)); + test.throws(() => d3.index(data, d => d.date)); +}); + +function entries(map, depth) { + return depth > 0 + ? Array.from(map, ([k, v]) => [k, entries(v, depth - 1)]) + : map; +}