15

Here in this snippet i am stuck as in _.uniqBy(array,iteratee),this

  • iteratee can be a function or a string at the same time
  • Where to put the condition to check uniqness on the property because itratee function can be anything

var sourceArray = [ { id: 1, name: 'bob' },
  { id: 1, name: 'bill' },
  { id: 1, name: 'bill' } ,
  {id: 2,name: 'silly'},
  {id: 2,name: 'billy'}]

function uniqBy (inputArray, callback) {
  return inputArray.filter(callback)
}
var inputFunc = function (item) {
  return item.name
}

// var destArray = _.uniqBy(sourceArray,'name')

var destArray = uniqBy(sourceArray, inputFunc)
console.log('destArray', destArray)

Any leads on this will be most appreciated.

Gyanesh Gouraw
  • 1,991
  • 4
  • 23
  • 31
  • [Check lodash.uniqBy() code](https://github.com/lodash/lodash/blob/master/lodash.js#L4253) – GillesC Nov 25 '16 at 10:28
  • Yeah, have a look and see if the source code helps: https://github.com/lodash/lodash/blob/4.17.2/lodash.js#L8419 – Fernando Nov 25 '16 at 10:32

5 Answers5

35

An ES6 uniqBy using Map with a complexity of O(n):

const uniqBy = (arr, predicate) => {
  const cb = typeof predicate === 'function' ? predicate : (o) => o[predicate];
  
  return [...arr.reduce((map, item) => {
    const key = (item === null || item === undefined) ? 
      item : cb(item);
    
    map.has(key) || map.set(key, item);
    
    return map;
  }, new Map()).values()];
};

const sourceArray = [ 
  { id: 1, name: 'bob' },
  { id: 1, name: 'bill' },
  null,
  { id: 1, name: 'bill' } ,
  { id: 2,name: 'silly'},
  { id: 2,name: 'billy'},
  null,
  undefined
];

console.log('id string: ', uniqBy(sourceArray, 'id'));

console.log('name func: ', uniqBy(sourceArray, (o) => o.name));
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • When there is a Object/item with null or undefined it breaks. Lodash handles this differently. E.g: _.uniqBy([{ 'x': 1 }, null, { 'x': 1 }, { 'y': 1 }], 'x'). Results into: [Object{x: 1}, null] – M. Suurland May 03 '19 at 11:53
  • @M.Suurland - good catch. Updated answer to handle `null` and `undefined` values in the same way. – Ori Drori May 03 '19 at 21:19
  • I like this implementation, but I keep getting "`.slice()` is not a function". Any ideas? – slimeygecko Dec 06 '19 at 17:56
  • 1
    My solution contain 0 uses of `.slice()` so I can't really help you with the :) – Ori Drori Dec 06 '19 at 17:58
  • 1
    I'm running my code through Webpack via CreateReactApp, it must be using a polyfill for spread that uses slice. I posted a variation of your solution: https://stackoverflow.com/a/59218578/2691271 (inline code doesn't work so well :) – slimeygecko Dec 06 '19 at 18:37
  • Is this faster than the Lodash version? Or is it pretty much the same thing? – ma1234 Jul 14 '21 at 16:02
3

Refactored @ori-drori's solution and removed

  1. undefined
  2. null
  3. extra numbers in mixed array
  4. return [] if first param is not Array

const uniqBy = (arr, predicate) => {
  if (!Array.isArray(arr)) { return []; }

  const cb = typeof predicate === 'function' ? predicate : (o) => o[predicate];

  const pickedObjects = arr
    .filter(item => item)
    .reduce((map, item) => {
        const key = cb(item);

        if (!key) { return map; }

        return map.has(key) ? map : map.set(key, item);
    }, new Map())
    .values();
 
  return [...pickedObjects];
};

const a = [ 
  12,
  undefined,
  { id: 1, name: 'bob' },
  null,
  { id: 1, name: 'bill' },
  null,
  undefined
];

const b = [ 
  12,
  { id: 1, name: 'bob' },
  { id: 1, name: 'bill' },
];

uniqBy(a, 'name');
uniqBy(b, Math.floor);
uniqBy([2.1, 1.2, 2.3], Math.floor);
w3debugger
  • 2,097
  • 19
  • 23
2

I'm running my code through Webpack via CreateReactApp, it must be using a polyfill for spread that uses slice. Here's what I did instead, a variation of @oridori's answer:

const uniqBy = (arr: any[], predicate: (item: any) => string) => {
  const cb = typeof predicate === 'function' ? predicate : (o) => o[predicate];
  const result = [];
  const map = new Map();

  arr.forEach((item) => {
    const key = (item === null || item === undefined) ? item : cb(item);

    if (!map.has(key)) {
      map.set(key, item);
      result.push(item);
    }
  });

  return result;
};
slimeygecko
  • 630
  • 7
  • 13
2

For those looking for the one line answer

    const inputArr = [
      { id: 1, name: { first: 'bob' } },
      { id: 1, name: { first: 'bill' } },
      { id: 1, name: { first: 'bill' } },
      { id: 1 },
      undefined,
      { id: 2, name: { first: 'silly' } },
      { id: 2, name: { first: 'billy' } }
    ]
    var uniqBy = (arr, key) => {
      return Object.values([...arr].reverse().reduce((m, i) => {m[key.split('.').reduce((a, p) => a?.[p], i)] = i; return m;}, {}))
    }
    console.log(uniqBy(inputArr, 'id'))
    console.log(uniqBy(inputArr, 'name.first'))
Yehuda Schwartz
  • 3,378
  • 3
  • 29
  • 38
  • 1
    I went with this solution as it's the only one that worked with nested properties One issue though, reverse function mutates the array, so you can't use a `const` to define the original array. A solution is to make a copy of the array before reversing it: `[...arr].reverse()` – Akaryatrh Jun 14 '23 at 11:25
0

You could use a sort ordered by name and a filter based on the neighborhood comparison like this :

var sourceArray = [ { id: 1, name: 'bob' },
  { id: 1, name: 'bill' },
  { id: 1, name: 'bill' } ,
  {id: 2,name: 'silly'},
  {id: 2,name: 'billy'}]

var uniqBy = (inputArray, callback) => inputArray.sort((a,b) => callback(a) > callback(b))
.filter((x,i,arr) => i === arr.length -1 ? true : callback(x) !== callback(arr[i+1]));
var inputFunc = item => item.name;


var destArray = uniqBy(sourceArray, inputFunc)
console.log('destArray', destArray)
kevin ternet
  • 4,514
  • 2
  • 19
  • 27