41

Say I have an array like this: [1, 1, 2, 2, 3]

I want to get the duplicates which are in this case: [1, 2]

Does lodash support this? I want to do it in the shortest way possible.

zianwar
  • 3,642
  • 3
  • 28
  • 37
  • Is an array already sorted? – Kiril Jul 28 '15 at 17:39
  • Possible duplicate of [Using lodash to check whether an array has duplicate values](http://stackoverflow.com/questions/28461014/using-lodash-to-check-whether-an-array-has-duplicate-values) – Gajus Feb 29 '16 at 16:19

13 Answers13

55

You can use this:

_.filter(arr, (val, i, iteratee) => _.includes(iteratee, val, i + 1))

Note that if a number appears more than two times in your array you can always use _.uniq.

saadel
  • 1,617
  • 1
  • 14
  • 19
  • 4
    One-liner, with some ES2015 sugar: `const duplicates = _.filter(array, (value, index, iteratee) => _.includes(iteratee, value, index + 1))` – Pier-Luc Gendreau May 19 '16 at 17:46
  • 4
    It works with objects and more complex structures if you use `_.find` instead of `_.includes` `_.filter(array, (value, index, iteratee) => { return _.find(iteratee, value, index + 1) })` – maraujop Apr 03 '18 at 14:50
  • 2
    Could you add an explanation of what's going on for those who are not as familiar with iteratee? – ahong Jun 28 '19 at 08:56
  • @ahong `iteratee===array` this is equivalent to a nested loop `for(;;){ for(;;){` – Vitim.us Oct 10 '19 at 21:38
  • @Vitim.us I understand nested loop, but what does `iteratee===array` mean? – ahong Oct 11 '19 at 09:06
35

Another way is to group by unique items, and return the group keys that have more than 1 item

_([1, 1, 2, 2, 3]).groupBy().pickBy(x => x.length > 1).keys().value()
gafi
  • 12,113
  • 2
  • 30
  • 32
18
var array = [1, 1, 2, 2, 3];
var groupped = _.groupBy(array, function (n) {return n});
var result = _.uniq(_.flatten(_.filter(groupped, function (n) {return n.length > 1})));

This works for unsorted arrays as well.

Mikhail Romanov
  • 1,542
  • 1
  • 13
  • 20
6

Another way, but using filters and ecmaScript 2015 (ES6)

var array = [1, 1, 2, 2, 3];

_.filter(array, v => 
  _.filter(array, v1 => v1 === v).length > 1);

//→ [1, 1, 2, 2]
Rafael Zeffa
  • 2,334
  • 22
  • 20
  • I like, very simple! The trick is that it identifies if more than one exists on the sub-filter. – justinpage Sep 07 '16 at 23:17
  • yes, note the output. Using this method if you input [1,1,1] it will return [1,1,1] Which is sometimes what you want. And sometimes not what you want :-) – kiwichris Aug 23 '23 at 05:00
6

How about using countBy() followed by reduce()?

const items = [1,1,2,3,3,3,4,5,6,7,7];

const dup = _(items)
    .countBy()
    .reduce((acc, val, key) => val > 1 ? acc.concat(key) : acc, [])
    .map(_.toNumber)

console.log(dup);
// [1, 3, 7]

http://jsbin.com/panama/edit?js,console

Brian Park
  • 2,557
  • 22
  • 21
6

Pure JS solution:

export function hasDuplicates(array) {
  return new Set(array).size !== array.length
}

For an array of objects:

/**
 * Detects whether an array has duplicated objects.
 * 
 * @param array
 * @param key
 */
export const hasDuplicatedObjects = <T>(array: T[], key: keyof T): boolean => {
  const _array = array.map((element: T) => element[key]);

  return new Set(_array).size !== _array.length;
};
Vadorequest
  • 16,593
  • 24
  • 118
  • 215
3

Here is another concise solution:

let data = [1, 1, 2, 2, 3]

let result = _.uniq(_.filter(data, (v, i, a) => a.indexOf(v) !== i))

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

_.uniq takes care of the dubs which _.filter comes back with.

Same with ES6 and Set:

let data = [1, 1, 2, 2, 3]

let result = new Set(data.filter((v, i, a) => a.indexOf(v) !== i))

console.log(Array.from(result))
Akrion
  • 18,117
  • 1
  • 34
  • 54
1

Well you can use this piece of code which is much faster as it has a complexity of O(n) and this doesn't use Lodash.

[1, 1, 2, 2, 3]
.reduce((agg,col) => {
  agg.filter[col] = agg.filter[col]? agg.dup.push(col): 2;
  return agg
 },
 {filter:{},dup:[]})
.dup;

//result:[1,2]
1

here is mine, es6-like, deps-free, answer. with filter instead of reducer

// this checks if elements of one list contains elements of second list 
// example code
[0,1,2,3,8,9].filter(item => [3,4,5,6,7].indexOf(item) > -1)

// function
const contains = (listA, listB) => listA.filter(item => listB.indexOf(item) > -1) 
contains([0,1,2,3], [1,2,3,4]) // => [1, 2, 3]

// only for bool
const hasDuplicates = (listA, listB) => !!contains(listA, listB).length

edit: hmm my bad is: I've read q as general question but this is strictly for lodash, however my point is - you don't need lodash in here :)

vonsko
  • 387
  • 4
  • 10
1

You can make use of a counter object. This will have each number as key and total number of occurrence as their value. You can use filter to get the numbers when the counter for the number becomes 2

const array = [1, 1, 2, 2, 3],
      counter = {};
      
const duplicates = array.filter(n => (counter[n] = counter[n] + 1 || 1) === 2)

console.log(duplicates)
adiga
  • 34,372
  • 9
  • 61
  • 83
0

Hope below solution helps you and it will be useful in all conditions

  hasDataExist(listObj, key, value): boolean {
    return _.find(listObj, function(o) { return _.get(o, key) == value }) != undefined;
  }



  let duplcateIndex = this.service.hasDataExist(this.list, 'xyz', value);
0

No need to use lodash, you can use following code:

function getDuplicates(array, key) {
  return array.filter(e1=>{
    if(array.filter(e2=>{
      return e1[key] === e2[key];
    }).length > 1) {
      return e1;
    }
  })
}
-5

Why don't use just this?

_.uniq([4, 1, 5, 1, 2, 4, 2, 3, 4]) // [4, 1, 5, 2, 3]
olvin
  • 31
  • 4