Find and replace object in array (based on id) - javascript

Got a bit of a puzzle here...I want to loop through allItems and return allItems but replace with any newItems that matches its id. How can I look for a match on id and then replace it with the correct object into the array?
const allItems = [
{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
}
]
const newItems = [
{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
]
What I tried so far:
for(let i = 0; i < allItems.length; i++) {
if(newItems.indexOf(allItems[i].id) > -1){
allItems[i] = newItems
}
}
How can I get the position of the object in newItems and then replace it into allItems?

Use Array.map and Array.find():
const allItems = [
{ 'id': 1, 'category_id': 1, 'text': 'old' },
{ 'id': 2, 'category_id': 1, 'text': 'old' }
];
const newItems = [
{ 'id': 1, 'category_id': 1, 'text': 'new', 'more_info': 'abcd' },
{ 'id': 2, 'category_id': 1, 'text': 'new', 'more_info': 'abcd' }
];
const result = allItems.map(x => {
const item = newItems.find(({ id }) => id === x.id);
return item ? item : x;
});
console.log(result);
This can even be shortened by using a logical or to return the original item when the call to find returns undefined:
const result = allItems.map(x => newItems.find(({ id }) => id === x.id) || x);
Regarding your code, you can't use indexOf since it only compares primitive values or references in the case of arrays and objects.

Just use map like so:
const allItems = [{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
}
];
const newItems = [{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
];
const replacedItems = allItems.map(e => {
if (newItems.some(({ id }) => id == e.id)) {
return newItems.find(({ id }) => id == e.id);
}
return e;
});
console.log(replacedItems);

Just using a simple Array.map and a method to check the other array.
const allItems = [
{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
},
{
'id': 3,
'category_id': 1,
'text': 'old_same'
}
]
const newItems = [
{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
]
const findNewItem = (oldItem) => newItems.find(item => item.id === oldItem.id);
let arr = allItems.map(item => findNewItem(item)||item);
console.log(arr);

Depending on how large your input arrays are, you might consider adding an intermediate step to build a "map" of your newItems, where the key of this map is the id of the item.
Using a mapping such as this would allow for much faster reconciliation (and replacement) of items from the allItems array with items in the newItems array:
function replaceItemsOnId(items, replacement) {
/*
Create a map where the key is the id of replacement items.
This map will speed up the reconciling process in the
subsequent "map" stage
*/
const replacementMap = replacement.reduce((map, item) => {
map[ item.id ] = item
return map;
}, {})
/*
Map the items to a new array where items in the result array
are either clones of the orignals, or replaced by items of
"replacement" array where thier id matches the item being mapped
*/
return items.map(item => {
const use = replacementMap[ item.id ] || item
return { ...use }
})
}
const allItems = [
{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
}
]
const newItems = [
{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
]
console.log(replaceItemsOnId(allItems, newItems))

You could get a reference to the object you are looking for by id but that would not be enough because then you would need the index in order to replace it in allItem (allItem[index] = newItem), so I suggest finding that index instead first like this:
for (let item of newItems) {
let indexNewItem = allItems.map(function (e) { return e.id }).indexOf(item.id);
allItems[indexNewItem] = item
}
this should work

The answers for this question mostly have the code iterating through all the items in the original array to see which should be replaced. If the original array is large, and you don't need to replace many, it might be better performance to change the logic. I only needed to replace one item in an array (customers). I did it like this:
const index = customers.findIndex(x => x.Id == editedCust.Id);
if (index >= 0)
customers[index] = editedCust;
You could iterate through all the items in newItems and do the above for each.

Related

How to display one item per `type` attribute in a list?

I am writing an Angular 8 application. In one of the views there is the option to display a list of items of class Product and this list may be filtered in several ways.
export class Product {
constructor(
public id: number,
public type: number
) {
}
}
What I want to do:
Among the attributes of Product there is type. How can I filter them in order to obtain exactly one item per type? What I would do is use this function, but I don't know how to write the logical statement in parentheses.
filterItemsOnePerType() {
this.filteredProducts = Object.assign([], this.products).filter(
item => (... item.type ...)
);
}
Notice that it's not really important the item that is going to be selected. What is important is that in the final view I won't see any duplicate among types.
EXAMPLE
I have a list of products:
[{id: 1, type:1},
{id: 2, type:1},
{id: 3, type:1},
{id: 4, type:2},
{id: 5, type:2},
{id: 6, type:3},
{id: 7, type:2}]
What I want to obtain is therefore the following list:
[{id: 1, type:1},
{id: 4, type:2},
{id: 6, type:3}]
this.filteredProducts = Object.assign([], this.products).filter(
(types => item => !types.has(item.type) && types.add(item.type))(new Set)
);
You might want to use a Set to exclude duplicates.
You could use the Array.reduce method to filter it out.
const array = [
{ id: 1, type: 1 },
{ id: 2, type: 1 },
{ id: 3, type: 1 },
{ id: 4, type: 2 },
{ id: 5, type: 2 },
{ id: 6, type: 3 },
{ id: 7, type: 2 }
];
const result = array.reduce((result, item) => {
const isFound = result.find(({ type }) => item.type === type);
if (isFound) {
return result;
}
return [...result, item];
}, []);
console.log(result);
Going back to your original code, you'd do something like the following:
filterItemsOnePerType() {
this.filteredProducs = Object.assign([], this.producs).reduce(
(result, item) => {
const isFound = result.find(
({ type }) => item.type === type
)
if (isFound) return result
return [...result, item]
}, []
)
}
Hope this helps.

How can i merge objects on the basis of same value in array of objects

I have array of multiple objects. Every object have a key "id". I want to merge objects On the basis of same value of "id".
var obj = [{'id': 1, 'title': 'Hi'}, {'id': 1, 'description': 'buddy'}, {'id': 2, 'title': 'come'}, {'id': 2, 'description': 'On'}]
And i want output something like that
var new_obj = [{'id': 1, 'title': 'Hi' 'description': 'buddy'}, {id: 2, 'title': 'come', 'description': 'on'}]
This way works :
let toMerge = [{'id': 1, 'title': 'Hi'}, {'id': 1, 'description': 'buddy'}, {'id': 2, 'title': 'come'}, {'id': 2, 'description': 'On'}]
let merged = []
for ( let object of toMerge ) {
// if object not already merged
if(!merged.find(o => o.id === object.id)) {
// filter the same id's objects, merge them, then push the merged object in merged array
merged.push(toMerge.filter(o => o.id === object.id).reduce((acc, val) => {return {...acc, ...val}}))
}
}
You could reduce the array by finding an object with the same id and assign the object to found or take a new object.
var array = [{ id: 1, title: 'Hi' }, { id: 1, description: 'buddy' }, { id: 2, title: 'come' }, { id: 2, description: 'On' }],
result = array.reduce((r, o) => {
var temp = r.find(({ id }) => o.id === id);
if (!temp) r.push(temp = {});
Object.assign(temp, o);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a solution
const data = [
{
id: 1,
title: "Hi"
},
{
description: "buddy",
id: 1
},
{
id: 2,
title: "come"
},
{
description: "On",
id: 2
}
]
const f = (data) =>
Object.values(data.reduce(
(y, x) => ({
...y,
[x.id]: {
...x,
...(y[x.id] || {})
},
}),
{},
))
console.log(f(data))
Here's a solution using Ramda.js
const data = [
{
id: 1,
title: "Hi"
},
{
description: "buddy",
id: 1
},
{
id: 2,
title: "come"
},
{
description: "On",
id: 2
}
]
const f = R.compose(R.values, R.reduceBy(R.merge, {}, R.prop('id')))
console.log(f(data))
<script src="//cdn.jsdelivr.net/npm/ramda#latest/dist/ramda.min.js"></script>

How would you unify two similar array declarations

I have two similar array declarations:
allFalseOrTrue: any[] = [{'id': -1, 'name': 'All'}, {'id': 0, 'name': 'No'}, {'id': 1, 'name': 'Yes'}];
bothFalseOrTrue: any[] = [{'id': -1, 'name': 'Both'}, {'id': 0, 'name': 'No'}, {'id': 1, 'name': 'Yes'}];
Would you recommend to unify the arrays?
I mean I want to have some sort of base thing that I can use to construct my two other and similar arrays to avoid copypaste.
Say if I have something like a source array or enum and just based on that able to construct my other arrays.
How about use a function to create the array:
type Name = "All" | "Both" | "No" | "Yes";
function createOptions(...names: Name[]) {
return names.map((name, i) => ({ id: i - 1, name }));
}
const all_no_yes = createOptions("All", "No", "Yes");
const both_no_yes = createOptions("Both", "No", "Yes");
Or you could create a single array and filter down to the ones you want:
const all = [{ id: -1, name: "All" }, { id: -1, name: "Both" }, { id: 0, name: "No" }, { id: 1, name: "Yes" }];
const all_no_yes = all.filter(item => item.name != "Both");
const both_no_yes = all.filter(item => item.name != "All");
Also note that using any[] is blurring the type, if you allow the type to be inferred (as above with no type annotation) you'll get arrays with items of shape { id, name }, which is better.

javascript foreach find and update

Let say I have an array of tree type data
[ { 'id': 1, 'name': 'root', 'parent_id': 1 },
{ 'id': 2, 'name': 'level 1', 'parent_id': 1 },
{ 'id': 3, 'name': 'level 2', 'parent_id': 1 },
{ 'id': 4, 'name': 'level 2.1', 'parent_id': 3 } ]
is it possible to update the array to
[ { 'id': 1, 'name': 'root', 'parent_id': 1, 'parent_name': 'root' },
{ 'id': 2, 'name': 'level 1', 'parent_id': 1, 'parent_name': 'root' },
{ 'id': 3, 'name': 'level 2', 'parent_id': 1, 'parent_name': 'root' },
{ 'id': 4, 'name': 'level 2.1', 'parent_id': 3, 'parent_name': 'level 2' } ]
using lodash forEach and find?
_.forEach(arr, function (o) {
let item = _.find(arr, {'id': o.parent_id})
o.parent_name = item.name
})
my problem is inside the function of forEach, it has no idea what arr is, I got it working replacing the forEach with a plain for loop.
for (i = 0; i < arr.length; i++) {
let item = _.find(arr, {'id': arr[i].parent_id})
arr[i].parent_name = item.name
}
So wondering if there're any more elegant way to accomplish this
Here's another way to solve your problem using a hash to improve performance:
let hash = _.keyBy(data, 'id');
This will create an object where the keys are the id:
{
1: { id: 1, name: 'root' ... },
2: { id: 2, name: 'level 1' ... },
...
}
Then just iterate over the data to set the parent_name using the hash:
_.each(data, item => item.parent_name = _.get(hash, [item.parent_id, 'name']));
Try reading the documentation on _.forEach.
The iteratee is invoked with three arguments: (value, index|key, collection).
You need to setup your forEach like so:
_.forEach(arr, function (o, idx, arr) {
let item = _.find(arr, {'id': o.parent_id})
o.parent_name = item.name
});
you can use .map to change object as-
const arr =[ { 'id': 1, 'name': 'root', 'parent_id': 1 },
{ 'id': 2, 'name': 'level 1', 'parent_id': 1 },
{ 'id': 3, 'name': 'level 2', 'parent_id': 1 },
{ 'id': 4, 'name': 'level 2.1', 'parent_id': 3 } ];
const newArr = arr.map((item) => {
if(name === 'root') return Object.assign({}, item, {parent_name: 'root'});
const findIndexOfParent = arr.findIndex((pItem) => pItem.id == item.parent_id);
if(findIndexOfParent > -1){
return Object.assign({}, item, {parent_name: arr[findIndexOfParent].name});
}
});
console.log(newArr);

ImmutableJS: merge two list of objects, without duplicating them

Supposing I have the below:
var allFoods = Immutable.List();
var frenchFood = Immutable.List([
{
'type': 'french fries',
'price': 3
},
{
'type': 'petit gateau',
'price': 40
},
{
'type': 'croissant',
'price': 20
},
]);
var fastFood = Immutable.List([
{
'type': 'cheeseburger',
'price': 5
},
{
'type': 'vegan burger',
'price': 20
},
{
'type': 'french fries',
'price': 3
}
]);
I want to merge both lists, in a way that I also remove dupes (in this case, french fries), so the expected result would be:
{
'type': 'french fries', // keep the first french fries
'price': 3
},
{
'type': 'petit gateau',
'price': 40
},
{
'type': 'croissant',
'price': 20
},
{
'type': 'cheeseburger',
'price': 5
},
{
'type': 'vegan burger',
'price': 20
}
What I'm trying (doesn't remove dupes):
allFoods = frenchFood.concat(fastFood);
allFoods = allFoods.filter(function(item, pos) {
return allFoods.indexOf(item) === pos;
});
Which returns arrays merged, but still duplicated.
What am I missing?
const allFoods = frenchFood.concat(fastFood.filter((item) =>
frenchFood.indexOf(item) < 0
));
I would use reduce
var result = frenchFood.concat(fastFood).reduce( (reduction, food) => {
if(reduction[food.type]) {
return reduction;
} else {
return reduction.set([food.type], food);
}
}, new Immutable.Map()).valueSeq().toList();
I would highly encourage you to not nest js objects inside immutable structures. Better to wrap those objects in an Immutable.Map() or do Immutable.fromJS(yourJsObj).
Least amount of code
const results = Immutable.Set(frenchFood).union(Immutable.Set(fastFood));
However #rooftop answer fastest
https://jsperf.com/union-vs-concat-immutable
I found a best solution (for me) on medium, link to origin answer is dead: https://medium.com/#justintulk/merging-and-deduplicating-data-arrays-with-array-reduce-efaa4d7ef7b0
const arr1 = [
{ id: 1, name: 'Array 1-1' },
{ id: 2, name: 'Array 1-2' },
{ id: 3, name: 'Array 1-3' }
]
const arr2 = [
{ id: 1, name: 'Array 2-1' },
{ id: 3, name: 'Array 2-3' },
{ id: 4, name: 'Array 2-4' }
]
const mergeArrObjectsUnique = (currentArr, newArr) => {
let obj = {}
currentArr.forEach(item => {
obj[item.id] = item
})
newArr.forEach(item => {
obj[item.id] = item
})
let result = [];
for(let p in obj) {
if(obj.hasOwnProperty(p))
result.push(obj[p])
}
console.log('result: ', result)
return result
}
mergeArrObjectsUnique(arr1, arr2)

Categories

Resources