I have an array of objects, where each object has a property whose value is another array, like this
[
{
location: 'somewhere',
location_id: 1,
parts: [
{
part_id: 1,
description: 'foo',
qty: 1,
}
]
}
]
I need to map these arrays into a single array like this
[
{
part_id: 1,
description: 'foo',
qty: 1
},
{
part_id: 2,
description: 'bar',
qty: 1
}
]
I tried using reduce like
newArr: arr.reduce((a,b) => a.parts.concat(b.parts)) but get the error 'Cannot read property concat of undefined.' I also tried providing the initialValue argument of reduce as an empty array but same error.
As a bonus, I will eventually need the final array to not contain duplicates of parts: I.e. if a part is in two locations, it would just combine the quantity.
Open to solutions using es6 but needs to not modify original array
This solution uses reduce and forEach to combine all objects in sub-arrays into a Map, with part_d as key, and the qty the result of the combined quantities. When a new part_id is found, a new object is created by spreading the part into a new object, and overriding qty with 0.
The Map is then converted back to an array by spreading the Map.values() iterator:
const data = [{"location":"somewhere","location_id":1,"parts":[{"part_id":1,"description":"foo","qty":1}]},{"location":"somewhere2","location_id":2,"parts":[{"part_id":1,"description":"foo","qty":3},{"part_id":2,"description":"bar","qty":1}]}];
const result = [...
data.reduce((r, { parts }) => {
(parts || []).forEach((o) => {
r.has(o.part_id) || r.set(o.part_id, { ...o, qty: 0 });
r.get(o.part_id).qty += o.qty;
});
return r;
}, new Map())
.values()];
console.log(result);
This adds all part objects into the base and removes the parts array. It will modify the old object.
let arr = [
{
location: 'somewhere',
location_id: 1,
parts: [
{
part_id: 1,
description: 'something'
}
]
}
]
arr.map(m => {
m.parts.forEach(p => Object.assign(m, p))
delete m.parts;
})
console.log(arr);
If you're sure you want to map the arrays and not the objects inside:
const input = [
{
location: 'somewhere',
location_id: 1,
parts: [
{
part_id: 1,
description: 'something'
}
]
},
{
location: 'somewhere2',
location_id: 22,
parts: [
{
part_id: 2,
description: 'something'
}
]
}
];
const mapped = input.map(outerObj => outerObj.parts[0]);
console.log(mapped);
You can use array#reduce to first join all your parts array of each object. Then use array#reduce to group each object in the result array on part_id and description and sum up the qty for each unique object. Then get all the values from this object.
const input = [ { location: 'somewhere', location_id: 1, parts: [ { part_id: 1, description: 'foo', qty: 1 } ] }, { location: 'somewhere2', location_id: 22, parts: [ { part_id: 2, description: 'something', qty: 3 } ] }, { location: 'somewhere2', location_id: 22, parts: [ { part_id: 2, description: 'something', qty: 4 } ] } ],
result = Object.values(input.reduce((r, {parts}) => r.concat(parts),[])
.reduce((r,o) => {
r[`${o.part_id}_${o.description}`] = r[`${o.part_id}_${o.description}`] || {...o, qty: 0};
r[`${o.part_id}_${o.description}`].qty += o.qty;
return r;
},{}));
console.log(result);
This question may have two scenarios :
each location have single object under parts array.
DEMO
let jsonObj = [
{
location: 'somewhere',
location_id: 1,
parts: [
{
part_id: 1,
description: 'foo',
qty: 1,
}
]
},
{
location: 'somewhere',
location_id: 2,
parts: [
{
part_id: 2,
description: 'foo',
qty: 1,
}
]
}
];
let res = jsonObj.map(obj => obj.parts[0]);
console.log(res);
Single location have multiple objects under parts array.
DEMO
let jsonObj = [
{
location: 'somewhere',
location_id: 1,
parts: [
{
part_id: 1,
description: 'foo',
qty: 1,
},
{
part_id: 2,
description: 'foo',
qty: 1,
}
]
}
];
let res = jsonObj[0].parts;
console.log(res);
I had a similar situation. What worked for me is defining a new array and then array.push(part) from inside a double map function:
const input = [{
location: 'somewhere',
location_id: 1,
parts: [{
part_id: 1,
description: 'something'
},
{
part_id: 2,
description: 'something'
}
]
},
{
location: 'somewhere2',
location_id: 22,
parts: [{
part_id: 3,
description: 'something'
},
{
part_id: 4,
description: 'something'
}
]
}
];
if this is your input..
var list = []
input.map(item => {
item.parts.map(part => {
list.push(part);
});
});
Related
I have an array that looks like this:
const myArray = [
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 3, name: 'Jake'},
],
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 4, name: 'Joe'},
],
]
I need to find common elements by id, and return them in an array that would look something like this:
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
]
If there isn't any way to do it with lodash, just JS could work too.
Note that I do not know how many of these arrays I will have inside, so it should work for any number.
You can use lodash's _.intersectionBy(). You'll need to spread myArray because _intersectionBy() expect arrays as arguments, and not a single array of array:
const myArray = [[{"id":1,"name":"Liam"},{"id":2,"name":"Oliver"},{"id":3,"name":"Jake"}],[{"id":1,"name":"Liam"},{"id":2,"name":"Oliver"},{"id":4,"name":"Joe"}]]
const result = _.intersectionBy(...myArray, 'id')
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
A vanilla solution can be as simple as a filter() call on the first element of the array checking to see that every() subsequent element contains some() elements that match.
const [srcElement, ...compArray] = [...myArray];
const intersection = srcElement.filter(o => (
compArray.every(arr => arr.some(p => p.id === o.id)))
);
console.log(intersection)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script>
const myArray = [
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 3, name: 'Jake' }],
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 4, name: 'Joe' }],
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 5, name: 'Dean' }, { id: 6, name: 'Mara' }]
]
</script>
Use nested forEach loops and Set. Go over each sub-array and find out the common items so far.
const intersection = ([firstArr, ...restArr]) => {
let common = new Set(firstArr.map(({ id }) => id));
restArr.forEach((arr) => {
const newCommon = new Set();
arr.forEach(({ id }) => common.has(id) && newCommon.add(id));
common = newCommon;
});
return firstArr.filter(({ id }) => common.has(id));
};
const myArray = [
[
{ id: 1, name: "Liam" },
{ id: 2, name: "Oliver" },
{ id: 3, name: "Jake" },
],
[
{ id: 1, name: "Liam" },
{ id: 2, name: "Oliver" },
{ id: 4, name: "Joe" },
],
[
{ id: 2, name: "Oliver" },
{ id: 4, name: "Joe" },
],
];
console.log(intersection(myArray));
Nowadays vanilla ES is pretty powerful to work with collections in a functional way even without the help of utility libraries.
You can use regular Array's methods to get a pure JS solution.
I've created two examples with pure JS.
Of course, there could be more approaches as well. And if you already use Lodash in your application, probably it would be better to just use its high-level implementation in form of _.intersectionBy() proposed above to reduce the code complexity.
const myArray = [
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 3, name: 'Jake'},
],
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 4, name: 'Joe'},
],
];
// Regular functional filter-reduce
const reducer = (accum, x) => {
return accum.findIndex(y => x.id == y.id) < 0
? [...accum, x]
: accum;
};
const resultFilterReduce = myArray
.flat()
.filter(x => myArray.every(y => y.findIndex(obj => obj.id === x.id) > -1))
.reduce(reducer, []);
console.log(resultFilterReduce);
// Filter-reduce with using of "HashMap" to remove duplicates
const resultWithHashMap = Object.values(
myArray
.flat()
.filter(x => myArray.every(y => y.findIndex(obj => obj.id === x.id) > -1))
.reduce((accum, x) => {
accum[x.id] = x;
return accum;
}, {})
);
console.log(resultWithHashMap);
if i want for an example loop Through this Array to find specific item in Items Array how to approach that? i made this logic but it doesn't work
DATA.map((D)=>{
return D.items.find((item)=>{
return item.name ==='Blue Beanie'
})
})
this is the Array plus how to create new ONE array includes the both of items arrays to be like that: items: [{
id: 1,
name: 'Brown Brim',
price: 25
},
{
id: 2,
name: 'Blue Beanie',
price: 18
},
{
id: 3,
name: 'Adidas NMD',
price: 220
},
{
id: 4,
name: 'Adidas Yeezy',
price: 280
}
]
const DATA= [
{
id: 1,
title: 'Hats',
routeName: 'hats',
items: [
{
id: 1,
name: 'Brown Brim',
price: 25
},
{
id: 2,
name: 'Blue Beanie',
price: 18
}
]
},
{
id: 2,
title: 'Sneakers',
routeName: 'sneakers',
items: [
{
id: 3,
name: 'Adidas NMD',
price: 220
},
{
id: 4,
name: 'Adidas Yeezy',
price: 280
}
]
}
];
Transform DATA into list of items and find from that list your expected item
const res = DATA.flatMap((D) => D.items).find(
(item) => item.name === "Brown Brim"
)
const DATA = [
{
id: 1,
title: "Hats",
routeName: "hats",
items: [
{
id: 1,
name: "Brown Brim",
price: 25,
},
{
id: 2,
name: "Blue Beanie",
price: 18,
},
],
},
{
id: 2,
title: "Sneakers",
routeName: "sneakers",
items: [
{
id: 3,
name: "Adidas NMD",
price: 220,
},
{
id: 4,
name: "Adidas Yeezy",
price: 280,
},
],
},
]
const res = DATA.flatMap((D) => D.items).find(
(item) => item.name === "Brown Brim"
)
console.log(res)
Reference
Array.prototype.flatMap()
Maybe this is helpful?
const DATA= [
{id: 1,title:'Hats',routeName:'hats',
items:[{id: 1,name:"Brown Brim",price:25},
{id: 2,name: 'Blue Beanie',price: 18}]},
{id: 2,title: 'Sneakers',routeName: 'sneakers',
items: [{id: 3,name: 'Adidas NMD',price: 220},
{id: 4,name: 'Adidas Yeezy',price: 280}]}
];
console.log(DATA.map(D=>D.items.find(item=>item.name==='Brown Brim'))
.filter(e=>e))
The .map returns either an element matching your criterion or undefined, The chained .filter then removes all "falsy" elements, i. e. all the undefined ones.
As for the first question "loop Through this Array to find a specific item in Items Array"
given it is not sorted in any way, this can be done by iterating over the DATA array and search inside the items
If want to have access to the item from the outside of the 'forEach' scope you have to declare the variable outside
Regarding the second question, use the reduce function while iterating the array
NOTE: You can obviously combine both tasks as you already iterate through the array, so no need to do it twice. But to avoid confusion, I separated the logic.
Also, if you do choose to combine the tasks, using the reduce is not relevant, but very much like the answer to the first question, you can declare a buffer such as an array, and just copy items to it on the go (I'll leave out questions on performance for that matter)
const DATA = [
{
id: 1, title: 'Hats', routeName: 'hats',
items: [
{id: 1,name: 'Brown Brim',price: 25},
{id: 2,name: 'Blue Beanie',price: 18}
]
},
{
id: 2, title: 'Sneakers', routeName: 'sneakers',
items: [
{id: 3,name: 'Adidas NMD',price: 220},
{id: 4,name: 'Adidas Yeezy',price: 280}
]
}
];
//Question 1
//creating the object that will hold the item
//outside of the 'forEach' scope so we can use it later
let res = {};
const searchRes = DATA.forEach(entry => {
entry.items.forEach(item => {
if (item.name === 'Brown Brim')
//duplicating the item to a variable declared outside of this scope
res = { ...item
};
});
});
console.log(`Q1 - The item we found:`);
console.log(res);
// Question 2
// Merging all object inside the 'items' array using Reduce
const merged = DATA.reduce((acc, curr) =>
acc.concat(curr.items), []);
console.log(`Q2 - The merged array:`);
console.log(merged)
Given an array like this:
[
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com" },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]
]
How could I use Javascript to reduce this to an array like this:
[
{ id: 1, emailAddress: "bill#test.com" },
{ id: 1, emailAddress: "bob#test.com" },
{ id: 2, emailAddress: "sarah#test.com" },
{ id: 3, emailAddress: "jane#test.com" },
{ id: 3, emailAddress: "laura#test.com" },
{ id: 3, emailAddress: "paul#test.com" }
]
I've read about the functions reduce, flat, map and so on and read lots of the questions on SO about using them but I can't find anything that's asking quite the same as this and I can't get my head around using those functions to do it.
You could use flatMap
const input = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"] }
]
const output = input.flatMap(o =>
o.emailAddresses.map(e => ({ id: o.id, emailAddress: e }) )
)
console.log(output)
If flatMap is not supported, you could use a nested for...of loop:
const input = [{id:1,emailAddresses:["bill#test.com","bob#test.com"]},{id:2,emailAddresses:["sarah#test.com"]},{id:3,emailAddresses:["jane#test.com","laura#test.com","paul#test.com"]}];
const output = []
for (const { id, emailAddresses } of input)
for (const emailAddress of emailAddresses)
output.push({ id, emailAddress })
console.log(output)
You can map over your data and then use reduce to flatten the resulting array:
const result = data
.map(datum => {
return datum.emailAddresses.map(emailAddress => {
return { id: datum.id, emailAddress };
});
})
.reduce((result, current) => {
return [...result, ...current];
}, []);
We can use Array.prototype.reduce to go over each object in the array and take into consideration the multiple values in the emailAddress property array and create separate object for each one and finally accumulate the new objects in the new array (r):
const data = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]}
]
const flat = data.reduce((r, e) => {
e.emailAddresses.forEach((obj) => r.push({id: e.id, emailAddresses : obj }));
return r;
}, []);
console.log(flat);
You can use reduce and map
const data = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]}
]
const flat = (toFlatten) =>
toFlatten.reduce((r,c)=> {
r.push(...c.emailAddresses.map(email=>({id: c.id, emailAddress: email})))
return r
}, [])
console.log(flat(data))
Here is a solution that doesn't use any array prototype but does, instead, take advantage of function generators.
The script below iterates the array, acquire all keys of the element except emailAddresses, which is handled separately, and for each email address it yields an object filled with the single email address and the rest of the data.
This solution iterate the original array only once.
Because it uses function generators, this solution is widely supported, it just won't work on IE due it's lack of support for function generators, despite babel or TSC can easily add compatibility to that.
const input = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"] }
];
function* flattenEmailAddresses(arr) {
for (var {emailAddresses, ...keys} of arr) {
for (var emailAddress of emailAddresses) yield {...keys, emailAddress};
}
}
console.log([...flattenEmailAddresses(input)]);
I am trying to swap out all the values of an array of objects in state with a whole new array of objects. However, nothing seems to be working. I've tried the following:
const list1 = [
{ id: 1, name: 'item1' },
{ id: 2, name: 'item1' },
{ id: 3, name: 'item1' },
{ id: 4, name: 'item1' },
]
const list2 = [
{ id: 1, name: 'newItem1' },
{ id: 2, name: 'newItem2' },
{ id: 3, name: 'newItem3' },
{ id: 4, name: 'newItem4' },
]
class FindTab extends Component {
state = {
status: 'loading',
location: null,
view: this.props.view,
map: this.props.map,
locationValues: list1,
}
}
this.setState(prevState => ({
locationValues: [ ...prevState.locationValues, list2 ],
}))
or just simpler:
this.setState(locationValues: list2)
Neither seem to work. Is there any guidance as to how one should replace an array of objects with another array for a state property?
You could spread the array in a new one like:
const locationValues = [ ...state.locationValues, ...list2 ]
this.setState({ locationValues })
I have a nested array of objects and I want to groupBy id and form a new array. Here's my array:
mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
I can flatten the entire array but it doesn't seem like the right way to do it.
My new array should look like something like this:
result = [
comapny_6: [
{
name: 'a',
age: 10,
desc: 'test1'
},
],
comapny_10: [
{
name: 'a',
age: 10,
desc: 'testa'
},
{
name: 'c',
age: 40,
desc: 'test3'
}
],
company_30 :[
{
name: 'b',
age: 20,
desc: 'test2'
},
{
name: 'c',
age: 40,
desc: 'testc'
}
]
]
I am open to suggestions on how the final data structure should look like. The bottom line is I want groupBy id so that I have information about each company separated out.
You can use reduce to loop thru the array and construct the desired object output. Use forEach to loop thru company
var mainArray = [{"name":"a","age":10,"company":[{"desc":"test1","id":6},{"desc":"testa","id":10}]},{"name":"b","age":20,"company":[{"desc":"test2","id":30}]},{"name":"c","age":40,"company":[{"desc":"test3","id":10},{"desc":"testc","id":30}]}];
var result = mainArray.reduce((c, {name,age,company}) => {
company.forEach(({id,desc}) => (c["company_" + id] = c["company_" + id] || []).push({name,age,desc}));
return c;
}, {});
console.log(result);
You can first create a 1D array using flatMap() and then use reduce() to group
const mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
const flat = mainArray.flatMap(({company,...rest}) => company.map(a => ({...rest,...a})));
const res = flat.reduce((ac,{id,...rest}) => ((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac),{})
console.log(res)
Explanation
reduce() is method with returns a single value after iterating through all the array. The accumulator i.e ac in above case is set to empty object {}(which is the second argument passed to function)
In each iteration we return the updated accumulator which becomes ac for next iteration. So what we return from function is following expression
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
ac[company_${id}] is using Bracket Notation which takes an expression company_${id}. It is same as
ac["company_" + id]
The above line checks if ac[company_${id}] exists in the ac then push() rest to the it.
If ac[company_${id}] is not created yet they set it to empty array [] then push() the rest to it.
The last part uses comma operator
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
The above whole expression will evaluate to the last value separated by comma , which is ac. So in each iteration we are pushing rest to the respective array and returning ac it the end. The code is equivalent to
const res = flat.reduce((ac,{id,...rest}) => {
//check if company id doesnot exist as key in ac then set it empty array
if(!ac[`company_${id}`]) ac[`company_${id}`] = [];
//push rest(which will be an object with all the keys expect id)
ac[`company_${id}`].push(rest)
//at last return ac
return ac;
})
You can achieve this with Array.reduce and inside it with an Array.forEach over the array of companies like this:
let data = [ { name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] }, { name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] }, { name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] } ]
let result = data.reduce((r,{ name, age, company }) => {
company.forEach(({ id, desc }) =>
r[`company_${id}`] = (r[`company_${id}`] || []).concat({ name, age, desc }))
return r
}, {})
console.log(result)