Efficiently merging fields from one array into two other arrays - javascript

Let's say you've got three arrays of objects:
let a1 = [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
let a2 = [
{ name: 'foo' },
{ name: 'bar' }
]
let a3 = [
{ name: 'bar' },
{ name: 'baz' }
]
The goal is to use a1 as a source, and add an id field to the elements of a2 and a3 with corresponding name fields in a1. What is an efficient way of accomplishing this? (Note: 'efficient' here meaning 'something more elegant than loops-within-loops-within-loops'.)
The result should look like this:
a2: [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' }
]
a3: [
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]

You could use a Map for referencing the id of a given name. Then assign.
var a1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'baz' }],
a2 = [{ name: 'foo' }, { name: 'bar' }],
a3 = [{ name: 'bar' }, { name: 'baz' }],
map = new Map(a1.map(o => [o.name, o.id]));
[a2, a3].forEach(a => a.forEach(o => o.id = map.get(o.name)));
console.log(a2);
console.log(a3);
.as-console-wrapper { max-height: 100% !important; top: 0; }

For an alternative answer, it could be like this.
It doesn't include loops and may be the shortest code in the answers.
const a1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'baz' }];
const a2 = [{ name: 'foo' }, { name: 'bar' }];
const a3 = [{ name: 'bar' }, { name: 'baz' }];
let f = x => a1.filter(a => x.some(y => y.name === a.name));
console.log(f(a2));
console.log(f(a3));
.as-console-wrapper { max-height: 100% !important; top: 0; }

a2.forEach((a2Elem) => a2Elem.id = a1.filter((a1Elem) => a1Elem.name === a2Elem.name)[0].id)

I'd first take the indexes of the given names, then just map the array to be merged into:
function combine(mergeInto, base) {
let indexes = base.map(e => e.name);
return mergeInto.map(e => ({
name: e.name,
id: base[indexes.indexOf(e.name)].id
}));
}
let a1 = [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
let a2 = [
{ name: 'foo' },
{ name: 'bar' }
]
let a3 = [
{ name: 'bar' },
{ name: 'baz' }
]
function combine(mergeInto, base) {
let indexes = base.map(e => e.name);
return mergeInto.map(e => ({
name: e.name,
id: base[indexes.indexOf(e.name)].id
}));
}
console.log(combine(a3, a1));

A single loop proposal - create a hash table and then merge fields into the arrays - demo below:
let a1=[{id:1,name:'foo'},{id:2,name:'bar'},{id:3,name:'baz'}], a2=[{name:'foo'},{name:'bar'}], a3=[{name:'bar'},{name:'baz'}];
// create a hash table
let hash = a1.reduce(function(p,c){
p[c.name] = c;
return p;
},Object.create(null))
// merge the results
function merge(arr) {
Object.keys(arr).map(function(e){
arr[e]['id'] = hash[arr[e].name]['id'];
});
return arr;
}
console.log(merge(a2), merge(a3));
.as-console-wrapper{top:0;max-height:100%!important;}

Related

JavaScript modifying an object inside the array

I am having a problem trying to modify the name of a nested object using map function and to return the modified object.
I was trying the approach with double forEach loop but I am also failing with that.
const myObject = [{
id: 1,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
{
id: 2,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: 'bar',
};
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
//trying to get:
alteredObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: 'bar',
},
{
id: 2,
name: 'foo',
},
],
},
{
id: 2,
childrenList: [
{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
];
This is the first time I am trying to modify a nested object. Normally with an array of objects, I am not having any issue so I am not sure what I am doing wrong
You only need to update the children with your map and it will work. Like this:
const myObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
},
{
id: 2,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
}
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList = thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: "bar"
};
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
And if you want to do it with forEach:
const myObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
},
{
id: 2,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
}
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList.forEach((item) => {
if (item.id === 1) {
item.name = 'bar';
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
If you can modify your object then you can do it with two forEach:
const myObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
},
{
id: 2,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
}
];
myObject.forEach((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList.forEach((item) => {
if (item.id === 1) {
item.name = 'bar';
}
return item;
});
}
});
console.log(myObject);
As you already know, Array.prototype.map() returns a new Array containing the modified version.
In your first map function myObject.map(), you aren't saving the second map function modified result as the childrenList content.
therefore no changes would be stored in the first map function and the result would have no changes.
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
// Here you should save the result of this
// Array.prototype.map() Function as the new 'thisChild.childrenList'
thisChild.childrenList = thisChild.childrenList.map((item) => {
// ...
});
}
return thisChild;
});
const myObject = [{
id: 1,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
{
id: 2,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList = thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: 'bar',
};
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
You can use this code :
const myObject = [
{
id: 1,
childrenList: [
{ id: 1, name: 'foo', },
{ id: 2, name: 'foo', },
],
},
{
id: 2,
childrenList: [
{ id: 1, name: 'foo', },
{ id: 2, name: 'foo', },
],
},
];
let result = myObject.map(
el => el.id === 1 ?
{...el, childrenList: el.childrenList.map(child => child.id === 1 ? {...child, name: 'bar'} : child)}
: el
);
console.log(result);
This can be done with a couple of map calls, we'll alter the name value if the firstChild id is 1 and the leaf object id is also 1:
const myObject = [ { id: 1, childrenList: [ { id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, { id: 2, childrenList: [ { id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, ];
const alteredObject = myObject.map((thisChild) => {
return { ...thisChild, childrenList: thisChild.childrenList.map(({id, name}) => {
return { id, name: (thisChild.id === 1 && id === 1) ? 'bar': name };
})}
});
console.log(alteredObject)
.as-console-wrapper { max-height: 100% !important; }
The array map method creates a new array (mdn), so the parent object alteredObject still has the childrenList key pointing to the original array.
To solve this, you can add assignment of the new array to the key:
thisChild.childrenList = thisChild.childrenList.map(...)
This way, the key will point to the newly created array
You're missing a return; you have to return the modified thisChild as {...thisChild, childrenList:modifiedChildrenList}
const myObject = [{ id: 1, childrenList: [{ id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, { id: 2, childrenList: [{ id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, ];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
return {...thisChild,childrenList:thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: 'bar',
};
}
return item;
})
}
}
return thisChild;
});
console.log(alteredObject);

How to groupBy the array of object and concat into single array? [duplicate]

This question already has answers here:
group array of objects by id
(8 answers)
Closed 1 year ago.
I want to group the array of objects based on the key and concat all the grouped objects into a single array. GroupBy based on the id
example,
payload
[
{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
},
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
]
expected response
[
[
{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
}
],
[
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
]
]
All the matched elements are in the same array and all the arrays should be in a single array.
Array.redue will help
const input = [
{ id: 1, name: 'a' },
{ id: 1, name: 'b' },
{ id: 1, name: 'c' },
{ id: 2, name: 'b' },
{ id: 2, name: 'c' }
];
const output = input.reduce((acc, curr) => {
const node = acc.find(item => item.find(x => x.id === curr.id));
node ? node.push(curr) : acc.push([curr]);
return acc;
}, []);
console.log(output)
Extract the ids using Set so you have a unique set of them,
then loop over those ids and filter the original array based on it.
let objects = [
{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
},
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
]
let ids = [...new Set(objects.map(i => i.id))]
let result = ids.map(id => objects.filter(n => id === n.id))
console.log(result)
you can create a object with ids array by using Array.reduce method, and get the object values by Object.values
var s = [{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
},
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
];
//go through the input array and create a object with id's, group the values to gather
var ids = s.reduce((a, c) => {
//check object has the `id` property, if not create a property and assign empty array
if (!a[c.id])
a[c.id] = [];
//push the value into desidred object property
a[c.id].push(c)
//return the accumulator
return a;
}, {});
//get the grouped array as values
var outPut = Object.values(ids);
console.log(outPut);
1) You can easily achieve the result using Map and forEach easily
const arr = [
{
id: 1,
name: "a",
},
{
id: 1,
name: "b",
},
{
id: 1,
name: "c",
},
{
id: 2,
name: "b",
},
{
id: 2,
name: "c",
},
];
const map = new Map();
arr.forEach((o) => !map.has(o.id) ? map.set(o.id, [o]) : map.get(o.id).push(o));
const result = [...map.values()];
console.log(result);
/* This is not a part of answer. It is just to give the output full height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
2) You can also achieve the result using reduce
const arr = [
{
id: 1,
name: "a",
},
{
id: 1,
name: "b",
},
{
id: 1,
name: "c",
},
{
id: 2,
name: "b",
},
{
id: 2,
name: "c",
},
];
const result = [...arr.reduce((map, curr) => {
!map.has(curr.id) ? map.set(curr.id, [curr]) : map.get(curr.id).push(curr);
return map;
}, new Map()).values()];
console.log(result);
/* This is not a part of answer. It is just to give the output full height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }

create new object value based on another object id

I need to update (or create new array) the id value in obj1 based on the id value in obj2. What is the best way to do this? I've tried with map and reduce but I wasn't successful. Any help will be appreciated.
const obj1 = [ { id: 1, name: 'foo' }, { id: 2, name: 'bar' } ]
const obj2 = { A: { id: 1, externalId:'AAA' }, B: { id: 2, externalId:'BBB' } }
outputExpected: [ { id: 'AAA', name: 'foo' }, { id: 'BBB', name: 'bar' } ]
Depending on the size of your arrays it may be more efficient to build a Map indexing obj2 ids (Map(2) { 1 => 'AAA', 2 => 'BBB', ... }) which can be used in mapping obj1.
const obj1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' },];
const obj2 = { A: { id: 1, externalId: 'AAA' }, B: { id: 2, externalId: 'BBB' } };
const map = new Map(Object.values(obj2).map((o) => [o.id, o.externalId]));
const result = obj1.map(({ id, ...o }) => ({ id: map.get(id), ...o }));
console.log(result);
I'd suggest using Array.map(), searching obj2 for a matching id in each object in obj1, we'd use Array.find() to get the corresponding value in obj2.
const obj1 = [ { id: 1, name: 'foo' }, { id: 2, name: 'bar' } ]
const obj2 = { A: { id: 1, externalId:'AAA' }, B: { id: 2, externalId:'BBB' } }
const result = obj1.map((obj) => {
const match = Object.values(obj2).find(el => el.id === obj.id);
return { id: match.externalId, name: obj.name };
});
console.log('Result:', result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Array.map works. Read values of obj2 as array to map to expected result. I suppose object keys A, B could be ignored.
const obj1 = [ { id: 1, name: 'foo' }, { id: 2, name: 'bar' } ];
const obj2 = { A: { id: 1, externalId:'AAA' }, B: { id: 2, externalId:'BBB' } };
const result = Object.values(obj2).map(({id, externalId}) => ({
id: externalId, name: obj1.find(item => item.id === id)?.name
}));
console.log(JSON.stringify(result));

Get the object with more properties from an array of objects

Assuming I have the following array in a JSON file:
[
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' }
]
How to get the object with more properties? In this example I should get the third item: { id: 3, name: 'foo', nick: 'bar' }
If there is another object with 3 properties, I can get two results or the last found, it doesn't matter, my purpose is to know all properties an object can have.
To cope with multiple results, you could use filter.
var data = [
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' },
{ id: 6, name: 'another 3', nick: '3'}
]
const mx = Math.max(...data.map(m => Object.keys(m).length));
const res = data.filter(f => Object.keys(f).length === mx)
console.log(res);
You can create an array and put values based on key length.
Since you want objects with most keys, you can get the last item.
var data = [
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' }
];
var res = data.reduce((a, c) => {
const len = Object.keys(c).length;
a[len] = a[len] || [];
a[len].push(c);
return a;
}, []).pop();
console.log(res);
You can use reduce and Object.keys() to return the object which has more length.
Try the following way:
var data = [
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' }
]
var res = data.reduce((a, c) => {
return Object.keys(a).length > Object.keys(c).length ? a : c;
})
console.log(res);
let biggestObj = {};
for(let el of array){
if(Object.keys(el).length > Object.keys(biggestObj).length){
biggestObj = el;
}
}
This should do the job!

Can destructuring assignment be used to effect a projection in CoffeeScript?

I'm having some trouble understanding destructuring assignment in CoffeeScript. The documentation contains a couple of examples which together seem to imply that renaming objects during assignment can be used to project (i.e. map, translate, transform) a source object.
I am trying to project a = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ] into b = [ { x: 1 }, { x: 2 } ]. I've tried the following without success; I've clearly misunderstood something. Can anyone explain whether this is possible?
My Poor Attempts That Don't Return [ { x: 1 }, { x: 2 } ]
a = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
# Huh? This returns 1.
x = [ { Id } ] = a
# Boo! This returns [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
y = [ { x: Id } ] = a
# Boo! This returns [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
z = [ { Id: x } ] = a
CoffeeScript's Parallel Assignment Example
theBait = 1000
theSwitch = 0
[theBait, theSwitch] = [theSwitch, theBait]
I understand this example as implying that variables can be renamed which in this case is used to perform a swap.
CoffeeScript's Arbitrary Nesting Example
futurists =
sculptor: "Umberto Boccioni"
painter: "Vladimir Burliuk"
poet:
name: "F.T. Marinetti"
address: [
"Via Roma 42R"
"Bellagio, Italy 22021"
]
{poet: {name, address: [street, city]}} = futurists
I understand this example as defining a selection of properties from an arbitrary object which includes assigning the elements of an array to variables.
Update: Using thejh's Solution to Flatten an Array of Nested Objects
a = [
{ Id: 0, Name: { First: 'George', Last: 'Clinton' } },
{ Id: 1, Name: { First: 'Bill', Last: 'Bush' } },
]
# The old way I was doing it.
old_way = _.map a, x ->
{ Id: id, Name: { First: first, Last: last } } = x
{ id, first, last }
# Using thejh's solution...
new_way = ({id, first, last} for {Id: id, Name: {First: first, Last: last}} in a)
console.log new_way
b = ({x} for {Id: x} in a) works:
coffee> a = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
[ { Id: 1, Name: 'Foo' },
{ Id: 2, Name: 'Bar' } ]
coffee> b = ({x} for {Id: x} in a)
[ { x: 1 }, { x: 2 } ]
coffee>
CoffeeScript Cookbook solves exactly the same problem as yours - solution is map:
b = a.map (hash) -> { x: hash.id }
or list comprehension:
c = ({ x: hash.id } for hash in a)
You can check this fiddle online on CoffeScript homepage (uses console.info to show results).
EDIT:
To make it destrutive just assign mapped variable a to itself:
a = a1 = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
a = a.map (hash) -> { x: hash.Id }
console.info a;
a1 = ({ x: hash.Id } for hash in a1)
console.info a1;

Categories