This works, but I was wondering if there was a better way than creating a string with a
and b
and later splitting it:
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
d3.rollups(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => `${d.a} ${d.b}`
)
.map(([key, values]) => {
const [a, b] = key.split(' ');
return {a, b, ...values};
});
// OUTPUT
// [
// {a: "10", b: "20", c: [30, 31], d: [40, 41]},
// {a: "12", b: "22", c: [32], d: [42]}
// ]
This works, but I was wondering if there was a better way than creating a string with a
and b
and later splitting it:
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
d3.rollups(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => `${d.a} ${d.b}`
)
.map(([key, values]) => {
const [a, b] = key.split(' ');
return {a, b, ...values};
});
// OUTPUT
// [
// {a: "10", b: "20", c: [30, 31], d: [40, 41]},
// {a: "12", b: "22", c: [32], d: [42]}
// ]
Share
Improve this question
edited Feb 17, 2021 at 0:14
Gerardo Furtado
102k9 gold badges128 silver badges177 bronze badges
asked Feb 16, 2021 at 12:13
nachocabnachocab
14.5k21 gold badges104 silver badges158 bronze badges
3
- 2 Good question! Looking simple at first, it turns out to be quite tricky. I doubt there is a better way to use a bined key like your approach. You could do a nested rollup but then you'd have to loop through the resulting arrays to flatten them to the desired output. – altocumulus Commented Feb 16, 2021 at 16:04
- Borderline duplicate of "Grouping objects in an array by multiple keys". Although we might be better off closing that one as a duplicate of this question given the quality of answers. Also, hitting Google with something like "javascript group array of objects by multiple keys" yields a plethora of possible approaches, both D3 as well as VanillaJS. – altocumulus Commented Feb 17, 2021 at 16:23
- Additionally, you might be interested in the performance results I posted as part of my ment on Robin's answer. – altocumulus Commented Feb 17, 2021 at 16:25
3 Answers
Reset to default 4With d3 v7 released, there is now a better way to do this using the new d3.flatRollup
.
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
const result = d3.flatRollup(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => d.a,
d => d.b
);
console.log(result);
const flattened = result.map(([a, b, values]) => ({a, b, ...values}));
console.log(flattened);
<script src="https://cdn.jsdelivr/npm/[email protected]/dist/d3-array.min.js"></script>
As you already know d3.rollups()
will create nested arrays if you have more than one key:
If more than one key is specified, a nested Map [or array] is returned.
Therefore, as d3.rollups
doesn't fit your needs, I believe it's easier to create a plain JavaScript function (I'm aware of "using D3" in your title, but even in a D3 code nothing forbids us of writing plain JS solutions where D3 has none).
In the following example I'm purposefully writing a verbose function (with ments) so each part of it is clear, avoiding more plex features which could make it substantially short (but more cryptic). In this function I'm using reduce
, so the data array is looped only once. myKeys
is the array of keys you'll use to rollup.
Here is the function and the ments:
function groupedRollup(myArray, myKeys) {
return myArray.reduce((a, c) => {
//Find the object in the acc with all 'myKeys' equivalent to the current
const foundObject = a.find(e => myKeys.every(f => e[f] === c[f]));
//if found, push the value for each key which is not in 'myKeys'
if (foundObject) {
for (let key in foundObject) {
if (!keys.includes(key)) foundObject[key].push(c[key]);
};
//if not found, push the current object with all non 'myKeys' keys as arrays
} else {
const copiedObject = Object.assign({}, c);//avoids mutation
for (let key in copiedObject) {
if (!keys.includes(key)) copiedObject[key] = [copiedObject[key]];
};
a.push(copiedObject);
};
return a;
}, [])
};
Here is the demo:
const data = [{
a: 10,
b: 20,
c: 30,
d: 40
},
{
a: 10,
b: 20,
c: 31,
d: 41
},
{
a: 12,
b: 22,
c: 32,
d: 42
}
];
const keys = ["a", "b"];
console.log(groupedRollup(data, keys))
function groupedRollup(myArray, myKeys) {
return myArray.reduce((a, c) => {
const foundObject = a.find(e => myKeys.every(f => e[f] === c[f]));
if (foundObject) {
for (let key in foundObject) {
if (!keys.includes(key)) foundObject[key].push(c[key]);
};
} else {
const copiedObject = Object.assign({}, c);
for (let key in copiedObject) {
if (!keys.includes(key)) copiedObject[key] = [copiedObject[key]];
};
a.push(copiedObject);
};
return a;
}, [])
};
And here is a demo with a more plex data:
const data = [{
a: 10,
b: 20,
c: 30,
d: 40,
e: 5,
f: 19
},
{
a: 10,
b: 55,
c: 37,
d: 40,
e: 5,
f: 19
},
{
a: 10,
b: 20,
c: 31,
d: 48,
e: 5,
f: 18
},
{
a: 80,
b: 20,
c: 31,
d: 48,
e: 5,
f: 18
},
{
a: 1,
b: 2,
c: 3,
d: 8,
e: 5,
f: 9
},
{
a: 10,
b: 88,
c: 44,
d: 33,
e: 5,
f: 19
}
];
const keys = ["a", "e", "f"];
console.log(groupedRollup(data, keys))
function groupedRollup(myArray, myKeys) {
return myArray.reduce((a, c) => {
const foundObject = a.find(e => myKeys.every(f => e[f] === c[f]));
if (foundObject) {
for (let key in foundObject) {
if (!keys.includes(key)) foundObject[key].push(c[key]);
};
} else {
const copiedObject = Object.assign({}, c);
for (let key in copiedObject) {
if (!keys.includes(key)) copiedObject[key] = [copiedObject[key]];
};
a.push(copiedObject);
};
return a;
}, [])
};
Finally, pay attention that this function will push duplicated values (in the above example d: [40, 40, 33]
). If that's not what you want then just check for duplicates.
The approach below allows you to remove the split
, but does not prevent the need to create a string for the pound key. In this case, using JSON.stringify({a: d.a, b: d.b})
instead of ${d.a} ${d.b}
, allows for the map
to return an object where the c
and d
properties can be assigned to the parse
of the key.
This preserves some of the 'd3-ishness' of your question and the utility of rollups
to deal with the creation of the arrays for c
and d
.
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
const groups = d3.rollups(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => JSON.stringify({a: d.a, b: d.b}) // pare with `${d.a} ${d.b}`
).map(arr => Object.assign(JSON.parse(arr[0]), arr[1]));
console.log(groups);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare./ajax/libs/d3/6.5.0/d3.min.js"></script>
The approach can acmodate the extensibility of @Gerado Furtado's answer, but I fear it's getting a little hectic:
const data = [
{a: 10, b: 20, c: 30, d: 40, e: 5, f: 19},
{a: 10, b: 55, c: 37, d: 40, e: 5, f: 19},
{a: 10, b: 20, c: 31, d: 48, e: 5, f: 18},
{a: 80, b: 20, c: 31, d: 48, e: 5, f: 18},
{a: 1, b: 2, c: 3, d: 8, e: 5, f: 9},
{a: 10, b: 88, c: 44, d: 33, e: 5, f: 19}
];
const keys = ["a", "e", "f"];
const groupedRollup = (data, keys) => {
const others = Object.keys(data[0])
.filter(k => !keys.includes(k)); // finds b, c, d as not part of pound key
return d3.rollups(
data,
x => Object.assign(
{},
...others.map(k => {
return {[k]: x.map(d => d[k])} // dynamically create reducer
})
),
d => JSON.stringify(
Object.assign(
{},
...keys.map(k => {
return {[k]: d[k]} // dynamically add keys
})
)
) // and stringify for pound key
).map(arr => Object.fromEntries( // sorting the output object
Object.entries( // keys in alpha order
Object.assign(JSON.parse(arr[0]), arr[1])).sort() // same approach
)
);
}
console.log(groupedRollup(data, keys));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare./ajax/libs/d3/6.5.0/d3.min.js"></script>
There's some interesting talk about the introduction of use of InternMap
in rollups
and the associated functions - but I don't see either that it's ready, or that it's useful for what you are trying to do.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742416557a4439856.html
评论列表(0条)