Reputation: 1670
I need to find unique objects from array based on 2 properties as below. When "class" and "fare" match, I need to pull out unique values and get them in results array.
Source:
var arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
Expected result:
var result = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "c"}
]
I looked over in SO and was able to find answer which is filtered based on one property (Create array of unique objects by property), but couldn't find which could do based on 2 properties.
Upvotes: 26
Views: 44108
Reputation: 21765
Another several variations of extracting a unique array of objects based on arbitrary array of their fields, tested on performance:
Always the best by performance, uses recursive function
const arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
const getUniqArrBy = (props = [], arrInp = [], objTmp={}, arrTmp=[]) => {
if (arrInp.length > 0) {
const lastItem = arrInp[arrInp.length -1]
const propStr = props.reduce((res, item) => (`${res}${lastItem[item]}`), '')
if (!objTmp[propStr]) {
objTmp[propStr] = true
arrTmp=[...arrTmp, lastItem]
}
arrInp.pop()
return getUniqArrBy(props, arrInp, objTmp, arrTmp)
}
return arrTmp
}
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
/*
[
{class: "first", fare: "c"},
{class: "second", fare: "a"},
{class: "first", fare: "a"},
{class: "second", fare: "b"},
]
*/
1.5% lags behind the first one by performance
const arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
const getUniqArrBy = (props = [], arrInp = []) => {
const objKey = {}
return arrInp.reduce((res, item) => {
const valStr = props.reduce((res, prop) => `${res}${item[prop]}`, '')
if(objKey[valStr]) return res
objKey[valStr] = item
return [...res, item]
}, [])
}
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
/*
[
{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "c"}
]
*/
Close to the fist two ones by performance
const arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
const getUniqArrBy = (props = [], arr = []) => arr.filter((e, i) =>
arr.findIndex(a => {
let aKey = '', eKey = ''
props.forEach(prop => (aKey = `${aKey}${a[prop]}`, eKey = `${eKey}${e[prop]}`))
return aKey === eKey
}) === i)
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
/*
[
{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "c"}
]
*/
In Firefox works fast, in Chrome and Edge - not, short by notation
const arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
const getUniqArrBy = (props = [], arrInp = []) => {
return Object.values(arrInp.reduce((res, item) => {
const keyComb = props.reduce((res, prop) => `${res}${item[prop]}`, '')
return { ...res, [keyComb]: item }
}, []))
}
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
/*
[
{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "c"}
]
*/
The short by notation, but usually a 5-8% slower then the first
const arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
const getUniqArrBy = (props = [], arr = []) => arr.filter((e, i) =>
arr.findIndex(a => props.reduce((res, prop) => `${res}${a[prop]}`, '') ===
props.reduce((res, prop) => `${res}${e[prop]}`, '')) === i)
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
/*
[
{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "c"}
]
*/
A slower by 9-18% than the first in average in different browsers
const arr = [{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "a"},
{class:"second", fare: "a"},
{class:"first", fare: "c"}
]
const getUniqArrBy = (props = [], arrInp = []) => {
const objKey = arrInp.reduce((res, item) => {
const valStr = props.reduce((res, prop) => `${res}${item[prop]}`, '')
return {...res, [valStr]: item }
}, {})
return Object.keys(objKey).map(item => objKey[item])
}
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
/*
[
{class:"second", fare: "a"},
{class:"second", fare: "b"},
{class:"first", fare: "a"},
{class:"first", fare: "c"}
]
*/
Upvotes: 2
Reputation: 197
Another variation of extracting a unique array of objects based on arbitrary array of their fields:
const arr = [
{ class: 'second', fare: 'a', weight: 12 },
{ class: 'second', fare: 'b', weight: 10 },
{ class: 'first', fare: 'a', weight: 15 },
{ class: 'first', fare: 'a', weight: 17 },
{ class: 'second', fare: 'a', weight: 12 },
{ class: 'first', fare: 'c', weight: 30 },
{ class: 'second', fare: 'b', weight: 22 },
]
const getUniqArrBy = (props = [], arrInp = [{}]) => {
const obj = {}
let result = []
arrInp.forEach((item, index) => {
let key = ''
props.forEach(prop => (key += item[prop]))
obj[key] = index
})
const keys = Object.keys(obj)
keys.forEach(key => (result = [...result, arrInp[obj[key]]]))
return result
}
const uniq = getUniqArrBy(['class', 'fare'], arr)
console.info({ uniq })
Upvotes: 1
Reputation: 1261
Hey you can use this method
const unique = (arr, props = []) => [...new Map(arr.map(entry => [props.map(k => entry[k]).join('|'), entry])).values()];
const array = [
{
class: 'second',
fare: 'a',
},
{
class: 'second',
fare: 'b',
},
{
class: 'first',
fare: 'a',
},
{
class: 'first',
fare: 'a',
},
{
class: 'second',
fare: 'a',
},
{
class: 'first',
fare: 'c',
},
];
const newArr = unique(array, ['class', 'fare']);
console.log(newArr) // [{ class: 'second', fare: 'a' }, { class: 'second', fare: 'b' }, { class: 'first', fare: 'a' }, { class: 'first', fare: 'c' }]
Upvotes: 8
Reputation: 198
short way to solve the above problem though it is a very old thread and maybe it might help someone in the future.
const array = [
{ class: "second", fare: "a" },
{ class: "second", fare: "b" },
{ class: "first", fare: "a" },
{ class: "first", fare: "a" },
{ class: "second", fare: "a" },
{ class: "first", fare: "c" }
];
const uniqueObjects = new Set();
array.forEach(arr => uniqueObjects.add(JSON.stringify(arr)));
console.log(uniqueObjects.entries())
Upvotes: 4
Reputation: 100
function unique(arr, keyProps) {
const kvArray = arr.map(entry => { // entry = {class: "second", fare: "a"}
// keyProps = ["class", "fare"]
// k="class"; entry[k]="second";
// k="fare"; entry[k]="a"
// keyProps.map(k => entry[k])=["second","a"];
// .join("|")="second|a";
const key = keyProps.map(k => entry[k]).join('|'); // key = "second|a"
return [key, entry]; // ["second|a",{"class":"second","fare":"a"}]
});
// kvArray = [["second|a",{"class":"second","fare":"a"}],["second|b",
// {"class":"second","fare":"b"}],["first|a",{"class":"first","fare":"a"}],
// ["first|a",{"class":"first","fare":"a"}],["second|a",
// {"class":"second","fare":"a"}],["first|c",{"class":"first","fare":"c"}]]
const map = new Map(kvArray); // this will remove duplicate entry with same key.
return Array.from(map.values()); // convert back to array from all entry's value
}
Upvotes: 1
Reputation: 45551
Like most answers, convert to a map with keys being a concatenation of the values, then back to an array. This one uses reduce
.
const array = [
{ class: "second", fare: "a" },
{ class: "second", fare: "b" },
{ class: "first", fare: "a" },
{ class: "first", fare: "a" },
{ class: "second", fare: "a" },
{ class: "first", fare: "c" }
];
console.log(unique(array, ['class', 'fare']));
function unique(arr, keyProps) {
return Object.values(arr.reduce((uniqueMap, entry) => {
const key = keyProps.map(k => entry[k]).join('|');
if (!(key in uniqueMap)) uniqueMap[key] = entry;
return uniqueMap;
}, {}));
}
Upvotes: 2
Reputation: 386560
You could build a combined key for the hash table and filter the given array.
var arr = [{ class: "second", fare: "a" }, { class: "second", fare: "b" }, { class: "first", fare: "a" }, { class: "first", fare: "a" }, { class: "second", fare: "a" }, { class: "first", fare: "c" }],
result = arr.filter(function (a) {
var key = a.class + '|' + a.fare;
if (!this[key]) {
this[key] = true;
return true;
}
}, Object.create(null));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The same without (mis)using thisArg
of Array#filter
.
var array = [{ class: "second", fare: "a" }, { class: "second", fare: "b" }, { class: "first", fare: "a" }, { class: "first", fare: "a" }, { class: "second", fare: "a" }, { class: "first", fare: "c" }],
seen = Object.create(null),
result = array.filter(o => {
var key = ['class', 'fare'].map(k => o[k]).join('|');
if (!seen[key]) {
seen[key] = true;
return true;
}
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 34
Reputation: 365
Using a combined key and a Map
const array = [
{ class: "second", fare: "a" },
{ class: "second", fare: "b" },
{ class: "first", fare: "a" },
{ class: "first", fare: "a" },
{ class: "second", fare: "a" },
{ class: "first", fare: "c" }
];
console.log(unique(array, ['class', 'fare']));
function unique(arr, keyProps) {
const kvArray = arr.map(entry => {
const key = keyProps.map(k => entry[k]).join('|');
return [key, entry];
});
const map = new Map(kvArray);
return Array.from(map.values());
}
Upvotes: 19
Reputation: 50291
You can use forEach
to loop and filter
or find
array property to find if the element exist in array
var arr = [{
class: "second",
fare: "a"
}, {
class: "second",
fare: "b"
}, {
class: "first",
fare: "a"
}, {
class: "first",
fare: "a"
}, {
class: "second",
fare: "a"
}, {
class: "first",
fare: "c"
}]
var _unArray = []; // new array without duplicate
arr.forEach(function(item) { // loop through array which contain duplicate
// if item is not found in _unArray it will return empty array
var isPresent = _unArray.filter(function(elem) {
return elem.class === item.class && elem.fare === item.fare
})
if (isPresent.length == 0) {
_unArray.push(item)
}
})
console.log(_unArray)
Upvotes: 0