d3 on how to sum values - javascript

I have the data below:
[
{ id: 0, Department: "Civil", Value: "40000", Title:"Sustainability", ComID: "45", organisation: { City: "New York", ComID: 45, Country: "USA" } },
{ id: 1, Department: "Energy", Value: "82000", Title: "Wind Energy", ComID: "62", organisation: { City: "Paris" , ComID: 62, Country: "France" } },
{ id: 2, Department: "Medical", Value: "67000", Title: "Neuroscience", ComID: "21", organisation: { City: "Berlin", ComID: 21, Country: "Germany" } },
{ id: 3, Department: "Computer", Value: "100000", Title: "Security", ComID: "67", organisation: { City: "Amsterdam", ComID: 67, Country: "Holland" } }
]
the data is an array of about 100 objects like the ones above.
In the data I have organizations of the same country. I want to sum the Value attribute of each Country and put it in a new table.
For example I would like to create:
[ { Name: "each Country", Value: "the summed value" } ]

Edited to respond to updated question
You can use d3.nest() to group objects by a given key (in this case, the country) and then "roll them up" to sum the value across each of the items belonging to a given country. In your case, a one-liner would look something like this:
d3.nest().key(function(d){
return d.organisation.Country; })
.rollup(function(leaves){
return d3.sum(leaves, function(d){
return d.Value;
});
}).entries(data)
.map(function(d){
return { Country: d.key, Value: d.values};
});
Here I'm using d3.sum, passing in an accessor function to specify that you want to sum the Value of each item:
This returns four objects on your example data:
[{"Country":"USA","Value":40000},
{"Country":"France","Value":82000},
{"Country":"Germany","Value":67000},
{"Country":"Holland","Value":100000}]
Javascript converts the strings into numbers for you. Note that you have some typos in your example data that I had to fix, giving the following:
var data = [ { id:0,Department: "Civil",Value : "40000",Title :"Sustainability",ComID : "45", organisation:{ City:"New York",ComID:"45",Country: "USA"}}, { id:1,Department: "Energy",Value : "82000",Title : "Wind Energy",ComID : "62", organisation:{ City:"Paris",ComID:"62",Country: "France"}}, { id:2,Department: "Medical",Value : "67000",Title : "Neuroscience",ComID : "21", organisation:{ City:"Berlin",ComID:"21",Country: "Germany"}}, { id:3,Department: "Computer",Value : "100000",Title : "Security",ComID : "67", organisation:{ City:"Amsterdam",ComID:"67",Country: "Holland"}}]

Since d3-collection has been deprecated in favor of d3.array, we can use d3.rollups to achieve what used to work with d3.nest:
d3
.rollups(
data,
xs => d3.sum(xs, x => x.Value),
d => d.organisation.Country
)
.map(([k, v]) => ({ Country: k, Value: v }))
This:
Applies a d3.rollups:
d3.rollups takes 3 parameters: the input array, a reducing function and a grouping function
d => d.organisation.Country groups items by Country
xs => d3.sum(xs, x => x.Value) reduces grouped values by extracting their
Value and summing them using d3.sum
Formats the rollups' output in order to get the expected output (the map transformation).
var data = [
{ id: 0, Department: "Civil", Value: "40000", Title:"Sustainability", ComID: "45", organisation: { City: "New York", ComID: 45, Country: "USA" } },
{ id: 1, Department: "Energy", Value: "82000", Title: "Wind Energy", ComID: "62", organisation: { City: "Paris" , ComID: 62, Country: "France" } },
{ id: 2, Department: "Medical", Value: "67000", Title: "Neuroscience", ComID: "21", organisation: { City: "Berlin", ComID: 21, Country: "Germany" } },
{ id: 3, Department: "Computer", Value: "100000", Title: "Security", ComID: "67", organisation: { City: "Amsterdam", ComID: 67, Country: "Holland" } }
];
var output =
d3.rollups(
data,
xs => d3.sum(xs, x => x.Value),
d => d.organisation.Country
)
.map(([k, v]) => ({ Country: k, Value: v }))
console.log(output);
<script src="https://d3js.org/d3-array.v2.min.js"></script>

Related

Field value from first Array to get the values from the Second Array in Javascript

I've got two arrays, arr1 has the field and I need to take only the field value from it. The field value should be checked with the arr2 and if it matches the name of the key then need to create as the output below:
let arr1 = [{
field: "name",
value: "some1",
value1: "some2"
},{
field: "job",
value: "some1",
value1: "some2"
},{
field: "from",
value: "some1",
value1: "some3"
}
];
let arr2 = [{
name: "John",
job: "engineer",
address: "abc",
from: "boston",
gender: "male"
},{
name: "Steph",
job: "worker",
address: "uhuh",
from: "uk",
gender: "male"
},{
name: "dor",
job: "farmer",
address: "gdgs",
from: "us",
gender: "female"
}
];
Needed Output:
[{
name: "John",
job: "engineer",
from: "boston"
},{
name: "Steph",
job: "worker",
from: "uk"
},{
name: "Ram",
job: "farmer",
from: "us"
}
];
I tried doing this but I am getting only the last values in arr2.
for(let index = 0; index < arr2.length; index++){
for(let newi = 0; newi < arr1.length; newi++){
newObj = arr1[newi].field;
final[newObj] = arr2[index][newObj]
last.push(final[newObj])
}
console.log(last)
}
Do it like this, using Array.map & Array.reduce
const arr1 = [{ field: "name", value: "some1", value1: "some2"},{ field: "job", value: "some1", value1: "some2"},{ field: "from", value: "some1", value1: "some3"}];
const arr2 = [{ name: "John", job: "engineer", address: "abc", from: "boston", gender: "male"},{ name: "Steph", job: "worker", address: "uhuh", from: "uk", gender: "male"},{ name: "dor", job: "farmer", address: "gdgs", from: "us", gender: "female"}];
const result = arr2.map(item => arr1.reduce((acc, {field}) => {
acc[field] = item[field];
return acc;
}, {}));
console.log(result);
Using map and Object.assign
const arr1 = [{ field: "name", value: "some1", value1: "some2"},{ field: "job", value: "some1", value1: "some2"},{ field: "from", value: "some1", value1: "some3"}];
const arr2 = [{ name: "John", job: "engineer", address: "abc", from: "boston", gender: "male"},{ name: "Steph", job: "worker", address: "uhuh", from: "uk", gender: "male"},{ name: "dor", job: "farmer", address: "gdgs", from: "us", gender: "female"}];
const fields = arr1.map(({ field }) => field);
const output = arr2.map((item) =>
Object.assign({}, ...fields.map((field) => ({ [field]: item[field] })))
);
console.log(output)

Map two separate arrays by matching object keys to values

Been scratching my head on this one for an entire evening with no solution in sight.
Put simply
I am querying two arrays from two separate APIs.
They return data in following format:
API 1
[{
balance: 4444,
age: "18",
gender: "Male",
level: "2",
name: "Joe"
}, {
balance: 3333,
age: "45",
gender: "Male",
level: "3",
name: "Angel"
}
}]
API 2
{
Joe: {
score: 32
},
Angel: {
score: 22
}
}
I need to match the object keys from the second API to the name value of playerInfo from first API so a new array is made that is completely flat like this:
[{
balance: 4444,
age: "18",
gender: "Male",
level: "2",
name: "Joe",
score: 32
}, {
balance: 3333,
age: "45",
gender: "Male",
level: "3",
name: "Angel",
score: 22
}
}]
Here's where I am being stone walled at the moment
var result = []
const matchKeys = (data, data1) => {
let arr = []
arr.push(data1)
data.map(item => {
arr.map(item1 => {
if (item.name === Object.keys(item1)) {
result.push(Object.assign(item, item1))
console.log(result)
}
})
})
}
matchKeys(api1, api2)
I suspect I'm not getting very far because I am not properly accessing my second dataset because there is no index that keeps track of which object I am supposed to pair up with corresponding value in the arrays.
Appreciate any help
You can implement that using Array.map.
const input1 = [{
balance: 4444,
age: "18",
gender: "Male",
level: "2",
name: "Joe"
}, {
balance: 3333,
age: "45",
gender: "Male",
level: "3",
name: "Angel"
}];
const input2 = {
Joe: {
score: 32
},
Angel: {
score: 22
}
}
function matchKeys(arr1, arr2) {
const result = arr1.map((item) => {
if (input2[item.name]) {
return { ...item, ...input2[item.name] };
}
return item;
});
return result;
}
console.log(matchKeys(input1, input2));
you could use the property of the second object as a way to search the right name.
const input1 = [{
balance: 4444,
age: "18",
gender: "Male",
level: "2",
name: "Joe"
}, {
balance: 3333,
age: "45",
gender: "Male",
level: "3",
name: "Angel"
}];
const input2 = {
Joe: {
score: 32
},
Angel: {
score: 22
}
}
const matchKeys = (data, data1) => {
return data.map((item) => ({ ...item, score: data1[item.name] ? data1[item.name].score : 0 }));
}
console.log(matchKeys(input1, input2));
also checked if it has a name and if for some reason it didn't I inserted a default score.

How to merge the property with same key in two object array?

There are two object array, some of them have the same key, I'd like to merge the same key in the first array. I have pasted my code.I used nested loop, but the performance was bad O(n²). Maybe I need another method to enhance performance.(I can't use ES6 for some reason, so I'll appreciate if it is the ES5 method.)
var people = [
{
id: "001",
name: "David",
age: 29
},
{
id: "002",
name: "Lucia",
age: 41
},
{
id: "003",
name: "Steve",
age: 18
}
];
var address = [
{
id: "001",
city: "Barcelona"
},
{
id: "002",
city: "Paris"
},
{
},
{
id: "003",
city: "Tokyo"
},
{
id: "004",
city: "Barcelona"
}
];
My code
people.forEach(function(item) {
var id = item.id;
address.forEach(function(location) {
if (location.id == id) {
item.address = location.address
}
});
});
Result
var people = [
{
id: "001",
name: "David",
age: 29,
city: "Barcelona"
},
{
id: "002",
name: "Lucia",
age: 41,
city: "Paris"
},
{
id: "003",
name: "Steve",
age: 18,
city: "Tokyo"
}
];
The new people array is I preferred.
You could take a Map with all addresses and then map new object with extended properties of the map.
This approach takes all properties of address objects.
var people = [{ id: "001", name: "David", age: 29 }, { id: "002", name: "Lucia", age: 41 }, { id: "003", name: "Steve", age: 18 }],
address = [{ id: "001", city: "Barcelona" }, { id: "002", city: "Paris" }, {}, { id: "003", city: "Tokyo" }, { id: "004", city: "Barcelona" }],
map = new Map(address.map(o => [o.id, o])),
result = people.map(o => Object.assign({}, o, map.get(o.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Make a Map with cities by id, and use it when iterating over the people array to find out the city:
let cities = new Map(address.map(a => [a.id, a.city]));
let people2 = people.map(p => ( {...p, city: cities.get(p.id)} ));
You could use Array#map to iterate over people, and Array#find to find the corresponding address from id within iterations:
const people = [{id: "001",name: "David",age: 29 },{ id: "002", name: "Lucia", age: 41
},{ id: "003", name: "Steve", age: 18 }],
address = [{ id: "001", city: "Barcelona" },{ id: "002", city: "Paris" },{ },{ id: "003", city: "Tokyo" },{ id: "004", city: "Barcelona" }];
console.log(
people.map(p => ({
...p,
...address.find(a => (p.id === a.id))
}))
);
However, that's supposing that the properties' name of address's items are not the same as people's ones.
The code below is not tested but it should work
// create an object to store them
const mergedItems = {};
// merge the 2 arrays so you only map them once (just for shorter code)
people.concat(address).map(entity => {
// add each entity on the object and id as a key
mergedItems[entity.id] = {
// if the key exist, it will merge it with the new entity
...mergedItems[entity.id],
...entity,
}
)
// this is your merged items
// Object.values will convert it from object to array
const finalItems = Object.values(mergedItems);
I used map instead of for loop because it is faster: https://codeburst.io/javascript-map-vs-foreach-f38111822c0f
I have used Object.assign method to add values from address
var people = [{ id: "001", name: "David", age: 29 }, { id: "002", name: "Lucia", age: 41 }, { id: "003", name: "Steve", age: 18 }],
address = [{ id: "001", city: "Barcelona" }, { id: "002", city: "Paris" }, {}, { id: "003", city: "Tokyo" }, { id: "004", city: "Barcelona" }];
people.forEach(function(item,pos){
Object.assign(item,{},address[address.findIndex(o=>o.id == item.id)]);
});
console.log(people);

Javascript - find largest number

I have a project data that has list of projects and associated employees. Each employee object has salary property. What I'm trying to do is to find the employee with maximum salary.
Example of code:
var projects = [
//Object(0)
{
projectName: "Winter",
projectCode: "O0123",
employee: [{
title: "Mr.",
name: "Tom",
id: 1005,
salary: 12345
},
{
title: "Mr.",
name: "Bunny",
id: 1009,
salary: 54321
},
{
title: "Mr.",
name: "Harris",
id: 1010,
salary: 23456
},
]
},
//Object(1)
{
projectName: "Summer",
projectCode: "P10406",
employee: [{
title: "Mr.",
name: "Seth",
id: 1006,
salary: 1234
},
{
title: "Mr.",
name: "Sam",
id: 1011,
salary: 654321
},
],
}
]
console.log(projects.length);
let maxSalary = 0;
for (var i = 0; i < projects.length; i++) {
console.log(projects[i].projectName);
for (var j = 0; j < projects[i].employee.length; j++) {
console.log("\t" + projects[i].employee[j].title + projects[i].employee[j].name + "\n" + "\t" + "Salary: " + projects[i].employee[j].salary);
if (i == 0 && j == 0) {
maxSalary <= projects[i].employee[j].salary;
}
if (projects[i].employee[j].salary > maxSalary) {
maxSalary = projects[i].employee[j].salary;
}
}
}
console.log("Max Salary = " + maxSalary);
Please suggest any inputs.
Simply loop through the different projects and then loop the employees to get the highest value.
var projects = [{
projectName: "Winter",
projectCode: "O0123",
employee: [
{title: "Mr.", name: "Tom", id: 1005, salary: 12345},
{title: "Mr.", name: "Bunny", id: 1009, salary: 54321},
{title: "Mr.", name: "Harris", id: 1010, salary: 23456}
]
},
{
projectName: "Summer",
projectCode: "P10406",
employee: [
{title: "Mr.", name: "Seth", id: 1006, salary: 1234},
{title: "Mr.", name: "Sam", id: 1011, salary: 654321}
]
}
];
var max = 0;
projects.forEach(p => p.employee.forEach(e => e.salary > max && (max = e.salary)));
console.log(max);
If you want to receive the employee, as you mentioned in your question, and not the salary, you could do it basically the same, just returning the whole object:
var projects = [{
projectName: "Winter",
projectCode: "O0123",
employee: [
{title: "Mr.", name: "Tom", id: 1005, salary: 12345},
{title: "Mr.", name: "Bunny", id: 1009, salary: 54321},
{title: "Mr.", name: "Harris", id: 1010, salary: 23456}
]
},
{
projectName: "Summer",
projectCode: "P10406",
employee: [
{title: "Mr.", name: "Seth", id: 1006, salary: 1234},
{title: "Mr.", name: "Sam", id: 1011, salary: 654321}
]
}
];
var max = {salary: 0};
projects.forEach(p => p.employee.forEach(e => e.salary > max.salary && (max = e)));
console.log(max);
I think these examples will give you a basic idea how to do this.
here you have to mix flatMap which will transform your array of array to simple flat array.
then you can use array reduce to pick up the highest salary.
const projects = [
{
employee: [{
salary: 12345
},
{
salary: 54321
},
{
salary: 23456
},
]
},
{
employee: [{
salary: 1234
},
{
salary: 654321
},
]
}
];
const salaries = projects.flatMap(project => {
// here we have array of array, goal is to craft flat array of salary.
return project.employee.map(employe => {
// From each employee, we pickup only the salary.
return employe.salary;
});
});
const highest = salaries.reduce((accumulator, currentValue) => {
// If current salary is highest than the previous, we keep track of it.
if(currentValue > accumulator) {
accumulator = currentValue;
}
return accumulator;
});
You already have the maximum salary, what you need to do is save the index of the employee with the highest salary as soon as you get it. You code should look like this.
var projects = [
//Object(0)
{
projectName: "Winter",
projectCode: "O0123",
employee: [{
title: "Mr.",
name: "Tom",
id: 1005,
salary: 12345
},
{
title: "Mr.",
name: "Bunny",
id: 1009,
salary: 54321
},
{
title: "Mr.",
name: "Harris",
id: 1010,
salary: 23456
},
]
},
//Object(1)
{
projectName: "Summer",
projectCode: "P10406",
employee: [{
title: "Mr.",
name: "Seth",
id: 1006,
salary: 1234
},
{
title: "Mr.",
name: "Sam",
id: 1011,
salary: 654321
},
],
}
]
console.log(projects.length);
let maxSalary = 0;
let employeeWithMaxSalary = {};
for (var i = 0; i < projects.length; i++) {
console.log(projects[i].projectName);
for (var j = 0; j < projects[i].employee.length; j++) {
console.log("\t" + projects[i].employee[j].title + projects[i].employee[j].name + "\n" + "\t" + "Salary: " + projects[i].employee[j].salary);
if (i == 0 && j == 0) {
maxSalary <= projects[i].employee[j].salary;
}
if (projects[i].employee[j].salary > maxSalary) {
maxSalary = projects[i].employee[j].salary;
employeeWithMaxSalary = projects[i].employee[j];
}
}
}
console.log("Max Salary = " + maxSalary);
console.log(employeeWithMaxSalary);
You can improve also improve your code by using ES6 syntax (arrow function and foreach) instead of having nested loops.

JavaScript native groupBy reduce

I am using JavaScript native reduce, however I want to slightly change in the grouping to get my desired result.
I have an array as follows:
const people = [
{name: "John", age: 23, city: "Seattle", state: "WA"},
{name: "Mark", age: 25, city: "Houston", state: "TX"},
{name: "Luke", age: 26, city: "Seattle", state: "WA"},
{name: "Paul", age: 28, city: "Portland", state: "OR"},
{name: "Matt", age: 21, city: "Oakland", state: "CA"},
{name: "Sam", age: 24, city: "Oakland", state: "CA"}
]
I want to group it and change it to this:
const arranged = [
{
city: "Seattle",
state: "WA",
persons: [
{ name: "John", age: 23 },
{name: "Luke", age: 26}
]
},
{
city: "Houston",
state: "TX",
persons: [
{name: "Mark", age: 25}
]
},
{
city: "Portland",
state: "OR",
persons : [
{name: "Paul", age: 28}
]
},
{
city: "Oakland",
state: "CA",
persons: [
{name: "Matt", age: 21},
{name: "Sam", age: 24}
]
}
]
You could use a Map and a stringified object as key for grouping.
Later render the wanted array with objects of the keys and the grouped persons.
var people = [{ name: "John", age: 23, city: "Seattle", state: "WA" }, { name: "Mark", age: 25, city: "Houston", state: "TX" }, { name: "Luke", age: 26, city: "Seattle", state: "WA" }, { name: "Paul", age: 28, city: "Portland", state: "OR" }, { name: "Matt", age: 21, city: "Oakland", state: "CA" }, { name: "Sam", age: 24, city: "Oakland", state: "CA" }],
arranged = Array.from(
people.reduce((m, o) => {
var key = JSON.stringify(Object.assign(...['city', 'state'].map(k => ({ [k]: o[k] }))));
return m.set(key, (m.get(key) || []).concat({ name: o.name, age: o.age }));
}, new Map),
([key, persons]) => Object.assign(JSON.parse(key), { persons })
);
console.log(arranged);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try this. I use Array.prototype.forEach and Array.prototype.push
const people = [
{name: "John", age: 23, city: "Seattle", state: "WA"},
{name: "Mark", age: 25, city: "Houston", state: "TX"},
{name: "Luke", age: 26, city: "Seattle", state: "WA"},
{name: "Paul", age: 28, city: "Portland", state: "OR"},
{name: "Matt", age: 21, city: "Oakland", state: "CA"},
{name: "Sam", age: 24, city: "Oakland", state: "CA"}
];
var arranged=[];
people.forEach(function(e){
var exist=false;
arranged.forEach(function(e1){
if(e1.state===e.state){
exist=true;
e1.persons.push({name:e.name,age:e.age});
}
});
if(!exist){
arranged.push({state:e.state,city:e.city,persons:[{name:e.name,age:e.age}]});
}
});
console.log(arranged);
This is not a trivial problem. You first have to define what constitutes a grouping, and you also have to define how like terms will be combined. You problem is exacerbated by the need to group by a non-primitive value: city and state. Ie, we can't just group based on city alone; more than half the states in the US have a city named Oakland. Other answers solve this by serializing the city and state in a string, but I will show you a more generic solution that works for compound data of any type.
This is tagged with functional programming, so I'll start with a module for separating our subtasks
const DeepMap =
{ has: (map, [ k, ...ks ]) =>
ks.length === 0
? map.has (k)
: map.has (k)
? DeepMap.has (map.get (k), ks)
: false
, set: (map, [ k, ...ks ], value) =>
ks.length === 0
? map.set (k, value)
: map.has (k)
? (DeepMap.set (map.get (k), ks, value), map)
: map.set (k, DeepMap.set (new Map, ks, value))
, get: (map, [ k, ...ks ]) =>
ks.length === 0
? map.get (k)
: map.has (k)
? DeepMap.get (map.get (k), ks)
: undefined
}
Now we can define our generic groupBy function
const identity = x =>
x
const { has, set, get } =
DeepMap
const groupBy = (key = identity, value = identity, xs = []) =>
xs.reduce
((m, x) =>
has (m, key (x))
? set ( m
, key (x)
, [ ...get (m, key (x)), value (x) ]
)
: set ( m
, key (x)
, [ value (x) ]
)
, new Map
)
We use groupBy by specifying a key and value functions – The key function specifies what an item is grouped by, and the value functions specifies the value to be added to the group
const people =
[ { name: "John", age: 23, city: "Seattle", state: "WA" }
, { name: "Mark", age: 25, city: "Houston", state: "TX" }
, { name: "Luke", age: 26, city: "Seattle", state: "WA" }
, { name: "Paul", age: 28, city: "Portland", state: "OR" }
, { name: "Matt", age: 21, city: "Oakland", state: "CA" }
, { name: "Sam", age: 24, city: "Oakland", state: "CA" }
]
const res =
groupBy ( k => [ k.state, k.city ]
, v => ({ name: v.name, age: v.age })
, people
)
console.log (res.get ('WA'))
// Map { 'Seattle' => [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ] }
console.log (res.get ('WA') .get ('Seattle'))
// [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
We can see how this intermediate result would be useful. It provides incredibly efficient lookup thanks to Map. Of course you'll want to iterate thru the deep map in more meaningful ways though. Let's add an entries procedure to our module
const DeepMap =
{ ...
, entries: function* (map, fields = [])
{
const loop = function* (m, path, [ f, ...fields ])
{
if (fields.length === 0)
for (const [ key, value ] of m)
yield [ { ...path, [ f ]: key }, value ]
else
for (const [ key, value ] of m)
yield* loop (value, { ...path, [ f ]: key }, fields)
}
yield* loop (map, {}, fields)
}
}
for (const [ key, value ] of DeepMap.entries (res, [ 'state', 'city' ]))
console.log (key, value)
// { state: 'WA', city: 'Seattle' } [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
// { state: 'TX', city: 'Houston' } [ { name: 'Mark', age: 25 } ]
// { state: 'OR', city: 'Portland' } [ { name: 'Paul', age: 28 } ]
// { state: 'CA', city: 'Oakland' } [ { name: 'Matt', age: 21 }, { name: 'Sam', age: 24 } ]
Now that our deep map is iterable, we can easily produce your desired output using Array.from
const arranged =
Array.from ( entries (res, [ 'state', 'city' ])
, ([ key, persons ]) => ({ ...key, persons })
)
console.log (arranged)
// [
// {
// city: "Seattle",
// state: "WA",
// persons: [
// { name: "John", age: 23 },
// { name: "Luke", age: 26 }
// ]
// },
// {
// city: "Houston",
// state: "TX",
// persons: [
// { name: "Mark", age: 25 }
// ]
// },
// {
// city: "Portland",
// state: "OR",
// persons : [
// { name: "Paul", age: 28 }
// ]
// },
// {
// city: "Oakland",
// state: "CA",
// persons: [
// { name: "Matt", age: 21 },
// { name: "Sam", age: 24 }
// ]
// }
// ]
Program demonstration
const DeepMap =
{ has: (map, [ k, ...ks ]) =>
ks.length === 0
? map.has (k)
: map.has (k)
? DeepMap.has (map.get (k), ks)
: false
, set: (map, [ k, ...ks ], value) =>
ks.length === 0
? map.set (k, value)
: map.has (k)
? (DeepMap.set (map.get (k), ks, value), map)
: map.set (k, DeepMap.set (new Map, ks, value))
, get: (map, [ k, ...ks ]) =>
ks.length === 0
? map.get (k)
: map.has (k)
? DeepMap.get (map.get (k), ks)
: undefined
, entries: function* (map, fields = [])
{
const loop = function* (m, path, [ f, ...fields ])
{
if (fields.length === 0)
for (const [ key, value ] of m)
yield [ { ...path, [ f ]: key }, value ]
else
for (const [ key, value ] of m)
yield* loop (value, { ...path, [ f ]: key }, fields)
}
yield* loop (map, {}, fields)
}
}
const identity = x =>
x
const { has, set, get, entries } =
DeepMap
const groupBy = (key = identity, value = identity, xs = []) =>
xs.reduce
((m, x) =>
has (m, key (x))
? set ( m
, key (x)
, [ ...get (m, key (x)), value (x) ]
)
: set ( m
, key (x)
, [ value (x) ]
)
, new Map
)
const people =
[ { name: "John", age: 23, city: "Seattle", state: "WA" }
, { name: "Mark", age: 25, city: "Houston", state: "TX" }
, { name: "Luke", age: 26, city: "Seattle", state: "WA" }
, { name: "Paul", age: 28, city: "Portland", state: "OR" }
, { name: "Matt", age: 21, city: "Oakland", state: "CA" }
, { name: "Sam", age: 24, city: "Oakland", state: "CA" }
]
const res =
groupBy ( k => [ k.state, k.city ]
, v => ({ name: v.name, age: v.age })
, people
)
for (const [ key, value ] of entries (res, [ 'state', 'city' ]))
console.log (key, value)
// { state: 'WA', city: 'Seattle' } [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
// { state: 'TX', city: 'Houston' } [ { name: 'Mark', age: 25 } ]
// { state: 'OR', city: 'Portland' } [ { name: 'Paul', age: 28 } ]
// { state: 'CA', city: 'Oakland' } [ { name: 'Matt', age: 21 }, { name: 'Sam', age: 24 } ]
const arranged =
Array.from ( entries (res, [ 'state', 'city '])
, ([ key, persons ]) => ({ ...key, persons })
)
console.log ('arranged', arranged)
// arranged [
// {
// city: "Seattle",
// state: "WA",
// persons: [
// { name: "John", age: 23 },
// { name: "Luke", age: 26 }
// ]
// },
// {
// city: "Houston",
// state: "TX",
// persons: [
// { name: "Mark", age: 25 }
// ]
// },
// {
// city: "Portland",
// state: "OR",
// persons : [
// { name: "Paul", age: 28 }
// ]
// },
// {
// city: "Oakland",
// state: "CA",
// persons: [
// { name: "Matt", age: 21 },
// { name: "Sam", age: 24 }
// ]
// }
// ]
You can use the function reduce to group and build the desired output.
const people = [ {name: "John", age: 23, city: "Seattle", state: "WA"}, {name: "Mark", age: 25, city: "Houston", state: "TX"}, {name: "Luke", age: 26, city: "Seattle", state: "WA"}, {name: "Paul", age: 28, city: "Portland", state: "OR"}, {name: "Matt", age: 21, city: "Oakland", state: "CA"}, {name: "Sam", age: 24, city: "Oakland", state: "CA"}]
const result = Object.values(people.reduce((a, {name, age, city, state}) => {
var key = [city, state].join('|');
(a[key] || (a[key] = {city, state, persons: []})).persons.push({name, age});
return a;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have built a generic group by reducer, you pass it the keys by which you want to group and it gives you a custom reducer function. This reducer gives you an object indexed by a (composed or simple) key containing an array of items that share this key. You can reuse it to have it grouped by the key(s) you want to.
Here are two examples.
const people = Object.freeze([{
name: "John",
age: 23,
city: "Seattle",
state: "WA"
}, {
name: "Mark",
age: 25,
city: "Houston",
state: "TX"
}, {
name: "Luke",
age: 26,
city: "Seattle",
state: "WA"
}, {
name: "Paul",
age: 28,
city: "Portland",
state: "OR"
}, {
name: "Matt",
age: 21,
city: "Oakland",
state: "CA"
}, {
name: "Sam",
age: 24,
city: "Oakland",
state: "CA"
}]);
const groupByReducer = (group) =>
(result, row) => {
const keygroup = group.map((v) => row[v]);
const key = keygroup.join(':');
if (result[key])
result[key].push(row);
else
result[key] = [row];
return result;
};
const byCityState = people.reduce(
groupByReducer(['city', 'state']), {});
const byState = people.reduce(groupByReducer(['state']), {});
console.log(byCityState);
console.log(byState);
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}

Categories

Resources