0

I need a function that takes in an array and will return an array with all the duplicates. I would prefer to use underscore if possible.

given the array:

[
    "apple",
    "apple",
    "pear",
    "pear",
    "kiwi",
    "peach"
]

I need to return an array

[
    "apple",
    "pear"
]

Many of the methods I've found will return a boolean and not an array of the duplicates.

For example

var fruits = ["apple","apple"];
var uniq_fruits = _.uniq(fruits);
var duplicates_exist = (fruits.length == uniq_fruits.length);
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
  • How would you do this if you didn't have a computer? – Pointy Aug 16 '13 at 18:35
  • @Pointy conveyor belt and color recognition? – ThomasReggi Aug 16 '13 at 18:37
  • So if I wrote down a list of fruit names on a piece of paper, you'd need a conveyor belt to find the duplicates? Isn't there a simpler way to handle a list of names of fruits? Like, is there some way you could keep track of each name you've seen? – Pointy Aug 16 '13 at 18:42
  • @Pointy Not sure where you're going with this. I do appreciate your time, effort, and patience though. I don't get to talk to many coders and I don't like to reinvent the wheel, I was assuming that there was a easy way to get this done with underscore and possibly without `.sort()`. – ThomasReggi Aug 16 '13 at 19:09

7 Answers7

5

You could use _.countBy to get the word frequencies and then use _.reduce to collect up the values with a frequency greater than one:

function collect_dups(a, n, word) {
    if(n > 1)
        a.push(word);
    return a;
}
var dups = _(words).chain()
                   .countBy()
                   .reduce(collect_dups, [])
                   .value();

Demo: http://jsfiddle.net/ambiguous/gKmfh/1/

mu is too short
  • 426,620
  • 70
  • 833
  • 800
3

Turn your list into a map, then turn the map into a list.

var fruits = ["apple", // ... ];

function fruitCounter(countMap, fruit) {
  if (countMap[fruit] == null)
    countMap[fruit] = 1;
  else
    countMap[fruit]++;
  return countMap;
}

function dupFinder(dupList, count, fruit) {
  if (count > 1)
    dupList.push(fruit);
  return dupList;
}

var dups = _.reduce(_.reduce(fruits, fruitCounter, {}), dupFinder, []);

It's sort-of unfortunate that there's nothing really like "filter" for the properties of an object, but it's not too bad with "reduce".

edit — a comment from someone better at Underscore than me points out that the inner "reduce" could be replaced by a simpler "countBy":

var dups = _.reduce(_.countBy(fruits, function(f) { return f; }), dupFinder, []);
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 1
    You can use [`_.countBy`](http://underscorejs.org/#countBy) instead of the inner `_.reduce`. – mu is too short Aug 16 '13 at 19:28
  • @muistooshort ah OK; I barely know Underscore :) – Pointy Aug 16 '13 at 19:30
  • 2
    You can also replace "function(f) { return f; }" with [_.identity](http://underscorejs.org/#identity) – ne8il Aug 16 '13 at 20:47
  • @ne8il oh cool. I've never done much serious work with Underscore, but I've used [Functional](http://osteele.com/sources/javascript/functional/) and it has a similar "I" identity function. – Pointy Aug 16 '13 at 20:51
  • You can also use [`_.chain`](http://underscorejs.org/#chain) to avoid the nested `_.X` calls (see below). – mu is too short Aug 16 '13 at 21:12
1
var common = function(array){

    var tally = function(array){
        var make_object = {};
        _.each(array, function(item){
            make_object[item] = (typeof make_object[item] == "undefined") ? 1 : make_object[item] + 1;
        });
        return make_object;        
    }(array);

    var duplicates = function(obj){
        var duplicates = [];
        _.each(obj, function(value, key){
            if(value > 1){
                duplicates.push(key);
            }
        });
        return duplicates;
    }(tally);

    return duplicates;

};
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
1

The idea is very straight forward. Group the items by its value and then find which group having more than 1 items. Finally pick only one item from each group.

lst = [ "apple", "apple", "pear", "pear", "kiwi", "peach"];
var result = _.chain(lst)
    .groupBy(function (i) { return i; })
    .filter(function (v, k) { return v.length > 1; })
    .map(function(v){return v[0]; })
    .value();

>>["apple", "pear"] 
zs2020
  • 53,766
  • 29
  • 154
  • 219
1

where arr is your input, you just check to see if the element is a key on the obj object - if it is, pass it to the output array and reloop, otherwise add the key to the object:

function findDupes(arr) {
  var obj = {}, newArr = [];
  for (var i = 0, l = arr.length; i < l; i++) {
    if (obj[arr[i]]) { newArr.push(arr[i]); continue; }
    obj[arr[i]] = true;
  }
  return newArr;
}

var dupes = findDupes(arr);

Andy
  • 61,948
  • 13
  • 68
  • 95
  • For what it's worth, Thomas, what @Pointy was talking about, and he and I demonstrated, is that there's one great thing about Javascript objects - their keys are unique. This means you can assign things to them and do a simple test to see if they exist. Unlike Pointy who converted the whole array to an object (map) first, you can do it on the fly as I described above, FWIW, while I like underscore for a lot of heavy-lifting, it's much easier this way IMO. – Andy Aug 17 '13 at 02:07
  • This will push the same item multiple times to the result if duplicated more than twice. –  Sep 09 '13 at 05:45
0

Giving you have a simple one level array of strings, I would suggest to sort an array first and then loop through it trying to compare current item with the next one. Something like this:

var fruit = [
    "apple",
    "apple",
    "apple",
    "pear",
    "pear",
    "cantalope"
];

var common = function(array){
    var mySortedArray = array.sort();
    var myResultArray = [];

    for (var i = 0; i < mySortedArray.length - 1; i++)
      if ( (mySortedArray[i + 1] == mySortedArray[i]) && 
        (mySortedArray[i] != myResultArray[myResultArray.length-1]) )
        myResultArray.push(mySortedArray[i]);

    return myResultArray;
};

alert(common(fruit));
Max Malyk
  • 860
  • 5
  • 8
0

I started from this function : https://stackoverflow.com/a/840849/1636522

function getDuplicates(arr) {
    var i,
        len = arr.length,
        out = [],
        obj = {};
    for (i = 0; i < len; i++) {
        switch (obj[arr[i]]) {
            case undefined: obj[arr[i]] = 1; break;
            case 1: obj[arr[i]] = 2; out.push(arr[i]); break;
        }
    }
    return out;
}
Community
  • 1
  • 1