Removing duplicate objects (based on multiple keys) from array - javascript

Assuming an array of objects as follows:
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
]
A duplicate entry would be if label and color are the same. In this case Objects with id = 1 and id = 5 are duplicates.
How can I filter this array and remove duplicates?
I know solutions where you can filter against one key with something like:
const unique = [... new Set(listOfTags.map(tag => tag.label)]
But what about multiple keys?
As per request in comment, here the desired result:
[
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
]

Late one, but I don't know why nobody suggests something much simpler:
listOfTags.filter((tag, index, array) => array.findIndex(t => t.color == tag.color && t.label == tag.label) == index);

You could use a Set in a closure for filtering.
const
listOfTags = [{ id: 1, label: "Hello", color: "red", sorting: 0 }, { id: 2, label: "World", color: "green", sorting: 1 }, { id: 3, label: "Hello", color: "blue", sorting: 4 }, { id: 4, label: "Sunshine", color: "yellow", sorting: 5 }, { id: 5, label: "Hello", color: "red", sorting: 6 }],
keys = ['label', 'color'],
filtered = listOfTags.filter(
(s => o =>
(k => !s.has(k) && s.add(k))
(keys.map(k => o[k]).join('|'))
)
(new Set)
);
console.log(filtered);
.as-console-wrapper { max-height: 100% !important; top: 0; }

const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
]
const unique = [];
listOfTags.map(x => unique.filter(a => a.label == x.label && a.color == x.color).length > 0 ? null : unique.push(x));
console.log(unique);

One way is create an object (or Map) that uses a combination of the 2 values as keys and current object as value then get the values from that object
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
]
const uniques = Object.values(
listOfTags.reduce((a, c) => {
a[c.label + '|' + c.color] = c;
return a
}, {}))
console.log(uniques)

I would tackle this by putting this into temporary Map with a composite key based on the properties you're interested in. For example:
const foo = new Map();
for(const tag of listOfTags) {
foo.set(tag.id + '-' tag.color, tag);
}

Based on the assumption that values can be converted to strings, you can call
distinct(listOfTags, ["label", "color"])
where distinct is:
/**
* #param {array} arr The array you want to filter for dublicates
* #param {array<string>} indexedKeys The keys that form the compound key
* which is used to filter dublicates
* #param {boolean} isPrioritizeFormer Set this to true, if you want to remove
* dublicates that occur later, false, if you want those to be removed
* that occur later.
*/
const distinct = (arr, indexedKeys, isPrioritizeFormer = true) => {
const lookup = new Map();
const makeIndex = el => indexedKeys.reduce(
(index, key) => `${index};;${el[key]}`, ''
);
arr.forEach(el => {
const index = makeIndex(el);
if (lookup.has(index) && isPrioritizeFormer) {
return;
}
lookup.set(index, el);
});
return Array.from(lookup.values());
};
Sidenote: If you use distinct(listOfTags, ["label", "color"], false), it will return:
[
{id: 1, label: "Hello", color: "red", sorting: 6},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
]

You can use reduce here to get filtered objects.
listOfTags.reduce((newListOfTags, current) => {
if (!newListOfTags.some(x => x.label == current.label && x.color == current.color)) {
newListOfTags.push(current);
}
return newListOfTags;
}, []);

const keys = ['label', 'color'],
const mySet = new Set();
const duplicateSet = new Set();
const result = objList.filter((item) => {
let newItem = keys.map((k) => item[k]).join("-");
mySet.has(newItem) && duplicateSet.add(newItem);
return !mySet.has(newItem) && mySet.add(newItem);
});
console.log(duplicateSet, result);
This can be used to filter duplicate and non duplicate

const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
];
let keysList = Object.keys(listOfTags[0]); // Get First index Keys else please add your desired array
let unq_List = [];
keysList.map(keyEle=>{
if(unq_List.length===0){
unq_List = [...unqFun(listOfTags,keyEle)];
}else{
unq_List = [...unqFun(unq_List,keyEle)];
}
});
function unqFun(array,key){
return [...new Map(array.map(o=>[o[key],o])).values()]
}
console.log(unq_List);

We can find the unique value by the below script, we can expand the array using forEach loop and check the value exists on the new array by using some() method and after that create the new array by using push() method.
const arr = [{ id: 1 }, { id: 2 }, { id: 4 }, { id: 1 }, { id: 4 }];
var newArr =[];
arr.forEach((item)=>{
if(newArr.some(el => el.id === item.id)===false){
newArr.push(item);
}
}
);
console.log(newArr);
//[{id: 1}, {id: 2}, {id: 4}];

const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6},
];
const objRes=listOfTags.filter((v,i,s)=>s.findIndex(v2=>['label','color'].every(k=>v2[k]===v[k]))===i);
console.log(objRes);

Maybe helpful. Extract duplicate items from array then delete all duplicates
// Initial database data
[
{ key: "search", en:"Search" },
{ key: "search", en:"" },
{ key: "alert", en:"Alert" },
{ key: "alert", en:"" },
{ key: "alert", en:"" }
]
// Function called
async function removeDuplicateItems() {
try {
// get data from database
const { data } = (await getList());
// array reduce method for obj.key
const reduceMethod = data.reduce((x, y) => {
x[y.key] = ++x[y.key] || 0;
return x;
}, {});
// find duplicate items by key and checked whether "en" attribute also has value
const duplicateItems = data.filter(obj => !obj.en && reduceMethod[obj.key]);
console.log('duplicateItems', duplicateItems);
// remove all dublicate items by id
duplicateItems.forEach(async (obj) => {
const deleteResponse = (await deleteItem(obj.id)).data;
console.log('Deleted item: ', deleteResponse);
});
} catch (error) {
console.log('error', error);
}
}
// Now database data:
[
{ key: "search", en:"Search" },
{ key: "alert", en:"Alert" }
]

One solution is to iterate the array and use a Map of maps to store the value-value pairs that have been encountered so far.
Looking up duplicates this way should be reasonably fast (compared to nested loops or .filter + .find approach).
Also the values could be any primitive type; they are not stringified or concatenated for comparison (which could lead to incorrect comparison).
const listOfTags = [
{id: 1, label: "Hello", color: "red", sorting: 0},
{id: 2, label: "World", color: "green", sorting: 1},
{id: 3, label: "Hello", color: "blue", sorting: 4},
{id: 4, label: "Sunshine", color: "yellow", sorting: 5},
{id: 5, label: "Hello", color: "red", sorting: 6}
];
let map = new Map();
let result = [];
listOfTags.forEach(function(obj) {
if (map.has(obj.label) === false) {
map.set(obj.label, new Map());
}
if (map.get(obj.label).has(obj.color) === false) {
map.get(obj.label).set(obj.color, true);
result.push(obj)
}
});
console.log(result);

Related

Flatten whilst splitting into smaller objects

I am having these kind of array structure.
[{ "primary_product": "Blueberry",
"list_of_products": ["Raspberry","Strawberry","Blackberry"]}]
I want to destructure the pattern and make it like this below
[{"id": 1,"value":"Blueberry"},{"id": 2,"value":"Raspberry"},{"id": 3,"value":"Strawberry"}, …]
Primary product will be the first product and then make the array of strings into key/value pair. How to do this using es6?
All you need is basic functions like forEach and push. I would recommend learning these.
let arr1 = [{ "primary_product": "Blueberry", "list_of_products": ["Raspberry","Strawberry","Blackberry"]}]
arr2 = [{ id: 1, value: arr1[0].primary_product }]
arr1[0].list_of_products.forEach((element) => {
arr2.push({ id: arr2.length + 1, value: element })
})
Here's a one-liner using map on the list_of_products:
const arr = ['Raspberry','Strawberry','Blackberry'];
return arr.map((val, i) => {return {id: i+1, value: val}});
This is the result:
[
{ id: 1, value: 'Raspberry' },
{ id: 2, value: 'Strawberry' },
{ id: 3, value: 'Blackberry' }
]
Note that the callback to map includes (currentValue, index, arr).
To make things slightly easier for the eyes I've simplified the structure:
const p = [ { a: 100, b: [101, 102, 103]}
, { a: 200, b: [201, 202, 203]}];
You can flatten all the numbers into a single list i.e. [100, 101, 102, 103, 200, 201, 202, 203] with:
p.flatMap(({a, b}) => [a, ...b]);
To get closer to what you're after, let's first create an id function that will return the next number:
const id = (n => () => ++n)(0);
id(); //=> 1
id(); //=> 2
id(); //=> 3
// …
Then let's create a function obj that takes an x and wraps it into an object:
const obj => x => ({id: id(), value: x});
obj(100); //=> {id: 1, value: 100);
obj(200); //=> {id: 2, value: 200);
// …
Then you can do:
p.flatMap(({a, b}) => [obj(a), ...b.map(obj)]);
//=> [ {id: 1, value: 100}
//=> , {id: 2, value: 101}
//=> , {id: 3, value: 102}
//=> , {id: 4, value: 103}
//=> , {id: 5, value: 200}
//=> , {id: 6, value: 201}
//=> , {id: 7, value: 202}
//=> , {id: 8, value: 203}]

Matching array of objects with array of values

If I have 2 arrays:
arr1: ["1","2"]
arr2: [{id: 1, color: "green"}, {id: 2, color: "yellow"}, {id: 3, color: "red"}]
And I want to obtain:
result: [{id: 1, color: "green"}, {id: 2, color: "yellow"}]
I am trying with
arr2.filter(
e => arr1.indexOf(e.id) !== -1
);
But result is empty.
The problem with your code is that as your inputs have different types(id is a number but arr1 has strings), this arr1.indexOf(e.id) !== -1 will always be false.
Convert one of them to a number like this:
const arr1 = ["1","2"]
const arr2 = [{id: 1, color: "green"}, {id: 2, color: "yellow"}, {id: 3, color: "red"}]
const result = arr2.filter(el => arr1.some(aEl => el.id === +aEl))
console.log(result)
Alternatively, you can convert the ids to strings and use your existing code:
const arr1 = ["1", "2"],
arr2 = [{
id: 1,
color: "green"
}, {
id: 2,
color: "yellow"
}, {
id: 3,
color: "red"
}],
result = arr2.filter(
e => arr1.indexOf(e.id.toString()) !== -1
);
console.log(result)
You could use Array.prototype.filter() with Array.prototype.some() method to get the result.
const arr1 = ['1', '2'];
const arr2 = [
{ id: 1, color: 'green' },
{ id: 2, color: 'yellow' },
{ id: 3, color: 'red' },
];
const ret = arr2.filter((x) => arr1.some((y) => +y === x.id));
console.log(ret);
Another solution using Set Object.
const arr1 = ['1', '2'];
const arr2 = [
{ id: 1, color: 'green' },
{ id: 2, color: 'yellow' },
{ id: 3, color: 'red' },
];
const set = new Set(arr1);
const ret = arr2.filter((x) => set.has(x.id.toString()));
console.log(ret);
Another solution is may be with .includes() method.
const arr1 = ["1","2"]
const arr2 = [{id: 1, color: "green"}, {id: 2, color: "yellow"}, {id: 3, color: "red"}]
const result = arr2.filter(n => arr1.includes(String(n.id)));
console.log(result);

Extracting values of unknown keys in an array of objects [duplicate]

This question already has answers here:
How to iterate over a JavaScript object?
(19 answers)
Closed 4 years ago.
I get a JSON from an external source which has an unknown number of keys. So for example the structure looks like:
data = [{
id: 1,
testObject_1_color: "red",
testObject_1_shape: "triangle",
testObject_2_color: "blue",
testObject_2_shape: "line",
},{
id: 2,
testObject_1_color: "green"
testObject_1_shape: "triangle",
},{
id: 3,
testObject_1_color: "brown",
testObject_1_shape: "square",
testObject_2_color: "black",
testObject_2_shape: "circle",
testObject_3_color: "orange"
testObject_3_shape: "square",
}]
To work with this data I'd need to convert it to something more useful like:
data = [
{object:1, color:"red", shape:"triangle"},
{object:2, color:"blue", shape:"line"},
{object:3, color:"green", shape:"triangle"}
]
The Number of testObject_x_color / shapeis undefined and can theoretically be anything between 0 and 100. Has anybody an idea how to walk through that collection without asking a 100 times if data.hasOwnProperty('testObject_x_color')...?
Another solution with a few loops:
var data = [{
id: 1,
testObject_1_color: "red",
testObject_1_shape: "triangle",
testObject_2_color: "blue",
testObject_2_shape: "line"
}, {
id: 2,
testObject_1_color: "green",
testObject_1_shape: "triangle"
}, {
id: 3,
testObject_1_color: "brown",
testObject_1_shape: "square",
testObject_2_color: "black",
testObject_2_shape: "circle",
testObject_3_color: "orange",
testObject_3_shape: "square"
}];
var sorted = data.reduce(function(result, item) {
// Temp object to store all properties
var tmp = {};
// Get all keys of the object
Object.keys(item).forEach(function(k) {
// Split by underscore
var parts = k.split('_');
// Process only properties with similar to testObject_1_color names
// and skip other e.g. id
if (parts.length === 3) {
if (!tmp[parts[1]]) {
tmp[parts[1]] = {};
}
tmp[parts[1]][parts[2]] = item[k];
}
});
// Fill data in the result array
Object.values(tmp).forEach(function (pair, i) {
result.push(pair);
});
return result;
}, []);
for (var i = 0; i < sorted.length; i++) {
console.log('object ' + (i + 1) + ': ', sorted[i]);
}
use reg and es5 array function.
let data = [{
id: 1,
testObject_1_color: 'red',
testObject_1_shape: 'triangle',
testObject_2_color: 'blue',
testObject_2_shape: 'line'
},
{
id: 2,
testObject_1_color: 'green',
testObject_1_shape: 'triangle'
},
{
id: 3,
testObject_1_color: 'brown',
testObject_1_shape: 'square',
testObject_2_color: 'black',
testObject_2_shape: 'circle',
testObject_3_color: 'orange',
testObject_3_shape: 'square'
}
]
let regId = /testObject_(\d{1,3})_(color|shape)/
let res = data
.reduce((re, obj) => {
Reflect.ownKeys(obj)
.filter(key => key !== 'id')
.forEach(key => {
let id = obj.id + '' + key.match(regId)[1]
let isFind = re.find(o => o.id === id)
if (!isFind) {
re.push({
id: obj.id + '' + key.match(regId)[1],
color: obj[key]
})
} else {
isFind.shape = obj[key]
}
})
return re
}, [])
.map(({
id,
color,
shape
}, index) => ({
id: index + 1,
color,
shape
}))
console.log(res)

Javascript concat and overwrite where element has the same id

Is it possible to concat two arrays with objects and let the second array overwrite the first array where they have the same id:
// array 1
[
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
// array 2:
[
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
// out:
[
{id: 1, name: "newFoo"}, // overwriten by array 2
{id: 2, name: "bar"}, // not changed (from array 1)
{id: 3, name: "baz"}, // not changed (from array 1)
{id: 4, name: "y"}, // added (from array 2)
{id: 5, name: "z"} // added (from array 2)
]
If it is possible I would like to do this without the use of third party libraries
var a = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
var b = [
{id: 1, name: "fooboo"},
{id: 4, name: "bar"},
{id: 5, name: "baz"}
];
/* iterate through each of b, if match found in a, extend with that of a. else push into b ...*/
b.forEach(m => {
var item = a.find(n => n.id === m.id);
if(item) { return Object.assign(item, m); }
a.push(m);
});
console.log(a);
You can do
let arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
let arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
let result = arr1.concat(arr2).reduce((a, b) => {
a[b.id] = b.name;
return a;
},{})
result = Object.keys(result).map(e => {
return {id : e, name : result[e]};
});
console.log(result);
Explanation
I am using the property of objects that they don't keep duplicate keys, so for an array concated together, I reduce it to an object with id as it's key and name as its value, hence overriding all duplicates. In the next step I converted this back into an array.
Check you my solution. There is no "rewrite", i just use a second array as base and don't write value if it has same id.
let a = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
let b = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
];
let duplicateId;
a.forEach(aitem => {
duplicateId = false;
b.forEach(bitem => {
if (aitem.id === bitem.id)
duplicateId = true;
});
if (!duplicateId)
b.push(aitem);
});
console.log(b);
Maybe you can use Object.assign and Object.entries to achieve, lets say:
const arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
const arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
const obj3 = Object.entries(Object.assign({}, ...arr1, arr2))
.map(([prop, value]) => ({[prop]:value}));
Example:
https://jsfiddle.net/0f75vLka/
Another option would be to convert arrays to map with id as key then merge the objects and then convert it back to array.
var arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
var arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
];
function arr2map(arr) {
var map = {};
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
map[item.id] = item;
}
return map;
}
function map2arr(map) {
var arr = [];
for (var i in map) {
arr.push(map[i]);
}
return arr;
}
var arr1m = arr2map(arr1);
var arr2m = arr2map(arr2);
var arr3m = map2arr( Object.assign({}, arr1m, arr2m) );
//output
alert(JSON.stringify(arr3m));

Compare to arrays of objects and update key in Angular

How can I compare two arrays of objects and update a key if an object exists in both arrays?
$scope.listOne = [
{id: 1, selected: false},
{id: 2, selected: false},
{id: 3, selected: false}
];
$scope.listTwo = [
{id: 4, color: orange},
{id: 5, color: blue},
{id: 2, color: green}
];
Using the above objects, how can I compare them and have listOne[1].selected updated to true?
Here, i am trying to loop through listone and checking if there is such key in listtwo if so making listone's selected property to true
This is done in vanila javascript
var listOne = [{
id: 1,
selected: false
}, {
id: 2,
selected: false
}, {
id: 3,
selected: false
}];
var listTwo = [{
id: 4,
color: "orange"
}, {
id: 5,
color: "blue"
}, {
id: 2,
color: "green"
}];
angular.forEach(listOne, function(value) {
for (var key in listTwo) {
if (listTwo[key]["id"] == value.id) {
value.selected = true;
}
}
});
console.log(listOne);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
Hope this helps
My first thought that jumps to mind is to use a lodash function:
let a = [{ id: 1, selected: false }];
let b = [{ id: 1, selected: false }, { id: 2, selected: true }];
let result = _.intersectionWith(a, b, _.isEqual);
'result' should be an array with one element, that part which match (ie., 'a').
Look into lodash _.some (https://lodash.com/docs/4.16.6#some)
Otherwise you could loop through the arrays, JSON.stringify each object and check for equality that way like this answer:
Object comparison in JavaScript
In just plain Javascript:
$scope.listOne.filter(function(item) {
return !!$scope.listTwo.find(function(innerItem) {
innerItem.id == item.id;
});
})
.forEach(function(item) {
item.selected = true;
});

Categories