I have an two arrays of objects. My goal is to replace an object from the second array into the first one based upon 'id'. I have a working solution, but would like to extend it by adding the object to the first array if a value isnt found. Please advice.
function mergeById(arr) {
return {
with: function(arr2) {
return _.map(arr, item => {
return _.find(arr2, obj => obj.id === item.id) || item
})
}
}
}
var result = mergeById([{
id: '124',
name: 'qqq'
},
{
id: '589',
name: 'www'
},
{
id: '567',
name: 'rrr'
}
])
.with([{
id: '124',
name: 'ttt'
}, {
id: '45',
name: 'yyy'
}])
console.log(result);
/**
[
{
"id": "124",
"name": "ttt"
},
{
"id": "589",
"name": "www"
},
{
"id": "567",
"name": "rrr"
},
{
id: '45',
name: 'yyy'
}
]
**/
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
Please advice.
You need to filter the second array and add the values who have no common id.
function mergeById(arr) {
return {
with: function(arr2) {
return [
..._.map(arr, item => _.find(arr2, obj => obj.id === item.id) || item),
..._.filter(arr2, item => !_.some(arr, obj => obj.id === item.id))
];
}
}
}
var result = mergeById([{ id: '124', name: 'qqq' }, { id: '589', name: 'www' }, { id: '567', name: 'rrr' } ])
.with([{ id: '124', name: 'ttt' }, { id: '45', name: 'yyy' }]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
A shorter approach with a Map and single loops for every array.
function mergeById(array) {
const
add2map = (m, o) => m.set(o.id, o),
map = array.reduce(add2map, new Map);
return {
with: function(array2) {
return Array.from(array2
.reduce(add2map, map)
.values()
);
}
}
}
var result = mergeById([{ id: '124', name: 'qqq' }, { id: '589', name: 'www' }, { id: '567', name: 'rrr' } ])
.with([{ id: '124', name: 'ttt' }, { id: '45', name: 'yyy' }]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use _.differenceBy(arr2, arr, 'id') to find all items that appear in arr2 that doesn't have a counterpart in arr by id, and concat them to the results of the _.map() action.
Note: instead using _.find() (O(n)) on each iteration, iterate arr2 once with _.keyBy() (O(n)) to create a dictionary { [id]: item }, and then get the items in O(1).
const mergeById = arr => ({
with(arr2) {
const arr2Dict = _.keyBy(arr2, 'id')
return _.map(arr, item => arr2Dict[item.id] || item)
.concat(_.differenceBy(arr2, arr, 'id'))
}
})
const result = mergeById([{ id: '124', name: 'qqq' }, { id: '589', name: 'www' }, { id: '567', name: 'rrr' } ])
.with([{ id: '124', name: 'ttt' }, { id: '45', name: 'yyy' }])
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
You can replace/add in a single loop by concating both arrays, reducing to a Map, and just adding the items by id to the Map:
const mergeById = arr => ({
with(arr2) {
return Array.from(
[...arr, ...arr2]
.reduce((r, o) => r.set(o.id, o), new Map)
.values()
)
}
})
const result = mergeById([{ id: '124', name: 'qqq' }, { id: '589', name: 'www' }, { id: '567', name: 'rrr' } ])
.with([{ id: '124', name: 'ttt' }, { id: '45', name: 'yyy' }])
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
Related
The following code produces the desired result but is there a way to refactor it to eliminate the accumulator variable?
const data = [
{ id: "428", date: "2017-01-24" },
{ id: "526", date: "2022-01-01" },
{ id: "428", name: "George" },
{ id: "526", name: "Sam" },
{ id: "827", name: "Dan" }
];
const accumulator = {};
data.forEach(o => {
accumulator[o.id] = { ...accumulator[o.id] , ...o } || o;
});
console.log(accumulator);
The built-in method reduce already does this. Pass the accumulator as the second argument. The first argument in the callback is the accumulated value. The second is the current object in this iteration. Return value of callback is passed to next iteration as the accumulated value.
const data = [
{ id: "428", date: "2017-01-24" },
{ id: "526", date: "2022-01-01" },
{ id: "428", name: "George" },
{ id: "526", name: "Sam" },
{ id: "827", name: "Dan" },
];
const result = data.reduce((accumulator, o) => {
accumulator[o.id] = { ...accumulator[o.id] , ...o } || o;
return accumulator;
}, {});
console.log(result);
The || o part doesn’t make sense (objects are always truthy), and the repeated spread can become a serious performance trap. It’s also good practice to start with Object.create(null) when using an object as a map to avoid keys colliding with things on Object.prototype (even though it looks like you have numeric keys for the moment).
So, let’s restart from this more efficient and reliable version:
const data = [
{ id: "428", date: "2017-01-24" },
{ id: "526", date: "2022-01-01" },
{ id: "428", name: "George" },
{ id: "526", name: "Sam" },
{ id: "827", name: "Dan" }
];
const accumulator = Object.create(null);
data.forEach(o => {
Object.assign(accumulator[o.id] ??= {}, o);
});
console.log(accumulator);
With that in mind, here’s how you would use Array.prototype.reduce:
const data = [
{ id: "428", date: "2017-01-24" },
{ id: "526", date: "2022-01-01" },
{ id: "428", name: "George" },
{ id: "526", name: "Sam" },
{ id: "827", name: "Dan" }
];
const accumulator = data.reduce(
(accumulator, o) => {
Object.assign(accumulator[o.id] ??= {}, o);
return accumulator;
},
Object.create(null)
);
console.log(accumulator);
You can reduce the dataset using Array.prototype.reduce. Just find the previous object by the id key in the accumulator (acc) or use a new object, and spread the current object over it.
const data = [
{ id: "428", date: "2017-01-24" },
{ id: "526", date: "2022-01-01" },
{ id: "428", name: "George" },
{ id: "526", name: "Sam" },
{ id: "827", name: "Dan" }
];
const reduceBy = (data, predicate) =>
data.reduce((acc, obj) =>
(key => ({
...acc,
[key]: {
...(acc[key] ?? {}),
...obj
}
}))
(typeof predicate === 'string'
? obj[predicate]
: predicate(obj)
), {});
const reducedData = reduceBy(data, ({ id }) => id); // or reduceBy(data, 'id')
console.log(reducedData);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Given an array of json object like this below, (the json object such as "name2" and "name4" will definitely have only one key-value)
[
{
abc: 123,
id: '18263322',
name: 'name1'
},
{ name: 'name2' },
{
abc: 456,
id: '18421634',
name: 'name3'
},
{ name: 'name4' }
]
How can I subset this so that I have two array of json objects:
[
{
abc: 123,
id: '18263322',
name: 'name1'
},
{
abc: 456,
id: '18421634',
name: 'name3'
}
]
and
[
{ name: 'name2' },
{ name: 'name4' }
]
You can use reduce here
const arr = [
{
abc: 123,
id: "18263322",
name: "name1",
},
{ name: "name2" },
{
abc: 456,
id: "18421634",
name: "name3",
},
{ name: "name4" },
];
const [single, multiple] = arr.reduce((acc, curr) => {
Object.keys(curr).length === 1 ? acc[0].push(curr) : acc[1].push(curr);
return acc;
},[[], []]
);
console.log(single);
console.log(multiple);
You can also do something like
const [single, multiple] = arr.reduce((acc, curr) => {
acc[Object.keys(curr).length === 1 ? 0 : 1].push(curr);
return acc;
},[[], []]);
using filter
const arr = [
{
abc: 123,
id: "18263322",
name: "name1",
},
{ name: "name2" },
{
abc: 456,
id: "18421634",
name: "name3",
},
{ name: "name4" },
];
const single = arr.filter((o) => Object.keys(o).length === 1);
const multiple = arr.filter((o) => Object.keys(o).length !== 1);
console.log(single);
console.log(multiple);
I have two array of objects and I need to introduce the name in a new array where listOfRegistries.Id === listOfTargets.regId
this.listOfRegistries = [
{ id: '123', name: 'name1' },
{ id: '245', name: 'name2' },
];
this.listOfTargets = [
{ regId: '123',key: 'value1' },
{ regId: '245', key: 'value2' },
];
I need to achieve this:
this. listOfTargetsNew = [
{ regId: '123',key: 'value1', name: 'name1' },
{ regId: '245', key: 'value2', name: 'name2' },
];
This is what I'm trying with no result
this.listOfTargetsNew = this.listOfTargets.map((el, index)=> {
if( this.listOfRegistries[index].id === el.regId) {
el['name'] = this.listOfRegistries[index].name;
return el;
}
});
Thank you very much for your responses.
You can easily map over the array and find the correct name via the find method.
const listOfRegistries = [
{ id: '123', name: 'name1' },
{ id: '245', name: 'name2' },
];
const listOfTargets = [
{ regId: '123',key: 'value1' },
{ regId: '245', key: 'value2' },
];
const listOfTargetsNew = listOfTargets.map((obj) => {
const registry = listOfRegistries.find(({ id }) => id === obj.regId);
return {...obj, name: registry.name};
});
console.log(listOfTargetsNew);
You could store the registries in an object and map the other array with addtional property.
const
listOfRegistries = [{ id: '123', name: 'name1' }, { id: '245', name: 'name2' }],
listOfTargets = [{ regId: '123', key: 'name1' }, { regId: '245', key: 'name2' }],
registries = Object.fromEntries(listOfRegistries.map(({ id, name }) => [id, name])),
listOfTargetsNew = listOfTargets.map(o => ({ ...o, name: registries[o.regId] }));
console.log(listOfTargetsNew);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have array of object like this
const data = [
{
name: "John",
transaction: "10/10/2010",
item: "Bag"
},
{
name: "Steven",
transaction: "31/10/2020",
item: "Shoe"
},
{
name: "John",
transaction: "18/06/2019",
item: "Sock"
}
]
you can see that the name of object in that array has duplicate name but different transaction
and then I want the result like this :
const result = [
{
name: "John",
transactions: [
{
date: "10/10/2010",
item: "Bag"
},
{
date: "18/06/2019",
item: "Sock"
}
]
},
{
name: "Steven",
transactions: [
{
date: "31/10/2020",
item: "Shoe"
}
]
},
]
so the new array recored the new transactions of the same person
the code for this is:
const data = [
{
name: "John",
transaction: "10/10/2010",
item: "Bag"
},
{
name: "Steven",
transaction: "31/10/2020",
item: "Shoe"
},
{
name: "John",
transaction: "18/06/2019",
item: "Sock"
}
]
let Transactions = []
data.forEach(data => {
Transactions.some(t => {
if(t.name === data.name){
t.transactions.push({date:data.transaction,item:data.item})
return;
}
})
Transactions.push({
name:data.name,
transactions:[
{date:data.transaction,item:data.item}
]
})
console.log(Transactions);
})
array.some is better than forEach loop i think.so decided to stick with that.
Please try the following example
const data = [
{
name: "John",
transaction: "10/10/2010",
item: "Bag",
},
{
name: "Steven",
transaction: "31/10/2020",
item: "Shoe",
},
{
name: "John",
transaction: "18/06/2019",
item: "Sock",
},
];
const output = data.reduce((previousValue, { name, transaction, item }) => {
const index = previousValue.findIndex((entry) => entry.name === name);
if (index === -1) {
previousValue = [
...previousValue,
{
name: name,
transactions: [{ date: transaction, item }],
},
];
} else {
previousValue[index].transactions = previousValue[
index
].transactions.concat({
date: transaction,
item,
});
}
return previousValue;
}, []);
console.dir(output, { depth: null, color: true });
See
Array.prototype.reduce()
Array.prototype.concat()
Array.prototype.findIndex()
a simple reduce do that
const data =
[ { name: 'John', transaction: '10/10/2010', item: 'Bag' }
, { name: 'Steven', transaction: '31/10/2020', item: 'Shoe' }
, { name: 'John', transaction: '18/06/2019', item: 'Sock' }
]
const result = data.reduce((a,{name,transaction:date,item})=>
{
let x = a.find(e=>e.name===name)
if (!x)
{
let n = a.push({name, transactions:[]}) -1
x = a[n]
}
x.transactions.push({date,item})
return a
},[])
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
shorter version
const result = data.reduce((a,{name,transaction:date,item})=>
{
let x = a.find(e=>e.name===name) || (a[a.push({name, transactions:[]}) -1])
x.transactions.push({date,item})
return a
},[])
You could do that in a functional way to make it readable, below worked solution is using ramdajs
const data = [
{
name: 'John',
transaction: '10/10/2010',
item: 'Bag'
},
{
name: 'Steven',
transaction: '31/10/2020',
item: 'Shoe'
},
{
name: 'John',
transaction: '18/06/2019',
item: 'Sock'
}
]
const result = pipe(
groupBy(obj => obj.name),
mapObjIndexed((groupObjs, groupName) => ({
name: groupName,
transactions: map(
groupObj => ({
date: groupObj.transaction,
item: groupObj.item
}),
groupObjs
)
})),
values
)(data)
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script>const { groupBy, mapObjIndexed, pipe, map, values } = R</script>
Here is the link to the ramdajs doc
How about using lodash's _.groupBy() function?
const data = [
{
name: "John",
transaction: "10/10/2010",
item: "Bag",
},
{
name: "Steven",
transaction: "31/10/2020",
item: "Shoe",
},
{
name: "John",
transaction: "18/06/2019",
item: "Sock",
}
]
const result = _.groupBy(data, "name")
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
I am hoping there is a way to use .filter() for a nested array to make things a little easier on the eyes.
Here is a basic example of what I want.
[
{
people: [
{
name: 'sam',
id: 1
}
{
name: 'john',
id: 2
}
]
},
people: [
{
name: 'marry',
id: 1
},
{
name: 'paul',
id: 1
}
]
},
...
]
can I use .filter to get all the people with id of 1?
I can do it by using .filter with a
for(...){
for(...){
...
}
...
}
but I don't really want nested for loops as this will add unnecessary complexity.
Edit: I would like output to look like a single array of the nested people object
[{
name: 'sam',
id: 1
},
{
name: 'john',
id: 1
},
{
name: 'john',
id: 1
]
You could use Array#filter and Array#map.
var arr = [{people:[{name:'sam',id:1},{name:'john',id:2}]},{people:[{name:'marry',id:1},{name:'paul',id:1}]}],
res = [].concat(...arr.map(v => v.people.filter(c => c.id == 1)));
console.log(res);
filter can certainly be part of the solution:
const people = [];
for (const obj of data) {
people.push(...obj.people.filter(p => p.id === 1));
}
const data = [
{
people: [
{
name: 'sam',
id: 1
},
{
name: 'john',
id: 2
}
]
},
{
people: [
{
name: 'marry',
id: 1
},
{
name: 'paul',
id: 1
}
]
}
];
const people = [];
for (const obj of data) {
people.push(...obj.people.filter(p => p.id === 1));
}
console.log(people);
.as-console-wrapper {
max-height: 100% !important;
}
(You could use data.forEach instead of the for-of loop if you prefer.)
You could use an ES5 approach with concatinating the filtered arrays.
var array = [{ people: [{ name: 'sam', id: 1 }, { name: 'john', id: 2 }] }, { people: [{ name: 'marry', id: 1 }, { name: 'paul', id: 1 }] }],
result = [].concat.apply([], array.map(function (a) {
return a.people.filter(function (p) {
return p.id === 1;
});
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }