diff --git a/README.md b/README.md
index 3a1b0dad..826f1425 100644
--- a/README.md
+++ b/README.md
@@ -417,6 +417,10 @@ This produces:
Equivalent to [group](#group), but returns nested arrays instead of nested maps.
+# d3.flatGroup(iterable, ...keys) · [Source](https://github.com/d3/d3-array/blob/master/src/group.js), [Examples](https://observablehq.com/@d3/d3-flatgroup)
+
+Equivalent to [group](#group), but returns a flat array of [*key0*, *key1*, …, *values*] instead of nested maps.
+
# d3.index(iterable, ...keys) · [Source](https://github.com/d3/d3-array/blob/master/src/group.js), [Examples](https://observablehq.com/@d3/d3-group)
Equivalent to [group](#group) but returns a unique value per compound key instead of an array, throwing if the key is not unique.
@@ -507,6 +511,10 @@ To convert a Map to an Array, use [Array.from](https://developer.mozilla.org/en-
Equivalent to [rollup](#rollup), but returns nested arrays instead of nested maps.
+# d3.flatRollup(iterable, ...keys) · [Source](https://github.com/d3/d3-array/blob/master/src/group.js), [Examples](https://observablehq.com/@d3/d3-flatgroup)
+
+Equivalent to [rollup](#rollup), but returns a flat array of [*key0*, *key1*, …, *value*] instead of nested maps.
+
# d3.groupSort(iterable, comparator, key) · [Source](https://github.com/d3/d3-array/blob/master/src/groupSort.js), [Examples](https://observablehq.com/@d3/d3-groupsort)
# d3.groupSort(iterable, accessor, key)
diff --git a/src/group.js b/src/group.js
index 77ae4dc7..0c30d7e9 100644
--- a/src/group.js
+++ b/src/group.js
@@ -9,6 +9,21 @@ export function groups(values, ...keys) {
return nest(values, Array.from, identity, keys);
}
+function flatten(groups, keys) {
+ for (let i = 1, n = keys.length; i < n; ++i) {
+ groups = groups.flatMap(g => g.pop().map(([key, value]) => [...g, key, value]));
+ }
+ return groups;
+}
+
+export function flatGroup(values, ...keys) {
+ return flatten(groups(values, ...keys), keys);
+}
+
+export function flatRollup(values, reduce, ...keys) {
+ return flatten(rollups(values, reduce, ...keys), keys);
+}
+
export function rollup(values, reduce, ...keys) {
return nest(values, identity, reduce, keys);
}
diff --git a/src/index.js b/src/index.js
index 44c5ac66..209d7b2f 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 {Adder, fsum, fcumsum} from "./fsum.js";
-export {default as group, groups, index, indexes, rollup, rollups} from "./group.js";
+export {default as group, flatGroup, flatRollup, groups, index, indexes, rollup, rollups} from "./group.js";
export {default as groupSort} from "./groupSort.js";
export {default as bin, default as histogram} from "./bin.js"; // Deprecated; use bin.
export {default as thresholdFreedmanDiaconis} from "./threshold/freedmanDiaconis.js";
diff --git a/test/flatGroup-test.js b/test/flatGroup-test.js
new file mode 100644
index 00000000..959d3771
--- /dev/null
+++ b/test/flatGroup-test.js
@@ -0,0 +1,21 @@
+import assert from "assert";
+import {flatGroup} from "../src/index.js";
+
+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"}
+];
+
+it("flatGroup(data, accessor, accessor) returns the expected array", () => {
+ assert.deepStrictEqual(
+ flatGroup(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'}]],
+ ['stacy', '34.05', [{name: 'stacy', amount: '34.05', date: '01/04/2016'}]]
+ ]
+ );
+});
diff --git a/test/flatRollup-test.js b/test/flatRollup-test.js
new file mode 100644
index 00000000..04b05496
--- /dev/null
+++ b/test/flatRollup-test.js
@@ -0,0 +1,32 @@
+import assert from "assert";
+import {flatRollup} from "../src/index.js";
+
+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"}
+];
+
+it("flatRollup(data, reduce, accessor) returns the expected array", () => {
+ assert.deepStrictEqual(
+ flatRollup(data, v => v.length, d => d.name),
+ [
+ ['jim', 1],
+ ['carl', 1],
+ ['stacy', 2]
+ ]
+ );
+});
+
+it("flatRollup(data, reduce, accessor, accessor) returns the expected array", () => {
+ assert.deepStrictEqual(
+ flatRollup(data, v => v.length, d => d.name, d => d.amount),
+ [
+ ['jim', '34.0', 1],
+ ['carl', '120.11', 1],
+ ['stacy', '12.01', 1],
+ ['stacy', '34.05', 1]
+ ]
+ );
+});