Find duplicates and update their values in JavaScript array - javascript

I was originally iterating through my array to remove any duplicates and only to return the unique values. However, I was wondering if it was possible to identify the duplicates and update their label in the original array in order to keep some of the original data that they contain. As of now, I been removing duplicates by doing the following:
const removeDuplicates = arrayCollection
.filter((link, i) => i === arrayCollection.findIndex((l) => (
l.collectionTitle === link.collectionTitle
)));
What I would like to do is for the collection title to attach something like "Link (0), Link (1)" if they share the same title. Overall, I'm trying to find a way to not remove the duplicates after finding them but rather to append something to differentiate them in the original array collection.
a sample of arrayCollection:
[
{id: '1', collectionTitle: '456'},
{id: '2', collectionTitle: '123'},
{id: '3', collectionTitle: '123'},
];
the desired output of updating duplicates:
[
{id: '1', collectionTitle: '456'},
{id: '2', collectionTitle: '123 (0)'},
{id: '3', collectionTitle: '123 (1)'},
];

First I group by the title so I can count how many of each. I use the famous array reduce method for that. Then use that grouped object to reconstruct the names.
var arrayCollection = [
{id: 1, collectionTitle: "Hello"},
{id: 2, collectionTitle: "Goodbye"},
{id: 3, collectionTitle: "Hello"},
];
var obj = arrayCollection.reduce(function(agg, item) {
agg[item.collectionTitle] = agg[item.collectionTitle] || []
agg[item.collectionTitle].push(item)
return agg;
}, {})
var result = [];
Object.values(obj).forEach(function(arr) {
if (arr.length == 1) {
result.push(arr[0])
} else {
for (var i = 0; i < arr.length; i++) {
arr[i].collectionTitle += " (" + i + ")";
result.push(arr[i])
}
}
})
console.log(result)

One of the fastest and most customizable ways to do this:
let result = [];
let dup = {}
for (let x = 0; x < arr.length; x++){
if (dup[arr[x]]) {
result.push(‘whatever u want’);
} else {
result.push(arr[x]);
dup[arr[x]] = true;
}
}

If you don't mind that first duplicate is not labelled, this will work:
const input = [
{id: '1', collectionTitle: '456'},
{id: '2', collectionTitle: '123'},
{id: '3', collectionTitle: '123'},
]
const result = input.map((current, i, array) => {
// Number of duplicates so far
const duplicatesCount = array
.slice(0, i)
.filter(el => el.collectionTitle.startsWith(current.collectionTitle))
.length
return {
id: current.id,
collectionTitle: duplicatesCount > 0
? `${current.collectionTitle} (${duplicatesCount})`
: current.collectionTitle,
}
})
console.log(result)
It is not a good solution performance-wise (nested .slice and .filter inside .map), but the code itself is relatively concise.

Related

How to transform array of objects to new array of objects and count the keys [duplicate]

This question already has answers here:
JS converting array of object into another array of objects, using keys of the first one
(4 answers)
Closed 7 months ago.
The code is something like this:
let inputArray=[
{Id: '1', Name: 'Ani'},
{Id: '2', Name: 'George'},
{Id: '4', Name: 'George'},
{Id: '5', Name: 'Ani'}];
I need to make a new array with objects in the format :
let result=[
{ Name: 'Ani', count:2},
{ Name: 'George',count:2},
{ Name: 'Henry',count:1}];
Any idea please? :)
You can try to use a Hash & Array.reduce here.
const hash = inputArray.reduce((memo, item) => {
memo[item.Name] = memo[item.Name] || {name: item.Name, count: 0}
memo[item.Name].count++
return memo
}, {})
result = Object.values(hash)
You can use this function to count the repetition of any field given the field name
let inputArray=[
{Id: '1', Name: 'Ani'},
{Id: '2', Name: 'George'},
{Id: '4', Name: 'George'},
{Id: '5', Name: 'Ani'}];
function countFields(input, field){
const count = {};
input.forEach(e =>{
const v = e[field];
if(!count[v])count[v] = 0;
count[v]++;
})
return count;
}
const result = countFields(inputArray, "Name")
console.log(result)
Another solution with reduce:
let inputArray = [
{Id: '1', Name: 'Ani'},
{Id: '2', Name: 'George'},
{Id: '4', Name: 'George'},
{Id: '5', Name: 'Ani'}];
const counts = inputArray.reduce((acc, curr) => {
const valIndex = acc.findIndex(val => val.name === curr.Name)
if(valIndex >= 0) {acc[valIndex].count += 1}
else {
acc.push({name: curr.Name, count: 1})
}
return acc
}, [])

Filter array of objects but return only specific properties - JS [duplicate]

This question already has answers here:
Extract certain properties from all objects in array
(5 answers)
Closed 4 months ago.
How to filter an array of objects with a condition and return only specific properties of filtered objects?
I know we can use filter followed by map to achieve this. But I am looking for more simple solution.
For ex:
let arr = [{name:"lala", id: 1}, {name: "coco", id:2}, {name: "lala", id:3}]
Suppose if I want only ids of name "lala".
Output should be,
[{id: 1}, {id: 3}]
The next simplest would be reduce
let arr = [{name:"lala", id: 1}, {name: "coco", id:2}, {name: "lala", id:3}];
console.log(
arr.reduce((values, value) =>
{
if (value.name === 'lala') values.push({ id: value.id });
return values;
}, [])
);
You can simply use Array.prototype.reduce to combine both mapping and filtering in the same operation. If you want to make it super concise, you can use object destructuring in the second argument of the reduce callback:
let arr = [{name:"lala", id: 1}, {name: "coco", id:2}, {name: "lala", id:3}];
let filteredMappedArr = arr.reduce((acc, { name, id }) => {
if (name === 'lala')
acc.push({ id });
return acc;
}, []);
console.log(filteredMappedArr);
filter followed by map is probably the most readable solution, but if you're looking to do it all in one step, you're looking at the classic for loop or using reduce.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
You can do it by using filter and map;
let arr = [{name:"lala", id: 1}, {name: "coco", id:2}, {name: "lala", id:3}]
let res = arr.filter(item => item.id % 2 === 1).map(item => ({id: item.id}))
console.log(res);
You could take Array#flatMap and return either a new obejct or an empty array which has no value for flattening.
let array = [{ name: "lala", id: 1 }, { name: "coco", id: 2 }, { name: "lala", id: 3 }],
result = array.flatMap(({ id, name }) => name === 'lala' ? [{ id }] : []);
console.log(result);
using .filter() and .map() functions:
let arr = [{name:"lala", id: 1}, {name: "coco", id:2}, {name: "lala", id:3}]
let newArr = arr.filter((elm) => (elm.name === 'lala')).map( (elm) => {return {id:elm.id}});
console.log(newArr);
let arr = [
{ name: "lala", id: 1 },
{ name: "coco", id: 2 },
{ name: "lala", id: 3 },
];
let a = [];
arr.filter(({ name, id }) => {
if (name === "lala") {
a.push({ id });
}
});
console.log(a);
with filter we check for the condition where name matches 'lala' if yes then we push id to new array...that's simple

Convert a string tree to array

I've got objects with an id as a string. Each object can be the child of another object. Relations can be guessed from IDs. For exemple:
[
{ id: '1:2:6', ids: ['1', '2', '6'] },
{ id: '1:4', ids: ['1', '4'] },
{ id: '1', ids: ['1'] },
{ id: '1:2', ids: ['1', '2'] },
]
In this exemple, root object is id: 1, which has 2 childrens id: 1:2 and id: 1:4. Finaly, id: 1:2 has a children id: 1:2:6.
I would like to convert this array to another array where childrens are embeded into parents, so the previous array would result in:
[
{
id: '1',
children: [
{
id: '1:2',
children: [
{ id: '1:2:6', children: [] }
],
},
{
id: '1:4',
children: [],
}
],
}
]
I can use ES6. I tried for hours to find a solution using all sort of loops but I can't figure this out. Any help would be appreciated!
You could iterate the objects and reduce ids by looking for an object at the actual level. If not found create a new object. Then return the children.
var data = [{ id: '1:2:6', ids: ['1', '2', '6'] }, { id: '1:4', ids: ['1', '4'] }, { id: '1', ids: ['1'] }, { id: '1:2', ids: ['1', '2'] }],
tree = data.reduce((r, { ids }) => {
ids.reduce((t, _, i, a) => {
var id = a.slice(0, i + 1).join(':'),
temp = t.find(o => o.id === id);
if (!temp) t.push(temp = { id, children: [] });
return temp.children;
}, r);
return r;
}, []);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think the iterative approach is more readable so maybe provided solution will help you understand how it is done (though it is lightly inspired by Nina's answer)
So what we do is iterate over all objects in the list.
Initially, for each object, we set the current nodeList as the final tree. And we iterate over the length of ids.
First, we create the id form the list of ids by dividing the ids array into incrementally larger chunks with slice (['1'], ['1','2'], ['1', '2', '6') and concatenate to a string with :. So we get 1, 1:2, 1:2:6 ids for the first item.
Next, we find a node in currentNodelist by previously constructed id. If we cannot find the node that means we have not added it yet so we need to create and add it (If we find it then we don't need to do add it).
In the next step, we need to go deeper into the tree so we assign the currently created(or the one that we found) node's children as currentNodelist. With this, we traverse the tree deeper by provided ids.
let objs = [
{ id: '1:2:6', ids: ['1', '2', '6'] },
{ id: '1:4', ids: ['1', '4'] },
{ id: '1', ids: ['1'] },
{ id: '1:2', ids: ['1', '2'] },
];
let tree = [];
for (let i = 0; i < objs.length; i++) {
let obj = objs[i];
let currentNodeList = tree;
for (let j = 0; j < obj.ids.length; j++) {
let id = obj.ids.slice(0, j + 1).join(':');
let currentNode = currentNodeList.find((node) => node.id === id);
if (!currentNode) {
currentNode = {id, children: []};
currentNodeList.push(currentNode);
}
currentNodeList = currentNode.children;
}
}
console.log(tree);
I created a simple gif that shows what is happening in the first 2 iterations. The arrow is pointing to currentListNode.
Figure out your algorithm "on paper" first. Let's start with an empty tree and take the first entry:
[1, 2, 6].
Node 1: add 1 to the tree - it's now the root and the last visited node.
Node 2: add child 2 to node 1.
Node 6: add child 6 to node 2.
When processing the next entry, [1, 4], 1 is already in the tree - just add 4 to it.
When processing the last entry, [1, 2], be mindful that 2 is also already in the tree.
P.S. It's "children" not "childs".
Building a tree using recursive algorithms
var jsonTree = [{ id: '1:2:6', ids: ['1', '2', '6'] },{ id: '1:4', ids: ['1', '4'] },{ id: '1', ids: ['1'] },{ id: '1:2', ids: ['1', '2'] },]
var newJsonTree = [];
var currentElement = {id: '1',childs: []}
newJsonTree.push(currentElement)
function buildTree(jsonTree, currentElement){
for(var i=0;i<jsonTree.length;i++){
var parent = jsonTree[i];
for(var j=0;j<jsonTree.length;j++){
var child = jsonTree[j];
if(child['visited'] != true && child['id'] != currentElement['id'] && child['id'].indexOf(currentElement['id']) == 0 ){
if(child['id'].split(":").length == currentElement['id'].split(":").length+1){
var newElement = {}
newElement['id'] = child['id'];
newElement['childs'] = [];
currentElement['childs'].push(newElement);
child['visited'] = true;
buildTree(jsonTree, newElement);
}
}
}
}
}
buildTree(jsonTree, currentElement);
document.write(JSON.stringify(newJsonTree));
result:
[{"id":"1","childs":[{"id":"1:4","childs":[]},{"id":"1:2","childs":[{"id":"1:2:6","childs":[]}]}]}]

How can I return the property values of a nested JavaScript array nested in an object from JSON?

Given a JS array containing many objects which all contain arrays:
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
How do I efficiently extract the inner most array (pages) values into an array?
var dataArray = [
{url: "www.abc.com", title: "abc"},
{url: "www.123.com", title: "123"},
{url: "www.xyz.com", title: "xyz"}
]
The easiest way to do this is to use Array#map like so:
var dataArray = data.map(function(o){return o.pages});
If pages is an array of objects (not a single object), this will result in an array of arrays, which you will need to flatten out for example using Array#reduce:
dataArray = dataArray.reduce(function(a,b){return a.concat(b)}, []);
You are looking for a flatMap
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
const concat = (xs, ys) => xs.concat(ys);
const prop = x => y => y[x];
const flatMap = (f, xs) => xs.map(f).reduce(concat, []);
console.log(
flatMap(prop('pages'), data)
);
If by "efficiently" you actually mean "concisely", then
[].concat(...data.map(elt => elt.pages))
The data.map will result in an array of pages arrays. The [].concat(... then passes all the pages arrays as parameters to concat, which will combine all of their elements into a single array.
If you are programming in ES5, the equivalent would be
Array.prototype.concat.apply([], data.map(function(elt) { return elt.pages; }))
Here's a working example on how to achieve what you want:
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}, {url:"www.google.com", title: "Google"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
var arr = Array();
var arr2 = Array();
// You can either iterate it like this:
for (var i = 0; i < data.length; i++) {
// If you only want the first page in your result, do:
// arr.push(data[i].pages[0]);
// If you want all pages in your result, you can iterate the pages too:
for (var a = 0; a < data[i].pages.length; a++) {
arr.push(data[i].pages[a]);
}
}
// Or use the array map method as suggested dtkaias
// (careful: will only work on arrays, not objects!)
//arr2 = data.map(function (o) { return o.pages[0]; });
// Or, for all pages in the array:
arr2 = [].concat(...data.map(function (o) { return o.pages; }));
console.log(arr);
console.log(arr2);
// Returns 2x [Object { url="www.abc.com", title="abc"}, Object { url="www.123.com", title="123"}, Object { url="www.xyz.com", title="xyz"}]
use array map() & reduce() method :
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
var dataArray = data.map(function(item) {
return item.pages;
});
dataArray = dataArray.reduce(function(a,b) {
return a.concat(b);
}, []);
console.log(dataArray);

is there a function in lodash to replace matched item

I wonder if there is a simpler method in lodash to replace an item in a JavaScript collection? (Possible duplicate but I did not understand the answer there:)
I looked at their documentation but could not find anything
My code is:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Can following code be reduced to something like _.XX(arr, {id:1}, {id:1, name: "New Name"});
_.each(arr, function(a, idx){
if(a.id === 1){
arr[idx] = {id:1, name: "Person New Name"};
return false;
}
});
_.each(arr, function(a){
document.write(a.name);
});
Update:
The object I'm trying to replace with has many properties like
{id: 1, Prop1: ..., Prop2:..., and so on}
Solution:
Thanks to dfsq but I found a proper solution within lodash that seems to work fine and is pretty neat and I put it in a mixin as well since I've this requirement at many places. JSBin
var update = function(arr, key, newval) {
var match = _.find(arr, key);
if(match)
_.merge(match, newval);
else
arr.push(newval);
};
_.mixin({ '$update': update });
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
_.$update(arr, {id:1}, {id:1, name: "New Val"});
document.write(JSON.stringify(arr));
Faster Solution
As pointed out by #dfsq, following is way faster
var upsert = function (arr, key, newval) {
var match = _.find(arr, key);
if(match){
var index = _.indexOf(arr, _.find(arr, key));
arr.splice(index, 1, newval);
} else {
arr.push(newval);
}
};
In your case all you need to do is to find object in array and use Array.prototype.splice() method, read more details here:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Find item index using _.findIndex (thanks #AJ Richardson for comment)
var index = _.findIndex(arr, {id: 1});
// Replace item at index using native splice
arr.splice(index, 1, {id: 100, name: 'New object.'});
// "console.log" result
document.write(JSON.stringify( arr ));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
Seems like the simplest solution would to use ES6's .map or lodash's _.map:
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
// lodash
var newArr = _.map(arr, function(a) {
return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});
// ES6
var newArr = arr.map(function(a) {
return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});
This has the nice effect of avoiding mutating the original array.
[ES6] This code works for me.
let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
function findAndReplace(arr, find, replace) {
let i;
for(i=0; i < arr.length && arr[i].id != find.id; i++) {}
i < arr.length ? arr[i] = replace : arr.push(replace);
}
Now let's test performance for all methods:
// TC's first approach
function first(arr, a, b) {
_.each(arr, function (x, idx) {
if (x.id === a.id) {
arr[idx] = b;
return false;
}
});
}
// solution with merge
function second(arr, a, b) {
const match = _.find(arr, a);
if (match) {
_.merge(match, b);
} else {
arr.push(b);
}
}
// most voted solution
function third(arr, a, b) {
const match = _.find(arr, a);
if (match) {
var index = _.indexOf(arr, _.find(arr, a));
arr.splice(index, 1, b);
} else {
arr.push(b);
}
}
// my approach
function fourth(arr, a, b){
let l;
for(l=0; l < arr.length && arr[l].id != a.id; l++) {}
l < arr.length ? arr[l] = b : arr.push(b);
}
function test(fn, times, el) {
const arr = [], size = 250;
for (let i = 0; i < size; i++) {
arr[i] = {id: i, name: `name_${i}`, test: "test"};
}
let start = Date.now();
_.times(times, () => {
const id = Math.round(Math.random() * size);
const a = {id};
const b = {id, name: `${id}_name`};
fn(arr, a, b);
});
el.innerHTML = Date.now() - start;
}
test(first, 1e5, document.getElementById("first"));
test(second, 1e5, document.getElementById("second"));
test(third, 1e5, document.getElementById("third"));
test(fourth, 1e5, document.getElementById("fourth"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div>
<ol>
<li><b id="first"></b> ms [TC's first approach]</li>
<li><b id="second"></b> ms [solution with merge]</li>
<li><b id="third"></b> ms [most voted solution]</li>
<li><b id="fourth"></b> ms [my approach]</li>
</ol>
<div>
If you're just trying to replace one property, lodash _.find and _.set should be enough:
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
_.set(_.find(arr, {id: 1}), 'name', 'New Person');
You can also use findIndex and pick to achieve the same result:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var data = {id: 2, name: 'Person 2 (updated)'};
var index = _.findIndex(arr, _.pick(data, 'id'));
if( index !== -1) {
arr.splice(index, 1, data);
} else {
arr.push(data);
}
As the time passes you should embrace a more functional approach in which you should avoid data mutations and write small, single responsibility functions. With the ECMAScript 6 standard, you can enjoy functional programming paradigm in JavaScript with the provided map, filter and reduce methods. You don't need another lodash, underscore or what else to do most basic things.
Down below I have included some proposed solutions to this problem in order to show how this problem can be solved using different language features:
Using ES6 map:
const replace = predicate => replacement => element =>
predicate(element) ? replacement : element
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }
const result = arr.map(replace (predicate) (replacement))
console.log(result)
Recursive version - equivalent of mapping:
Requires destructuring and array spread.
const replace = predicate => replacement =>
{
const traverse = ([head, ...tail]) =>
head
? [predicate(head) ? replacement : head, ...tail]
: []
return traverse
}
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }
const result = replace (predicate) (replacement) (arr)
console.log(result)
When the final array's order is not important you can use an object as a HashMap data structure. Very handy if you already have keyed collection as an object - otherwise you have to change your representation first.
Requires object rest spread, computed property names and Object.entries.
const replace = key => ({id, ...values}) => hashMap =>
({
...hashMap, //original HashMap
[key]: undefined, //delete the replaced value
[id]: values //assign replacement
})
// HashMap <-> array conversion
const toHashMapById = array =>
array.reduce(
(acc, { id, ...values }) =>
({ ...acc, [id]: values })
, {})
const toArrayById = hashMap =>
Object.entries(hashMap)
.filter( // filter out undefined values
([_, value]) => value
)
.map(
([id, values]) => ({ id, ...values })
)
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const replaceKey = 1
const replacement = { id: 100, name: 'New object.' }
// Create a HashMap from the array, treating id properties as keys
const hashMap = toHashMapById(arr)
console.log(hashMap)
// Result of replacement - notice an undefined value for replaced key
const resultHashMap = replace (replaceKey) (replacement) (hashMap)
console.log(resultHashMap)
// Final result of conversion from the HashMap to an array
const result = toArrayById (resultHashMap)
console.log(result)
Came across this as well and did it simply that way.
const persons = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
const updatedPerson = {id: 1, name: "new Person Name"}
const updatedPersons = persons.map(person => (
person.id === updated.id
? updatedPerson
: person
))
If wanted we can generalize it
const replaceWhere = (list, predicate, replacement) => {
return list.map(item => predicate(item) ? replacement : item)
}
replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}
If the insertion point of the new object does not need to match the previous object's index then the simplest way to do this with lodash is by using _.reject and then pushing new values in to the array:
var arr = [
{ id: 1, name: "Person 1" },
{ id: 2, name: "Person 2" }
];
arr = _.reject(arr, { id: 1 });
arr.push({ id: 1, name: "New Val" });
// result will be: [{ id: 2, name: "Person 2" }, { id: 1, name: "New Val" }]
If you have multiple values that you want to replace in one pass, you can do the following (written in non-ES6 format):
var arr = [
{ id: 1, name: "Person 1" },
{ id: 2, name: "Person 2" },
{ id: 3, name: "Person 3" }
];
idsToReplace = [2, 3];
arr = _.reject(arr, function(o) { return idsToReplace.indexOf(o.id) > -1; });
arr.push({ id: 3, name: "New Person 3" });
arr.push({ id: 2, name: "New Person 2" });
// result will be: [{ id: 1, name: "Person 1" }, { id: 3, name: "New Person 3" }, { id: 2, name: "New Person 2" }]
Using lodash unionWith function, you can accomplish a simple upsert to an object. The documentation states that if there is a match, it will use the first array. Wrap your updated object in [ ] (array) and put it as the first array of the union function. Simply specify your matching logic and if found it will replace it and if not it will add it
Example:
let contacts = [
{type: 'email', desc: 'work', primary: true, value: 'email prim'},
{type: 'phone', desc: 'cell', primary: true, value:'phone prim'},
{type: 'phone', desc: 'cell', primary: false,value:'phone secondary'},
{type: 'email', desc: 'cell', primary: false,value:'email secondary'}
]
// Update contacts because found a match
_.unionWith([{type: 'email', desc: 'work', primary: true, value: 'email updated'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
// Add to contacts - no match found
_.unionWith([{type: 'fax', desc: 'work', primary: true, value: 'fax added'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
If you want to make a function and keep it "lodash-ey", you can make a wrapper function that works with callbacks. It makes the function more general use.
To write this try something like
function findAllAndReplace(array, replacement, callback){
return array.map( element => callback(element) ? replacement : element )
}
To find and replace by key, just make your callback very simple. (itemInArray) => itemInArray.keyOnItem
But if you want more advanced functionality you can incorporate it with barely any extra effort. Here are some examples.
(Simple) Find the item with id 2, replace it to have an id: 7
const items = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}]
findAllAndReplace( items, {id: 7}, item => item.id === 2 )
(Slightly More Complex) Find 28 year old named John, and replace him with a 28 year old named Jon
const people = [
{
name: "John",
age: 20
},
{
name: "John",
age: 28
},
{
name: "Jim",
age: 28
},
]
findAllAndReplace(
people, // all the people
{ name: "Jon", age: 28 }, // Replacement value
(person) => person.name === "jon" && person.age === 21 // callback function
)
Also, the method above will find all instances that match and replace them, but if you just want to do it for one you could do something like below.
function findOneAndReplace(array, replacement, callback){
const splitIndex = array.findIndex(callback)
// This if statement can be ommitted, but might
// be handy depending on your use case
if(splitIndex < 0){
throw new Error("Swap Element not found")
}
const leadingTerms = array.slice(0, splitIndex)
const trailingTerms = array.slice(splitIndex + 1, array.length)
return [...leadingTerms, replacement, ...trailingTerms]
)
note: It might be useful to make your function break if it doesn't find a matching element, but if you don't want that feature you can cut those lines of code out.
Not bad variant too)
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
var id = 1; //id to find
arr[_.find(arr, {id: id})].name = 'New Person';
If you're looking for a way to immutably change the collection (as I was when I found your question), you might take a look at immutability-helper, a library forked from the original React util. In your case, you would accomplish what you mentioned via the following:
var update = require('immutability-helper')
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}]
var newArray = update(arr, { 0: { name: { $set: 'New Name' } } })
//=> [{id: 1, name: "New Name"}, {id:2, name:"Person 2"}]
You can do it without using lodash.
let arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
let newObj = {id: 1, name: "new Person"}
/*Add new prototype function on Array class*/
Array.prototype._replaceObj = function(newObj, key) {
return this.map(obj => (obj[key] === newObj[key] ? newObj : obj));
};
/*return [{id: 1, name: "new Person"}, {id: 2, name: "Person 2"}]*/
arr._replaceObj(newObj, "id")
Immutable, suitable for ReactJS:
Assume:
cosnt arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
The updated item is the second and name is changed to Special Person:
const updatedItem = {id:2, name:"Special Person"};
Hint: the lodash has useful tools but now we have some of them on Ecmascript6+, so I just use map function that is existed on both of lodash and ecmascript6+:
const newArr = arr.map(item => item.id === 2 ? updatedItem : item);

Categories