whyAto8
whyAto8

Reputation: 1670

javascript - find unique objects in array based on multiple properties

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

Answers (9)

Roman
Roman

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

Ann
Ann

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

Asaf
Asaf

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

A-P
A-P

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

Nam Tang
Nam Tang

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

Jason Harwig
Jason Harwig

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

Nina Scholz
Nina Scholz

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

Tamo Maes
Tamo Maes

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

brk
brk

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)

JSFIDDLE

Upvotes: 0

Related Questions