Reducing object in JS wrong behavior? - javascript

I use the following reduce:
const data = this.forms.reduce((accumulator, current) => {
return (accumulator[current.name] = current.value);
}
}, {});
Where this.forms is:
[
{value: {document: "fsfsf", seria: "fsfsfsf"}, "name": "Form1"},
{value: {seria: "AA", age: "45"}, "name": "Form2"},
{value: {marry: "yes", hobby: "AAA"}, "name": "Form3"}
]
I need to build this result:
{
"Form1": {document: "fsfsf", seria: "fsfsfsf"},
"Form2": {seria: "AA", age: "45"},
"Form3": {marry: "yes", hobby: "AAA"}
}
But I get wrong result:
{
{document: "fsfsf", seria: "fsfsfsf"},
"Form2": {}
}
I can not get why?

The callback function to the accumulator for reduce must return the accumulator to be applied to subsequent elements in the array. Returning the result of an assignment expression returns the result of the expression (current.value in this case), not the accumulator.
const forms = [
{value: {document: "fsfsf", seria: "fsfsfsf"}, "name": "Form1"},
{value: {seria: "AA", age: "45"}, "name": "Form2"},
{value: {marry: "yes", hobby: "AAA"}, "name": "Form3"}
];
const data = forms.reduce((accumulator, current) => {
accumulator[current.name] = current.value;
return accumulator;
}, {});
console.log(data);

Related

Transform values inside object of object

I would like to transform values inside an object of an object. Something like this:
Initial object:
const studentDetails = {
'details1': {Name: "John", CountryName: "US", value: 1},
'details2': {Name: "David", CountryName: "AUS", value: 2},
'details3': {Name: "Bob", CountryName: "UK", value: 3},
};
Transformed object:
{
'details1': {Name: "John", CountryName: "US", value: 2},
'details2': {Name: "David", CountryName: "AUS", value: 3},
'details3': {Name: "Bob", CountryName: "UK", value: 4},
};
I did something like this already but could not figure it out
Object.fromEntries(Object.entries(studentDetails).map(([key,
value]) => [key, some data transformation on value]))
You can do something like this. We define a transformValue function which takes in the student details object and any transform function. Then applies the transform function on every value and returns the whole transformed details object.
const studentDetails = {details1: { Name: "John", CountryName: "US", value: 1 }, details2: { Name: "David", CountryName: "AUS", value: 2 }, details3: { Name: "Bob", CountryName: "UK", value: 3 }};
const transformValue = (details, transform) => {
return Object.entries(details).reduce((acc, [key, detail]) => {
acc[key] = {
...detail,
value: transform(detail.value)
}
return acc;
}, {});
};
console.log(transformValue(studentDetails, (val) => val + 1)); // Increments value
console.log(transformValue(studentDetails, (val) => val * val)); // Squaring values
A solution that doesn't involve Object.fromEntries or reduce. Just iterate over the object, adding updated objects to output, and then returning output.
const data={details1:{Name:"John",CountryName:"US",value:1},details2:{Name:"David",CountryName:"AUS",value:2},details3:{Name:"Bob",CountryName:"UK",value:3}};
const increment = (n) => n + 1;
const decrement = (n) => n - 1;
function transform(data, prop, fn) {
const output = {};
for (const key in data) {
output[key] = {
...data[key],
[prop]: fn(data[key][prop])
};
}
return output;
}
const update1 = transform(data, 'value', increment);
const update2 = transform(update1, 'value', decrement);
console.log(update1);
console.log(update2);

Unable to get object instead of array

Hello I am new to the site, and I have a problem with javascript that I do not know how to fix.
I have an array, which I want to turn into an object.
arr = [
{prefer: "sport_swimming", rating: "1"},
{prefer: "sport_running", rating: "5"},
{prefer: "sport_tennis", rating: "2"},
{prefer: "study_archeology", rating: "4"}];
obj = Object.assign({}, arr);
console.log(obj);
I want to get to something like this:
{
"sport": {
"swimming":"1",
"running":"5",
"tennis":"2"
},
"study":
{
"archeology":"4"
}
}
Using reduce you can look over the array and build an object using the keys you split off the property.
const arr = [
{prefer: "sport_swimming", rating: "1"},
{prefer: "sport_running", rating: "5"},
{prefer: "sport_tennis", rating: "2"},
{prefer: "study_archeology", rating: "4"}
];
const out = arr.reduce((acc, data) => {
const parts = data.prefer.split("_");
acc[parts[0]] = acc[parts[0]] || {};
acc[parts[0]][parts[1]] = data.rating;
return acc;
}, {});
console.log(out);
You can use reduce:
const arr = [
{ prefer: "sport_swimming", rating: "1" },
{ prefer: "sport_running", rating: "5" },
{ prefer: "sport_tennis", rating: "2" },
{ prefer: "study_archeology", rating: "4" }];
const result = arr.reduce((a, e) =>
([parent, child] = e.prefer.split('_'),
(a[parent] ??= {})[child] = e.rating,
a), {});
console.log(result);

Filter an array based on object different values

I want to simplify an array of objects filtering. Let's assume that I have following array,
const users = [{
name: "user1",
gender: "m"
}, {
name: "user2",
gender: "f"
}, {
name: "user3",
gender: "f"
}, {
name: "user4",
gender: "m"
}, {
name: "user5",
gender: "m"
}];
For example i need to filter all users by male or female, so i do loop through filter object property:
const fUsers = users.filter(user => user.gender === "f");
const mUsers = users.filter(user => user.gender === "m");
const results = [fUsers, mUsers];
console.log(results);
Is there any better way to filter to do it in one shot, so i can get results in mutiple arrays.
You can use reduce function and in the accumulator pass a nested array. In the callback check the gender and you can use acc[0] to add elements in the first nested array of the accumulator or acc[1]
const users = [{
name: "user1",
gender: "m"
}, {
name: "user2",
gender: "f"
}, {
name: "user3",
gender: "f"
}, {
name: "user4",
gender: "m"
}, {
name: "user5",
gender: "m"
}];
const newArr = users.reduce((acc, curr) => {
if (curr.gender === 'f') {
acc[0].push(curr)
} else {
acc[1].push(curr)
}
return acc;
}, [
[],
[]
]);
console.log(newArr)
Alternatively you can also use normal for loop
const users = [{
name: "user1",
gender: "m"
}, {
name: "user2",
gender: "f"
}, {
name: "user3",
gender: "f"
}, {
name: "user4",
gender: "m"
}, {
name: "user5",
gender: "m"
}];
let arr = [
[],
[]
];
for (let i = 0; i < users.length; i++) {
users[i].gender === 'f' ? arr[0].push(users[i]) : arr[1].push(users[i])
};
console.log(arr)
This can be done using Array.reduce.
On Array.reduce callback, if gender is m, you can push to acc[0] and if gender is f, you can push to acc[1].
const users = [{
name: "user1",
gender: "m"
}, {
name: "user2",
gender: "f"
}, {
name: "user3",
gender: "f"
}, {
name: "user4",
gender: "m"
}, {
name: "user5",
gender: "m"
}];
const result = users.reduce((acc, cur) => {
cur.gender === 'f' ? acc[0].push(cur) : acc[1].push(cur);
return acc;
}, [ [], [] ]);
console.log(result);
Basic approach
forEach
Basically for any kind of operation with array, you can use a forEach.In this case you just create as much help variables as you neeed and process filter logic in forEach.
const males = []
const females = []
users.forEach(u=> u.gender === 'm' ? males.push(u) : females.push(u))
Another javascript Loop
Another simple solution is to use one of javascript loops, in my example I have chosen for-in but principle is the same as in forEach example
const males = []
const females = []
for (u in users) {
if (u.gender === 'm') {
males.push(u)
} else {
females.push(u)
}
}
Advanced approach
reduce
Reduce is function which allows as to process array loop and store result to single variable called accumulator, which is shared in every loop step. We can use [[],[]] => array of arrays accumulator
const [males, females] = users.reduce((acc, user) => {
if (user.gender === 'm') {
acc[0].push(u)
} else {
acc[1].push(u)
}
return acc;
}, [[], []])
reduce one-liner
Same principle as the last, but refactored to one line. (not the beast readability)
const [males, females] = users.reduce((acc, u) => (u.gender === 'm' ? acc[0].push(u) : acc[1].push(u), acc), [[],[]])
lodash/underscore
If you use lodash, you can use a groupBy function, which create object with keys consist of filter attributes and array values, same function you can find also in underscore.
const result = _.groupBy(users, 'gender'));
// { 'm': [{male1, male2}], 'f': [female1, female2] }
You could group by gender and take a destructuring with a renaming to the wanted variable names.
const
users = [{ name: "user1", gender: "m" }, { name: "user2", gender: "f" }, { name: "user3", gender: "f" }, { name: "user4", gender: "m" }, { name: "user5", gender: "m" }],
{ f: fUsers, m: mUsers } = users.reduce((r, o) => ((r[o.gender] ??= []).push(o), r), {});
console.log(fUsers);
console.log(mUsers);
.as-console-wrapper { max-height: 100% !important; top: 0; }
this will work, instead of two filters, you can reduce into an array of two arrays, sorting as you go. the acc is set after the function that reduce uses.
const sortedByGender =users.reduce((acc, user)=>{
user.gender==="f"?acc[0].push(user):acc[1].push(user);
return acc;
},[[],[]]);

Sorting an array of objects by properties before mapping [duplicate]

I have this sample data returned from an API.
I'm using Lodash's _.groupBy to convert the data into an object I can use better.
The raw data returned is this:
[
{
"name": "jim",
"color": "blue",
"age": "22"
},
{
"name": "Sam",
"color": "blue",
"age": "33"
},
{
"name": "eddie",
"color": "green",
"age": "77"
}
]
I want the _.groupBy function to return an object that looks like this:
[
{
color: "blue",
users: [
{
"name": "jim",
"color": "blue",
"age": "22"
},
{
"name": "Sam",
"color": "blue",
"age": "33"
}
]
},
{
color: "green",
users: [
{
"name": "eddie",
"color": "green",
"age": "77"
}
]
}
]
Currently I'm using
_.groupBy(a, function(b) { return b.color})
which is returning this.
{blue: [{..}], green: [{...}]}
the groupings are correct, but I'd really like to add the keys I want (color, users). is this possible using _.groupBy? or some other LoDash utility?
You can do it like this in both Underscore and Lodash (3.x and 4.x).
var data = [{
"name": "jim",
"color": "blue",
"age": "22"
}, {
"name": "Sam",
"color": "blue",
"age": "33"
}, {
"name": "eddie",
"color": "green",
"age": "77"
}];
console.log(
_.chain(data)
// Group the elements of Array based on `color` property
.groupBy("color")
// `key` is group's name (color), `value` is the array of objects
.map((value, key) => ({ color: key, users: value }))
.value()
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Original Answer
var result = _.chain(data)
.groupBy("color")
.pairs()
.map(function(currentItem) {
return _.object(_.zip(["color", "users"], currentItem));
})
.value();
console.log(result);
Online Demo
Note: Lodash 4.0 onwards, the .pairs function has been renamed to _.toPairs()
Isn't it this simple?
var result = _(data)
.groupBy(x => x.color)
.map((value, key) => ({color: key, users: value}))
.value();
Highest voted answer uses Lodash _.chain function which is considered a bad practice now "Why using _.chain is a mistake."
Here is a fewliner that approaches the problem from functional programming perspective:
import tap from "lodash/fp/tap";
import flow from "lodash/fp/flow";
import groupBy from "lodash/fp/groupBy";
const map = require('lodash/fp/map').convert({ 'cap': false });
const result = flow(
groupBy('color'),
map((users, color) => ({color, users})),
tap(console.log)
)(input)
Where input is an array that you want to convert.
another way
_.chain(data)
.groupBy('color')
.map((users, color) => ({ users, color }))
.value();
Thanks #thefourtheye, your code greatly helped.
I created a generic function from your solution using the version 4.5.0 of Lodash.
function groupBy(dataToGroupOn, fieldNameToGroupOn, fieldNameForGroupName, fieldNameForChildren) {
var result = _.chain(dataToGroupOn)
.groupBy(fieldNameToGroupOn)
.toPairs()
.map(function (currentItem) {
return _.zipObject([fieldNameForGroupName, fieldNameForChildren], currentItem);
})
.value();
return result;
}
To use it:
var result = groupBy(data, 'color', 'colorId', 'users');
Here is the updated fiddler;
https://jsfiddle.net/sc2L9dby/
Other answers look good by using lodash.
And I would suggest a different approach by using JavaScript, such as Array#reduce, object for storing and ??= syntax. As a result, it just takes O(n) time complexity.
Using reduce to aggregate data.
Using logical nullish assignment only assigns if acc[color] is nullish (null or undefined).
const data = [{ "name": "jim", "color": "blue", "age": "22" }, { "name": "Sam", "color": "blue", "age": "33" }, { "name": "eddie", "color": "green", "age": "77" }];
const result = data.reduce((acc, {
name,
color,
age
}) => {
acc[color] ??= {
color: color,
users: []
};
acc[color].users.push({
name,
color,
age
});
return acc;
}, {});
console.log(Object.values(result));
Here is an updated version using lodash 4 and ES6
const result = _.chain(data)
.groupBy("color")
.toPairs()
.map(pair => _.zipObject(['color', 'users'], pair))
.value();
Example groupBy and sum of a column using Lodash 4.17.4
var data = [{
"name": "jim",
"color": "blue",
"amount": 22
}, {
"name": "Sam",
"color": "blue",
"amount": 33
}, {
"name": "eddie",
"color": "green",
"amount": 77
}];
var result = _(data)
.groupBy(x => x.color)
.map((value, key) =>
({color: key,
totalamount: _.sumBy(value,'amount'),
users: value})).value();
console.log(result);
I would suggest a different approach, using my own library you could do this in a few lines:
var groupMe = sequence(
groupBy(pluck('color')),
forOwn(function(acc, k, v) {
acc.push({colors: k, users: v});
return acc;
},[])
);
var result = groupMe(collection);
This would a be a bit difficult with lodash or Underscore because the arguments are in the opposite order order, so you'd have to use _.partial a lot.
const groupBy = (array, key, name) => {
return array.reduce((result, obj) => {
result[obj[key]] = result[obj[key]] || {
location: obj[name],
child: [],
};
// result[obj[key]] ??= {
// location: obj[name],
// child: []
// };
result[obj[key]].child.push(obj);
return result;
}, {});
};
const items = [
{
id: 1,
age: 75,
name: 'Kumar',
location: 'chennai',
},
{
id: 1,
age: 25,
name: 'Christo',
location: 'Dindigul',
},
{
id: 1,
age: 28,
name: 'SK',
location: 'chennai',
},
{
id: 1,
age: 21,
name: 'Ram',
location: 'chennai',
},
{
id: 1,
age: 21,
name: 'ravi',
location: 'chennai',
},
];
let obj = groupBy(items, 'age', 'location');
const result = Object.keys(obj).map((key) => [obj[key]][0]);
console.log(result);
In 2017 do so
_.chain(data)
.groupBy("color")
.toPairs()
.map(item => _.zipObject(["color", "users"], item))
.value();

using lodash .groupBy. how to add your own keys for grouped output?

I have this sample data returned from an API.
I'm using Lodash's _.groupBy to convert the data into an object I can use better.
The raw data returned is this:
[
{
"name": "jim",
"color": "blue",
"age": "22"
},
{
"name": "Sam",
"color": "blue",
"age": "33"
},
{
"name": "eddie",
"color": "green",
"age": "77"
}
]
I want the _.groupBy function to return an object that looks like this:
[
{
color: "blue",
users: [
{
"name": "jim",
"color": "blue",
"age": "22"
},
{
"name": "Sam",
"color": "blue",
"age": "33"
}
]
},
{
color: "green",
users: [
{
"name": "eddie",
"color": "green",
"age": "77"
}
]
}
]
Currently I'm using
_.groupBy(a, function(b) { return b.color})
which is returning this.
{blue: [{..}], green: [{...}]}
the groupings are correct, but I'd really like to add the keys I want (color, users). is this possible using _.groupBy? or some other LoDash utility?
You can do it like this in both Underscore and Lodash (3.x and 4.x).
var data = [{
"name": "jim",
"color": "blue",
"age": "22"
}, {
"name": "Sam",
"color": "blue",
"age": "33"
}, {
"name": "eddie",
"color": "green",
"age": "77"
}];
console.log(
_.chain(data)
// Group the elements of Array based on `color` property
.groupBy("color")
// `key` is group's name (color), `value` is the array of objects
.map((value, key) => ({ color: key, users: value }))
.value()
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Original Answer
var result = _.chain(data)
.groupBy("color")
.pairs()
.map(function(currentItem) {
return _.object(_.zip(["color", "users"], currentItem));
})
.value();
console.log(result);
Online Demo
Note: Lodash 4.0 onwards, the .pairs function has been renamed to _.toPairs()
Isn't it this simple?
var result = _(data)
.groupBy(x => x.color)
.map((value, key) => ({color: key, users: value}))
.value();
Highest voted answer uses Lodash _.chain function which is considered a bad practice now "Why using _.chain is a mistake."
Here is a fewliner that approaches the problem from functional programming perspective:
import tap from "lodash/fp/tap";
import flow from "lodash/fp/flow";
import groupBy from "lodash/fp/groupBy";
const map = require('lodash/fp/map').convert({ 'cap': false });
const result = flow(
groupBy('color'),
map((users, color) => ({color, users})),
tap(console.log)
)(input)
Where input is an array that you want to convert.
another way
_.chain(data)
.groupBy('color')
.map((users, color) => ({ users, color }))
.value();
Thanks #thefourtheye, your code greatly helped.
I created a generic function from your solution using the version 4.5.0 of Lodash.
function groupBy(dataToGroupOn, fieldNameToGroupOn, fieldNameForGroupName, fieldNameForChildren) {
var result = _.chain(dataToGroupOn)
.groupBy(fieldNameToGroupOn)
.toPairs()
.map(function (currentItem) {
return _.zipObject([fieldNameForGroupName, fieldNameForChildren], currentItem);
})
.value();
return result;
}
To use it:
var result = groupBy(data, 'color', 'colorId', 'users');
Here is the updated fiddler;
https://jsfiddle.net/sc2L9dby/
Other answers look good by using lodash.
And I would suggest a different approach by using JavaScript, such as Array#reduce, object for storing and ??= syntax. As a result, it just takes O(n) time complexity.
Using reduce to aggregate data.
Using logical nullish assignment only assigns if acc[color] is nullish (null or undefined).
const data = [{ "name": "jim", "color": "blue", "age": "22" }, { "name": "Sam", "color": "blue", "age": "33" }, { "name": "eddie", "color": "green", "age": "77" }];
const result = data.reduce((acc, {
name,
color,
age
}) => {
acc[color] ??= {
color: color,
users: []
};
acc[color].users.push({
name,
color,
age
});
return acc;
}, {});
console.log(Object.values(result));
Here is an updated version using lodash 4 and ES6
const result = _.chain(data)
.groupBy("color")
.toPairs()
.map(pair => _.zipObject(['color', 'users'], pair))
.value();
Example groupBy and sum of a column using Lodash 4.17.4
var data = [{
"name": "jim",
"color": "blue",
"amount": 22
}, {
"name": "Sam",
"color": "blue",
"amount": 33
}, {
"name": "eddie",
"color": "green",
"amount": 77
}];
var result = _(data)
.groupBy(x => x.color)
.map((value, key) =>
({color: key,
totalamount: _.sumBy(value,'amount'),
users: value})).value();
console.log(result);
I would suggest a different approach, using my own library you could do this in a few lines:
var groupMe = sequence(
groupBy(pluck('color')),
forOwn(function(acc, k, v) {
acc.push({colors: k, users: v});
return acc;
},[])
);
var result = groupMe(collection);
This would a be a bit difficult with lodash or Underscore because the arguments are in the opposite order order, so you'd have to use _.partial a lot.
const groupBy = (array, key, name) => {
return array.reduce((result, obj) => {
result[obj[key]] = result[obj[key]] || {
location: obj[name],
child: [],
};
// result[obj[key]] ??= {
// location: obj[name],
// child: []
// };
result[obj[key]].child.push(obj);
return result;
}, {});
};
const items = [
{
id: 1,
age: 75,
name: 'Kumar',
location: 'chennai',
},
{
id: 1,
age: 25,
name: 'Christo',
location: 'Dindigul',
},
{
id: 1,
age: 28,
name: 'SK',
location: 'chennai',
},
{
id: 1,
age: 21,
name: 'Ram',
location: 'chennai',
},
{
id: 1,
age: 21,
name: 'ravi',
location: 'chennai',
},
];
let obj = groupBy(items, 'age', 'location');
const result = Object.keys(obj).map((key) => [obj[key]][0]);
console.log(result);
In 2017 do so
_.chain(data)
.groupBy("color")
.toPairs()
.map(item => _.zipObject(["color", "users"], item))
.value();

Categories