I'm saving an object in state that looks like:
ingredients: {
salad: {
amount: 3,
basePrice: 1
},
cheese: {
amount: 2,
basePrice: 1.2
}
}
I want to use this in my component's state as follows
ingredients: {
salad: 3,
cheese: 2
}
I used Object.keys and map at first but it returns an Array of key value pairs instead of objects.
Although this works:
const newIngredientsObject = {};
for (const i in ingredientsObject)
newIngredientsObject[i] = ingredientsObject[i].amount;
return newIngredientsObject;
I want to find a solution without a helper method, as such:
const mapStateToProps = state => {
return {
ingredients: Object.keys(state.burger.ingredients).map(i => (
{ [i]: state.burger.ingredients[i].amount } )),
totalPrice: state.burger.totalPrice
}
};
You could map the entries of the object by using the amount property of the values and take Object.assign for getting a new object.
var ingredients = { salad: { amount: 3, basePrice: 1 }, cheese: { amount: 2, basePrice: 1.2 } },
result = Object.assign(
...Object.entries(ingredients).map(([key, value]) => ({ [key]: value.amount }))
);
console.log(result);
when you want to create one object to inside to another go this web
object 1 = {
content 1 = {
stuff = {
},
more Stuff = {
}
}
}
object 2 = {
key 1:"",
key 2:""
}
object 1.content 1 = object 2;
You asked a solution with Object.keys, here is one:
var x = {ingredients: {
salad: {
amount: 3,
basePrice: 1
},
cheese: {
amount: 2,
basePrice: 1.2
}
}}
Object.keys(x.ingredients).map((d,i)=>[d,x.ingredients[d].amount]).reduce((ac,d,i)=>(ac[d[0]]=d[1],ac),{})
//{salad: 3, cheese: 2}
The shortest I could come up with it using Object.keys and reduce
const ingredients = {
salad: {
amount: 3,
basePrice: 1
},
cheese: {
amount: 2,
basePrice: 1.2
}
};
let newObject = Object.keys(ingredients).reduce((acc, val) => {
acc[val] = ingredients[val].amount;
return acc;
}, {});
console.log(newObject);
ANSWER:
Thanks for the solutions provided by #ibowankenobi and #Nina Scholz
Reduce function:
Object.keys(ingredients)
.map( (d, i) => [d, ingredients[d].amount])
.reduce( (ac, d, i) => (ac[d[0]] = d[1], ac), {} );
Entries function:
Object.assign(...Object.entries(ingredients)
.map( ([key, value]) => ({ [key]: value.amount }) ) );
reduce: 0.0849609375ms
entries: 0.1650390625ms
forLoop: 0.07421875ms
reduce: 0.024169921875ms
entries: 0.048095703125ms
forLoop: 0.010009765625ms
reduce: 0.010009765625ms
entries: 0.016845703125ms
forLoop: 0.0078125ms
After some testing, it seems like using a for loop is the most efficient and after that the reduce function.
Related
i'm trying to duplicate objects based on two properties that have multiple values differentiated by a comma.
For example:
I have an object
const obj = {
id: 1
date: "2021"
tst1: "111, 222"
tst2: "AAA, BBB"
}
And I would like the result to be an array of 2 objects in this case (because there are 2 values in tst1 OR tst2, these 2 properties will always have the same nr of values differentiated by a comma)
[{
id: 1,
date: "2021",
tst1: "111",
tst2: "AAA",
},
{
id: 1,
date: "2021",
tst1: "222",
tst2: "BBB",
}]
What I tried is this:
I created a temporary object
const tempObject = {
id: obj.id,
date: obj.date,
}
And then I would split and map the property that has multiple values, like this:
cont newObj = obj.tst1.split(",").map(function(value) {
let finalObj = {}
return finalObj = {
id: tempObject.id,
date: tempObject.date,
tst1: value,
})
And now, the newObj is an array of objects and each object contains a value of tst1.
The problem is I still have to do the same for the tst2...
And I was wondering if there is a simpler method to do this...
Thank you!
Here is an example that accepts an array of duplicate keys to differentiate. It first maps them to arrays of entries by splitting on ',' and then trimming the entries, then zips them by index to create sub-arrays of each specified property, finally it returns a result of the original object spread against an Object.fromEntries of the zipped properties.
const mapDuplicateProps = (obj, props) => {
const splitProps = props.map((p) =>
obj[p].split(',').map((s) => [p, s.trim()])
);
// [ [[ 'tst1', '111' ], [ 'tst1', '222' ]], [[ 'tst2', 'AAA' ], [ 'tst2', 'BBB' ]] ]
const dupeEntries = splitProps[0].map((_, i) => splitProps.map((p) => p[i]));
// [ [[ 'tst1', '111' ], [ 'tst2', 'AAA' ]], [[ 'tst1', '222' ], [ 'tst2', 'BBB' ]] ]
return dupeEntries.map((d) => ({ ...obj, ...Object.fromEntries(d) }));
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(mapDuplicateProps(obj, ['tst1', 'tst2']));
Not sure if that's what you're searching for, but I tried making a more general use of what you try to do:
const duplicateProperties = obj => {
const properties = Object.entries(obj);
let acc = [{}];
properties.forEach(([key, value]) => {
if (typeof value === 'string' && value.includes(',')) {
const values = value.split(',');
values.forEach((v, i) => {
if (!acc[i]) {
acc[i] = {};
}
acc[i][key] = v.trim();
});
} else {
acc.forEach(o => o[key] = value);
}
});
return acc;
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(duplicateProperties(obj));
You could start by determining the length of the result using Math.max(), String.split() etc.
Then you'd create an Array using Array.from(), returning the correct object for each value of the output index.
const obj = {
id: 1,
date: "2021",
tst1: "111, 222",
tst2: "AAA, BBB",
}
// Determine the length of our output array...
const length = Math.max(...Object.values(obj).map(s => (s + '').split(',').length))
// Map the object using the relevant index...
const result = Array.from({ length }, (_, idx) => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
const a = (value + '').split(/,\s*/);
return [key, a.length > 1 ? a[idx] : value ]
}))
})
console.log(result)
.as-console-wrapper { max-height: 100% !important; }
I have an array of objects. I want to reduce this array of objects based on the property category, so that I can get a total sum of each category. I wrote this function which returns an object:
const expenses =
[{
title: 'burger',
sum: 57,
category: 'eating_out',
notes: 'was good',
},
{
title: 'water bill',
sum: 142.81,
category: 'utilities',
notes: 'this sucks',
},
{
title: 'electricity bill',
sum: 112.90,
category: 'utilities',
notes: 'could be worse',
}]
const totalExpensesByCategory = expenses.reduce(
(acc, curr) => {
if (!acc[curr.category]) {
acc[curr.category] = curr.sum;
} else {
acc[curr.category] = +acc[curr.category] + curr.sum;
}
return acc;
},
{}
);
console.log(totalExpensesByCategory)
I want it to be an iterable object so that I can easily render components based on those entries.
How can I write it differently so that maybe it will return an array of objects that I can use map() on?
Object.entries() doesn't seem like the ideal way to do it.
Since you have valuable info in the object keys (the category) and similar in the values (the sum) you'll need both the key and the value from each object entry to create an array element. One approach is Object.entries, followed by map to create the individual objects that go into the output array.
const expenses =
[{
title: 'burger',
sum: 57,
category: 'eating_out',
notes: 'was good',
},
{
title: 'water bill',
sum: 142.81,
category: 'utilities',
notes: 'this sucks',
},
{
title: 'electricity bill',
sum: 112.90,
category: 'utilities',
notes: 'could be worse',
}]
const totalExpensesByCategory = Object.entries(expenses.reduce(
(acc, curr) => {
if (!acc[curr.category]) {
acc[curr.category] = curr.sum;
} else {
acc[curr.category] = +acc[curr.category] + curr.sum;
}
return acc;
},
{}
)).map(([k, v]) => ({category: k, sum: v}));
console.log(totalExpensesByCategory);
There's no reason not to build it up as an array of objects inside the reduce in the first place:
const totalExpensesByCategory = expenses.reduce(
(acc, curr) => {
const categoryIndex = acc.findIndex((category) => category.name === curr.category)
if (categoryIndex === -1) {
acc.push({name: curr.category, sum: curr.sum})
} else {
acc[categoryIndex].sum = acc[categoryIndex].sum + curr.sum;
}
return acc;
},
[]
);
Heres the output:
Just use Maps instead of objects. That's what they are for. Unlike objects, Maps are iterable:
const totalExpensesByCategory = expenses.reduce(
(m, curr) =>
m.set(curr.category, (m.get(curr.category) ?? 0) + curr.sum),
new Map
);
for (let [category, sum] of totalExpensesByCategory)
console.log(category, sum)
As a bonus, your reducer looks much nicer without curly braces and return
Could you please suggest any shorter code to solve following problem. I have array of objects:
const arr1=[
{ '1': { grade: 1.3, counter: 2 } },
{ '1': { grade: 2.8, counter: 2 } },
{ '2': { grade: 4.5, counter: 1 } },
{ '2': { grade: 2.4, counter: 1 } }
]
the output should look like:
const obj1={
'1': {grade:4.1,counter:4}
'2': {grade:6.9,counter:2}
}
here is the code i have tried :
arr1.reduce((acc,e)=> {
let element = Object.keys(e)
if(!acc.hasOwnProperty(element)){
acc[element]={grade:0,counter:0}
}
acc[element].grade+= e[element].grade
acc[element].counter+= e[element].counter
return acc
}, {})
Thank you
Your problem here is that Object.keys(e) return an array of keys and not a single key.
If your object will always be the same (with one key) the you can get the key with : let element = Object.keys(e)[0]
I've decomposed the code with 2 main parts.
Getting the key of the current item (with const key = Object.keys(current)[0])
Checking if the newObject has the key
If yes : updating the grade and counter value with the current object
If no : adding the current object to the key
const arr1 = [{
'1': {
grade: 1.3,
counter: 2
}
},
{
'1': {
grade: 2.8,
counter: 2
}
},
{
'2': {
grade: 4.5,
counter: 1
}
},
{
'2': {
grade: 2.4,
counter: 1
}
}
]
const newObject = arr1.reduce((newObject, current) => {
const key = Object.keys(current)[0]
const associatedObject = newObject[key]
if (associatedObject) {
associatedObject.grade += current[key].grade
associatedObject.counter += current[key].counter
} else {
newObject[key] = current[key]
}
return newObject
}, {})
console.log(newObject)
I would go for:
arr1.reduce((o,n)=>{
Object.keys(n).forEach((key)=>{
if(!o[key]) o[key]={grade:0, counter:0};
o[key]["grade"] += n[key]["grade"];
o[key]["counter"] += n[key]["counter"];
});
return o;
},{});
Step1: reducing over the array, acc = {}
Step2: iterating over every key of the object at the current index
Step3: summing up the according properties
You forgot to iterate over the keys.
Comment: As a pons asinorum I use for array.reduce() the variable names:
o meaning old value (=acc=_accumulator)
n meaning new value.
Try this
let arr1=[{'1': {grade: 1.3, counter: 2}},{'1': {grade: 2.8, counter: 2}},{'2': {grade: 4.5, counter: 1}},{'2': {grade: 2.4, counter: 1}}];
arr1 = arr1.reduce((acc, obj) => {
let [k, v] = Object.entries(obj)[0];
if(!acc.hasOwnProperty(k)) acc[k] = {grade:0,counter:0};
acc[k].grade += v.grade;
acc[k].counter += v.counter;
return acc
}, {});
console.log(arr1)
Object.keys returns an array of strings, so to check here you would probably want the first one:
arr1.reduce((acc, e) => {
const element = Object.keys(e)[0]; // `[0]` accesses the first key
if(!acc.hasOwnProperty(element)){
acc[element]={grade:0,counter:0}
}
acc[element].grade+= e[element].grade
acc[element].counter+= e[element].counter
return acc
}, {});
Then instead of the if statement you can use the new logical nullish assignment operator:
arr1.reduce((acc, e) => {
const element = Object.keys(e)[0];
acc[element] ??= { grade: 0, counter: 0 };
acc[element].grade += e[element].grade;
acc[element].counter += e[element].counter;
return acc
}, {});
This is probably as short as I'd go before it starts getting pointless.
Even further; using nullish coalescing and optional chaining:
arr1.reduce((acc, e) => {
const element = Object.keys(e)[0];
acc[element] = {
grade: (acc[element]?.grade ?? 0) + e[element].grade,
counter: (acc[element?.counter ?? 0) + e[element].counter,
};
return acc
}, {});
With Object.assign to do it all in one line:
arr1.reduce((acc, e) => {
const element = Object.keys(e)[0];
return Object.assign(acc, {
[element]: {
grade: (acc[element]?.grade ?? 0) + e[element].grade,
counter: (acc[element?.counter ?? 0) + e[element].counter,
},
});
}, {});
Finally, just for kicks, let's inline the element variable altogether to get this amazing line:
arr1.reduce((acc, e) => Object.assign(acc, {
[Object.keys(e)[0]]: {
grade: (acc[Object.keys(e)[0]]?.grade ?? 0) + e[Object.keys(e)[0]].grade,
counter: (acc[Object.keys(e)[0]]?.counter ?? 0) + e[Object.keys(e)[0]].counter,
},
}), {});
Another solution is:
const obj1 = arr1.reduce((acc, value) => {
if(acc[Object.keys(value)[0]]) {
acc[Object.keys(value)[0]].grade = acc[Object.keys(value)[0]].grade + value[Object.keys(value)[0]].grade;
acc[Object.keys(value)[0]].counter = acc[Object.keys(value)[0]].counter + value[Object.keys(value)[0]].counter;
} else {
acc[Object.keys(value)[0]] = value[Object.keys(value)[0]];
}
return acc;
})
I have the following function:
export const functionName = (key) => {
const data = [
{
displayOrder: 0,
key: 'key-name-1',
},
{
displayOrder: 2,
key: 'key-name-2',
},
];
for (let index in data) {
return data[index].displayOrder === 0
? data[index].key
: data[0].key;
}
};
The purpose of the function is to return the key with the lowest displayOrder. This works, but is there a better more slick method to achieve this?
Secondly, how best could I create a similar version to re-order the entire array based on displayOrder?
The proper method to use when you want to extract a single thing from an array (and you can't identify the element by itself with .find) is to use .reduce:
const functionName = () => {
const data = [
{
displayOrder: 0,
key: 'key-name-1',
},
{
displayOrder: 2,
key: 'key-name-2',
},
];
const lowestDisplayObj = data.reduce((lowestObjSoFar, currentObj) => {
if (currentObj.displayOrder < lowestObjSoFar.displayOrder) return currentObj;
return lowestObjSoFar;
}, { displayOrder: Infinity, key: null });
return lowestDisplayObj.key;
};
console.log(functionName());
Note that your current argument to functionName, key, was unused in your snippet, I'm not sure what it's for.
You could also use .sort() and select the first element in the array, but that's more computationally expensive than it needs to be.
You could make use of array reduce and do something like below.
const data = [{
displayOrder: 10,
key: 'key-name-10',
},
{
displayOrder: 2,
key: 'key-name-2',
},
{
displayOrder: 1,
key: 'key-name-1 Lowest',
}
];
let a = data.reduce((prev, item) => {
if (!prev) {
return item;
}
return (item.displayOrder < prev.displayOrder) ? item : prev;
});
console.log(a.key);
const data = [
{
displayOrder: 0,
key: 'key-name-1',
},
{
displayOrder: 2,
key: 'key-name-2',
},
];
const min = Math.min.apply(null, data.map(({displayOrder}) => +displayOrder));
const obj = data.find((obj) => obj.displayOrder == min);
console.log(obj.key);
I have an array of objects like
[
{id: example1, count: 2}, {id: example2, count:2}
]
which I would like to turn into an object like
{
example1: { id: example1, count: 2},
example2: { id: example2, count: 2},
}
...and so on.
This is supposed to be used in an redux reducer which uses es6 so if something in there is available that is or in lodash.
Answer:
For clarity, this is what I ended up doing as #james-emanon suggested in his answer. It is of course just a matter of using reduce.
case ActionTypes.FETCH_SNAPSHOTS_SUCCESS:
let snapshots = action.payload.reduce((p, c) =>
Object.assign({},
p, {
[c.id]: c
}
), {}
)
return Object.assign({},
state, {
isFetching: false,
lastUpdated: action.meta.receivedAt
},
snapshots
)
how about something like this? (using es6 conventions)
// assuming you action will pass an object like this
var a = [
{id: 'example1', count: 2}, {id: 'example2', count:2}
]
// snippet from reducer
case TYPE.SOME_CASE_CONSTANT:
let newObj = action.yourArrayofObj
.reduce((p,c) => {
return {...p, [c.id]: c}
}, {})
return {
...state,
newObj
}
or
action.yourArrayofObj
.reduce((p,c) => {
p[c.id] = c
return p
}, {})
Something like ?
function arrayToObj(array, keyProperty){
var result = {}
for (var i = 0, l = array.length; i<l;i++)
result[array[i][keyProperty]] = array[i];
return result;
}
Assuming that example1 and example2 are supposed to be strings (e.g., "example1" and "example2"), using es6 arrow-notation:
let arrayToObj = (arr) => {
let o = {};
arr.forEach( (x) => o[x.id]=x );
return o;
};
This is just syntactic sugar over Andrew McCallum's answer.