I'm not sure what would be the best way to approach transforming this JS Array.
I have an array with a structure like (could contain more than 1 element):
let arr1 = [{amount: '10', quantity: 2, id: '123'}, {....}]
but I need to take that and transform that into an array structured like
let arr2 = [{value: '10'}, {value: '10'}]
Essentially adding new objects into the array based on the quantity in array 1.
I was thinking basic for loop but it seems to be getting a bit messy. Is there a simple way in JS to do this? Either with some sort of built in function etc?
You can easily get the result using flatMap.
First, you can create a temp array with the number of elements as quantity then map over the temp array to get the object with property amount in it.
let arr1 = [
{ amount: "10", quantity: 2, id: "123" },
{ amount: "30", quantity: 5, id: "123" },
];
const result = arr1.flatMap((obj) => {
const { amount, quantity } = obj;
return Array(quantity)
.fill(0)
.map((x) => ({ amount }));
});
console.log(result);
You can also make the above snippet succinct
const result = arr1.flatMap(({ quantity, amount }) => Array(quantity).fill(0).map((x) => ({ amount })));
let arr1 = [
{ amount: "10", quantity: 2, id: "123" },
{ amount: "30", quantity: 5, id: "123" },
];
const result = arr1.flatMap(({ quantity, amount }) =>
Array(quantity)
.fill(0)
.map((x) => ({ amount }))
);
console.log(result);
I was thinking basic for loop but it seems to be getting a bit messy
You just need a simple for and a while loop:
const input = [{amount: '10', quantity: 2, id: '123'}],
output = []
for (let { amount, quantity } of input)
while (quantity--)
output.push({ value: amount })
console.log(output)
You could create o.quantity number of objects using Array.from(). Use flatMap to get a flat array for each item in the input array
const output = input.flatMap(o =>
Array.from({ length: o.quantity }, _ => ({ value: o.amount }) )
)
You could also do it using reduce, with a manual for loop to append the correct number of entries.
let arr1 = [{amount: '10', quantity: 2, id: '123'}];
const result = arr1.reduce((accum, v) => {
for (let i = 0; i < v.quantity; i++) {
accum.push({value: v.amount});
}
return accum;
} , []);
This might have a slight performance improvement over flatMap, as it does not create any temporaries.
Related
I have an array
[{fruit: "Apple", cost: 2},{fruit: "Banana", cost: 3}, {fruit: "Apple", cost: 4}]
I just want an array of how many instances of each fruit in this array. So I used: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#remove_duplicate_items_in_an_array.
except that it gives me
[{"Apple", 2}, {"Banana", 1}]
and I'd like it to be
[{fruit:"Apple", count:2}, {fruit:"Banana", count:1}]
How to remap it to have the desired keys?
const reduced = array.reduce((allTypes, item) => {
if (item in allTypes) {
allTypes[item] ++;
}
else {
allTypes[item] = 1;
}
return allTypes;
}, []);
You can easily achieve this result using reduce and find
You just need to use reduce(that will give you a single result, array in this case) and increment the count if its respective fruit is already present else create a new object with 2 keys fruit and count. Make new object count to 1 by default.
const arr = [
{ fruit: "Apple", cost: 2 },
{ fruit: "Banana", cost: 3 },
{ fruit: "Apple", cost: 4 },
];
const result = arr.reduce((acc, curr) => {
const { fruit } = curr;
const isExist = acc.find((el) => el.fruit === fruit);
if (isExist) ++isExist.count;
else acc.push({ fruit, count: 1 });
return acc;
}, []);
console.log(result);
you can also do
const data =
[ { fruit: 'Apple', cost: 2 }
, { fruit: 'Banana', cost: 3 }
, { fruit: 'Apple', cost: 4 }
]
const result = data.reduce((acc, {fruit}, i, arr) =>
{
if (!acc.some(x=>x.fruit === fruit))
{
let count = arr.filter(x=>x.fruit === fruit).length
acc.push({ fruit, count })
}
return acc;
}, []);
console.log(result);
.as-console-wrapper {max-height: 100%!important;top:0}
Other responses are fine, but I'd like to propose you a one-liner solution, but that returns an object instead of an array of objects.
arr.reduce((acc, { fruit }) => ({ ...acc, [fruit]: (acc[fruit] || 0) + 1 }), {})
// returns {Apple: 2, Banana: 1}
I think it servers your use case, as the result you want is the count value mapped by the fruit name as key, anyway.
If you really need an array of objects, you can convert the result of the previous expression x by doing this:
Object.entries(x).map(([f, c]) => ({ fruit: f, count: c}))
This question already has answers here:
Sum JavaScript object propertyA values with the same object propertyB in an array of objects
(12 answers)
How to group by and sum an array of objects? [duplicate]
(2 answers)
Better way to sum a property value in an array
(20 answers)
Closed 1 year ago.
The problem was:
const arr = [
{ name1: 'value1', qty: 1, time: 1 },
{ name1: 'value2', qty: 1, time: 2 },
];
// using this reducer works!
const reducer = (acc, { name1, qty}) => {
const newQty = parseInt(acc[name1] ?? 0) + parseInt(qty); //?
return {
...acc,
[name1]: newQty
};
};
arr.reduce(reducer, {})
// this returns: {value1: sum, value2: sum, ... }
So far so good... but what happens when you have this?
const arr2 = [
{ name2: 'valueX', qty: 1, time: 1},
{ name2: 'valueY', qty: 1, time: 2},
];
Copy pasting and then changing the name works fine, of course... but is there another way?
It really depends on your specific use case, but you can create a function that generates the correct reducer, taking the key as input:
const arr = [
{ name1: 'value1', qty: 1, time: 1 },
{ name1: 'value2', qty: 1, time: 2 },
];
// using this reducer works!
const makeReducer = key => (acc, cur) => {
const newQty = parseInt(acc[key] ?? 0) + parseInt(cur.qty);
return {
...acc,
[cur[key]]: newQty
};
};
console.log(arr.reduce(makeReducer('name1'), {}))
const arr2 = [
{ name2: 'valueX', qty: 1, time: 1},
{ name2: 'valueY', qty: 1, time: 2},
];
console.log(arr2.reduce(makeReducer('name2'), {}))
It turns out there is:
const reducer = (acc, { time, qty, ...rest }) => {
const name = Object.values(rest);
const newQty = parseInt(acc[name] ?? 0) + parseInt(qty);
return {
...acc,
[name]: newQty
};
};
This will work fine as long all objects you need to reduce have that format.
(I didn't test adding non existent keys, but probably would still work...)
All you need to do is to "remove" by expliciting all the keys that are "common" and using the spreading operator to catch the changing one.
I'm having a bad time comparing two array of objects on a key.
I would like to compare, substract value when the key matches and display negative value when not in my target array. Finally, I want to have all target objects (if key didn't match) inside my final array.
An exemple would save 1000 words :
const initial = [{id: 1, value: 47}, {id: 2, value: 20}, {id: 7, value: 13}];
const target = [{id: 1, value: 150}, {id: 3, value: 70}, {id: 40, value: 477}];
//Desired output
// [{id: 1, value: 103}, {id: 2, value: -20}, {id: 7, value: -13}, {id: 3, value: 70}, {id: 40, value: 477}];
let comparator = [];
initial.map(initia => {
let hasSame = target.find(targ => {
return initia.id === targ.id
});
if(hasSame){
initia.value -= hasSame.value
} else{
initia.value = -initia.value
}
});
console.log(initial);
I'm getting almost the result I want except that I don't know how to merge target values properly. Is it possible to merge this values without looping over target array once more? Or could I do that inside the find ?
I want to get advice to do this as clean as possible
Thanks you!
You could use a Map and collect same id with a wanted factor for summing.
As result take key/value as new properties.
var add = (map, factor) => ({ id, value }) => map.set(id, map.has(id)
? map.get(id) - value * factor
: value * factor
),
initial = [ {id: 1, value: 47 }, { id: 2, value: 20 }, { id: 7, value: 13 }],
target = [{ id: 1, value: 150 }, { id: 3, value: 70 }, { id: 40, value: 477 }],
map = new Map,
result;
initial.forEach(add(map, -1));
target.forEach(add(map, 1));
result = Array.from(map, ([id, value]) => ({ id, value }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If the intent is to avoid a nested find inside of the loop, you could reduce the two arrays in to a Set. They don't allow duplicate keys, so you would ensure a single value for each ID provided.
const valueSet = [...initial, ...target].reduce((total, obj) => {
total[obj.id] = !total[obj.id]
? -obj.value
: total[obj.id] -= obj.value
return total;
}, {});
const result = Object.keys(valueSet).map(key => ({ id: key, value: valueSet[key]}))
console.log(result);
Then you'd map back over the result to build out the intended array.
I have an array:
const array = [
{name: "abc", numbers:[1,2]},
{name: "def", numbers:[3,4]}
];
I want to use .map() to return a new array like:
[
{name:"abc", number:1},
{name:"abc", number:2},
{name:"def", number:3},
{name:"def", number:4}
]
What should I do?
It would be more performant to use forEach instead of map.
As #MohammadUsman's nice answer shows, the output of map has to be flattened before you can get the result you want. With forEach (which returns nothing), you can just append to the output array directly:
const data = [
{ name: "abc", numbers: [1,2] },
{ name: "def", numbers: [3,4] }
];
var result = [];
data.forEach(
({ numbers, ...rest }) => numbers.forEach(
n => result.push(Object.assign({number: n}, rest )))
);
console.log(result);
You can iterate over input array using .map() and use Object.assign() to generate new object and finally flat the array using .flat() to get the desired output.
const data = [
{ name: "abc", numbers: [1,2] },
{ name: "def", numbers: [3,4] }
];
const result = data.map(
({ numbers, ...rest }) => numbers.map(n => Object.assign({number: n}, rest ))
).flat();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could take directly Array#flatMap with an inner mapping of new objects and get an array of objects.
const
array = [{ name: "abc", numbers:[1, 2] }, { name: "def", numbers:[3, 4] }],
result = array.flatMap(({ name, numbers }) => numbers.map(number => ({ name, number })));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use .reduce if flatmap not available. It is faster in your cases. Iteration will be only one time for each element.
const array = [
{ name: "abc", numbers: [1, 2] },
{ name: "def", numbers: [3, 4] },
];
const results = array.reduce((r, item) => {
r = r.concat(item.numbers.map((number) => ({ name: item.name, number })));
return r;
}, []);
console.log(results)
Here is a simple solution if you are up for using jquery...
const array = [
{name: "abc", numbers:[1,2]},
{name: "def", numbers:[3,4]}
];
var newArray = [];
$.each(array, function(k,v){
var strName = v.name;
$.each(v.numbers, function(key,value){
newArray.push({name: strName, number: value});
});
});
console.log(newArray);
Is there any way to convert a list of objects into a new list where the properties are now values. I have tried to use Object.keys() and Object.values() to make two separate lists and then trying to combine them, without much success.
Example:
I want to turn:
[{ip: 123, name: "bob"}]
into:
[{property: "ip", value: 123}, {property: "name", value: "bob"}]
How do I solve this problem?
I would simply do a for loop, go through the array and then push these new objects in a new array.
The following code should solve your issue:
const array1 = [{ ip: 123, name: 'bob' }];
let newArray;
for (let i = 0; i < array1.length; i++) {
newArray.push({
property: 'ip',
value: array1[i].value
});
newArray.push({
property: 'name',
value: array1[i].name
});
}
You could use upcoming Array#flatMap and take the entries of the object.
var data = [{ ip: 123, name: "bob" }],
result = data.flatMap(o => Object.entries(o).map(([property, value]) => ({ property, value })));
console.log(result);
Traditional approach with Array#reduce.
var data = [{ ip: 123, name: "bob" }],
result = data.reduce((r, o) => [
...r,
...Object.entries(o).map(([property, value]) => ({ property, value }))
], []);
console.log(result);