JavaScript: Remove duplicates of objects sharing same property value - javascript

I have an array of objects that I would like to trim down based on a specific key:value pair. I want to create an array that includes only one object per this specific key:value pair. It doesn't necessarily matter which object of the duplicates is copied to the new array.
For example, I want to trim based on the price property of arrayWithDuplicates, creating a new array that only includes one of each value:
var arrayWithDuplicates = [
{"color":"red",
"size": "small",
"custom": {
"inStock": true,
"price": 10
}
},
{"color":"green",
"size": "small",
"custom": {
"inStock": true,
"price": 30
}
},
{"color":"blue",
"size": "medium",
"custom": {
"inStock": true,
"price": 30
}
},
{"color":"red",
"size": "large",
"custom": {
"inStock": true,
"price": 20
}
}
];
Would become:
var trimmedArray = [
{"color":"red",
"size": "small",
"custom": {
"inStock": true,
"price": 10
}
},
{"color":"green",
"size": "small",
"custom": {
"inStock": true,
"price": 30
}
},
{"color":"red",
"size": "large",
"custom": {
"inStock": true,
"price": 20
}
}
];
Is there a JavaScript or Angular function that would loop through and do this?
EDIT: The property to filter on is nested within another property.

This function removes duplicate values from an array by returning a new one.
function removeDuplicatesBy(keyFn, array) {
var mySet = new Set();
return array.filter(function(x) {
var key = keyFn(x), isNew = !mySet.has(key);
if (isNew) mySet.add(key);
return isNew;
});
}
var values = [{color: "red"}, {color: "blue"}, {color: "red", number: 2}];
var withoutDuplicates = removeDuplicatesBy(x => x.color, values);
console.log(withoutDuplicates); // [{"color": "red"}, {"color": "blue"}]
So you could use it like
var arr = removeDuplicatesBy(x => x.custom.price, yourArrayWithDuplicates);

I don't think there's a built-in function in Angular, but it isn't hard to create one:
function removeDuplicates(originalArray, objKey) {
var trimmedArray = [];
var values = [];
var value;
for(var i = 0; i < originalArray.length; i++) {
value = originalArray[i][objKey];
if(values.indexOf(value) === -1) {
trimmedArray.push(originalArray[i]);
values.push(value);
}
}
return trimmedArray;
}
Usage:
removeDuplicates(arrayWithDuplicates, 'size');
Returns:
[
{
"color": "red",
"size": "small"
},
{
"color": "blue",
"size": "medium"
},
{
"color": "red",
"size": "large"
}
]
And
removeDuplicates(arrayWithDuplicates, 'color');
Returns:
[
{
"color": "red",
"size": "small"
},
{
"color": "green",
"size": "small"
},
{
"color": "blue",
"size": "medium"
}
]

Use Array.filter(), keeping track of values by using an Object as a hash, and filtering out any items whose value is already contained in the hash.
function trim(arr, key) {
var values = {};
return arr.filter(function(item){
var val = item[key];
var exists = values[val];
values[val] = true;
return !exists;
});
}

You can use underscore for this:
//by size:
var uSize = _.uniqBy(arrayWithDuplicates, function(p){ return p.size; });
//by custom.price;
var uPrice = _.uniqBy(arrayWithDuplicates, function(p){ return p.custom.price; });

You can use lodash to remove duplicate objects:
import * as _ from 'lodash';
_.uniqBy(data, 'id');
Here 'id' is your unique identifier

Try the following function:
function trim(items){
const ids = [];
return items.filter(item => ids.includes(item.id) ? false : ids.push(item.id));
}

using lodash you can filter it out easily
the first parameter will be your array and second will be your field with duplicates
_.uniqBy(arrayWithDuplicates, 'color')
it will return an array with unique value

Simple solution although not the most performant:
var unique = [];
duplicates.forEach(function(d) {
var found = false;
unique.forEach(function(u) {
if(u.key == d.key) {
found = true;
}
});
if(!found) {
unique.push(d);
}
});

for (let i = 0; i < arrayWithDuplicates.length; i++) {
for (let j = i + 1; j < arrayWithDuplicates.length; j++) {
if (arrayWithDuplicates[i].name === students[j].name) {
arrayWithDuplicates.splice(i, 1);
}
}
}
this will work perfectly...and this will delete first repeated array.
To delete last repeated array we only have to change
arrayWithDuplicates.splice(i, 1) ; into
arrayWithDuplicates.splice(j, 1);

Off the top of my head there is no one function that will do this for you as you are dealing with an array of objects and also there is no rule for which duplicate would be removed as duplicate.
In your example you remove the one with size: small but if you were to implement this using a loop you'd most likely include the first and exclude the last as you loop through your array.
It may very well be worth taking a look at a library such as lodash and creating a function that uses a combination of it's API methods to get the desired behaviour you want.
Here is a possible solution you could use making use of basic Arrays and a filter expression to check whether a new item would be considered a duplicate before being attached to a return result.
var arrayWithDuplicates = [
{"color":"red", "size": "small"},
{"color":"green", "size": "small"},
{"color":"blue", "size": "medium"},
{"color":"red", "size": "large"}
];
var reduce = function(arr, prop) {
var result = [],
filterVal,
filters,
filterByVal = function(n) {
if (n[prop] === filterVal) return true;
};
for (var i = 0; i < arr.length; i++) {
filterVal = arr[i][prop];
filters = result.filter(filterByVal);
if (filters.length === 0) result.push(arr[i]);
}
return result;
};
console.info(reduce(arrayWithDuplicates, 'color'));
You can check out some literature on Array filtering here
If you need to provide a preference on which item to remove you could define extra parameters and logic that will make extra property checks before adding to a return value.
Hope that helps!

Here is the typescript way
public removeDuplicates(originalArray:any[], prop) {
let newArray = [];
let lookupObject = {};
originalArray.forEach((item, index) => {
lookupObject[originalArray[index][prop]] = originalArray[index];
});
Object.keys(lookupObject).forEach(element => {
newArray.push(lookupObject[element]);
});
return newArray;
}
And
let output = this.removeDuplicates(yourArray,'color');

This is just another 'feature' based on yvesmancera's solution (after I started tinkering for my own solution) Also noted we are only allowed to currently use IE 11, so limited ES5 is allowed.
var newArray = RemoveDuplicates(myArray,'Role', 2);
function RemoveDuplicates(array, objKey, rtnType) {
var list = [], values = [], value;
for (var i = 0; i < array.length; i++) {
value = array[i][objKey];
if(values.indexOf(value) === -1){
list.push(array[i]);
values.push(value);
}
}
if(rtnType == 1)
return list;
return values;
};
Hoping this will work for most, if not all arrays when filtering out objects based on a single object property value.

Related

Get the nearby objects of array item

So, I have data. It is array of objects.
data = [
{
"id": "200",
"price": "5000"
},
{
"id": "137",
"price": "8000"
},
{
"id": "230",
"price": "9000"
},
{
"id": "241",
"price": "9000"
},
{
"id": "78",
"price": "10000"
}
]
json=JSON.parse(data);
I make something like pager.
My code should return nearby (previous and next) elements of original element.
It is not allowed to change order of objects.
I'm trying to do something like
json.indexOf(JSON.parse('{"id":"200","price":"5000"}'))
but it returns -1.
Also json[0]==JSON.parse('{"id":"200","price":"5000"}') return false, but I think that this elements are similar.
What way do you see?
json=JSON.parse('[{"id":"200","price":"5000"},{"id":"137","price":"8000"},{"id":"230","price":"9000"},{"id":"241","price":"9000"},{"id":"78","price":"10000"}]');
console.log(json.indexOf(JSON.parse('{"id":"200","price":"5000"}')));
console.log(json[0]==JSON.parse('{"id":"200","price":"5000"}'));
console.log(json[0]);
console.log(JSON.parse('{"id":"200","price":"5000"}'));
You could take a function which finds the index of the wanted id and returns items before that index, the index and one after the index with adjustment at the beginning of the array.
function getParts(id) {
var index = array.findIndex(o => o.id === id),
min = Math.max(index - 1, 0);
if (index !== -1) {
return array.slice(min, min + (index ? 3 : 2));
}
}
var array = JSON.parse('[{"id":"200","price":"5000"},{"id":"137","price":"8000"},{"id":"230","price":"9000"},{"id":"241","price":"9000"},{"id":"78","price":"10000"}]');
console.log(getParts('200'));
console.log(getParts('137'));
console.log(getParts('230'));
console.log(getParts('78'));
Try this, i think it would work as the 'id' property is unique
var words = [{"id":"200","price":"5000"},{"id":"137","price":"8000"},{"id":"230","price":"9000"},{"id":"241","price":"9000"},{"id":"78","price":"10000"}];
let k;
let sayYourKeyId = "137";
const result = words.find((word, index) => {
if(word.id == sayYourKeyId){
k = index;
}
});
console.log(words[k-1]);
console.log(words[k]);
console.log(words[k+1]);

Single variable to stand in for multiple key names - javascript

I have an array, something like this:
array =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
I have a function that searches for values in the array.
function findValue (index, key) {
return array[index][key];
}
var value = findValue(0, "id");
// returns redApple
Is there a way I could pass a single argument to the function if I wanted to find something deeper in the array? For example, if I wanted to find "bunchName" could I pass it something like 1, "options[0].bunchName" and get back "bunch1"?
I want a function that can handle multiple keys. In my real project sometimes I'm looking for something on the first level, sometimes I'm looking on the second level, sometimes the third level, etc.
jQuery can be used if for some reason that would help.
You could take the string, replace the brackets, split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
function findValue(index, path) {
return getValue(array[index], path);
}
var array = [{ type: "apple", color: "red", id: "redApple" }, { type: "grape", color: "green", id: "greenGrape", options: [{ bunchName: "bunch1", size: "8" }, { bunchName: "bunch2", size: "10" }, { bunchName: "bunch3", size: "5" }] }];
console.log(findValue(1, "options[0].bunchName"));
From what I understand, output of findValue(object, "bunchName"); should be "bunch3", where object is array in OP's example.
var object =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
var findValue = (object, key) => {
var resultValue;
var rec = (currentObj) => {
if(currentObj && typeof currentObj === "object"){
for(let curKey in currentObj){
if (curKey === key){
resultValue = currentObj[curKey];
}else{
rec(currentObj[curKey]);
}
}
}
}
rec(object);
return resultValue;
}
console.log(findValue(object, "bunchName"));
You could add a function that takes an object and a key and returns object[key] and then split your key string into a list of individual keys by the dot. Then you could traverse the list of keys and use the function to get the value for each level in your object:
Totally untested code I just whipped up:
function valueByKey(obj, key) {
if (obj) {
return obj[key];
}
}
function findValue(index, key) {
const keys = key.split('.');
let value = array[index];
for (let i = 0; i < keys.length; i++) {
value = valueByKey(value, keys[i]);
}
return value;
}
Non-recurrent solution:
var array = [
{
'a': {
'b': 1
}
}
];
function findValue(index, key) {
var keys = key.split('.');
var tmp = array[index];
for (var i = 0; i < keys.length; i++) {
if (!tmp.hasOwnProperty(keys[i]) || typeof tmp !== 'object') {
// throw an exception, or return default value – property not found.
}
tmp = tmp[keys[i]];
}
return tmp;
}
findValue(0, 'a.b');

How to add a new key to multiple indices of an array of objects?

I've got an array of three people. I want to add a new key to multiple objects at once based on an array of indices. Clearly my attempt at using multiple indices doesn't work but I can't seem to find the correct approach.
var array = [
{
"name": "Tom",
},
{
"name": "Dick",
},
{
"name": "Harry",
}
];
array[0,1].title = "Manager";
array[2].title = "Staff";
console.log(array);
Which returns this:
[
{
"name": "Tom",
},
{
"name": "Dick",
"title": "Manager"
},
{
"name": "Harry",
"title": "Staff"
}
]
But I'd like it to return this.
[
{
"name": "Tom",
"title": "Manager"
},
{
"name": "Dick",
"title": "Manager"
},
{
"name": "Harry",
"title": "Staff"
}
]
You cannot use multiple keys by using any separator in arrays.
Wrong: array[x, y]
Correct: array[x] and array[y]
In your case, it will be array[0].title = array[1].title = "manager";
1st method::
array[0].title = "Manager";
array[1].title = "Manager";
array[2].title = "Staff";
array[0,1] will not work.
2nd method::
for(var i=0;i<array.length;i++) {
var msg = "Manager";
if(i===2) {
msg = "Staff"
}
array[i].title = msg
}
You can use a helper function like this
function setMultiple(array, key, indexes, value)
{
for(i in array.length)
{
if(indexes.indexOf(i)>=0){
array[i][key] = value;
}
}
}
And then
setMultiple(array, "title", [0,1], "Manager");
Try this: `
for (var i=0; var<= array.length; i++){
array[i].title = "manager";
}`
Or you can change it around so var is less than or equal to any n range of keys in the index.
EDIT: instead make var <= 1. The point is to make for loops for the range of indices you want to change the title to.
Assuming that you have a bigger set of array objects.
var array = [
{
"name": "Tom",
},
{
"name": "Dick",
},
{
"name": "Harry",
},
.
.
.
];
Create an object for the new keys you want to add like so:
let newKeys = {
'Manager': [0,2],
'Staff': [1]
}
Now you can add more such titles here with the required indexes.
with that, you can do something like:
function addCustomProperty(array, newKeys, newProp) {
for (let key in newKeys) {
array.forEach((el, index) => {
if (key.indexOf(index) > -1) { // if the array corresponding to
el[newProp] = key // the key has the current array object
} // index, then add the key to the
}) // object.
}
return array
}
let someVar = addCustomProperty(array, newKeys, 'title')

Add a key to each value in json array javascript

I would like to transform the below JSon. The input JSon array can be of any size. I know its a basic question but I can't find the duplicate.
var input = [{
"value": 1
}, {
"value": 2
}]
var output = [{
"key": {
"value": 1
}
}, {
"key": {
"value": 2
}
}]
Appreciate all the help.
Create a new array and use Array#forEach to push an object with key = key and a currently iterated object from input as the value.
var input = [{value:1},{value:2}],
result = [];
input.forEach(v => result.push({ 'key': v }));
console.log(result);
Try using this, this should solve your problem
output = input.map(value => ({ "key": value }) );
console.log(output);
I used ES6 for simplicity, but this does exactly the same.
I think this will be the most oldschool and hands-on way of doing this.
var input = [{
"value": 1
}, {
"value": 2
}],
output = [],
newItem,
i = 0, ii = input.length;
for(i; i<ii; i++){
newItem = {};
newItem.key = {"value":input[i].value};
output.push(newItem);
}
console.log(output)

how to construct a json object using info from several javascript arrays

I have two js arrays already, say: names and values (with the same length), now I would like to construct a json object in certain format? For example:
names = ["label1","label2","label3"];
values = [[[0,1],[1,9],[2,10]],[[0,89],[1,91],[2,1]],[[0,1],[1,9],[2,10]]];
I would like to have a json array data_spec in this format:
[{
label:"label1",
data:[[0,1],[1,9],[2,10]]
},
{
label:"label2",
data:[[0,89],[1,91],[2,1]]
},
{
label:"label3",
data:[[0,1],[1,9],[2,10]]
}]
Could anyone tell one how? Thanks a lot!
For a bit of variety and a check,
var data_spec = [];
if (names.length != values.length) {
// panic, throw an exception, log an error or just return an empty array
} else {
for (var i=0, name; name = names[i]; i++) { // assuming a non-sparse array
data_spec[i] = {
label : name,
data : values[i]
};
}
}
That is, non-sparse and not containing anything else that would evaluate to false.
If your framework has an each function added to Array and you don't care about performance,
var data_spec = [];
names.each(function(name) {
data_spec.push({ label : name, data : values[names.indexOf(name)] });
});
If your framework is a clean one like Dojo and puts it somewhere else (ex is Dojo),
var data_spec = [];
dojo.forEach(names, function(name) {
data_spec.push({ label : name, data : values[names.indexOf(name)] });
});
If your framework has an each function that returns an Array of identical length with the results of every operation at their expected position,
var data_spec = arrayOfResultsEach(names, function(name) {
return { label : name, data : values[names.indexOf(name)] };
});
These are just for illustration, indexOf inside loops of arbitrary length is a major code smell.
Just use a loop (make sure the two arrays are of same length)
result = [];
for(var i=0, len=names.length; i < len; i++) {
result.push({label: names[i], data: values[i]});
}
var myArray =
[{
"label": "label1",
"data" :
{
"0": "1",
"1": "9",
"2": "10"
}
},
{
"label": "label2",
"data" :
{
"0": "89",
"1": "91",
"2": "1"
}
},
{
"label": "label3",
"data" :
{
"0": "1",
"1": "9",
"2": "10"
}
}];
alert(myArray[0].data[2]);

Categories