2

Here's what I have in mind:

Given an array of objects:

[
    {
        "name": "Kirk",
        "count": 1
    },
    {
        "name": "Spock",
        "count": 1
    },
    {
        "name": "Kirk",
        "count": 1
    }
]

I am trying to get:

[
    {
        "name": "Kirk",
        "count": 2
    },
    {
        "name": "Spock",
        "count": 1
    }
]

I am wondering if there's already an algorithm, perhaps combining some higher order functions to achieve this. I could do this easily with loops, but I am looking for a way to solve it using higher order functions. If someone could point me to what I should use to achieve this, it would be great. Again, I'm looking for something as elegant as possible (two maps and a filter would not be a big improvement from loops).

This is my current solution and I'm looking for something better (and by better I mean more expressive):

function mergeDuplicates(input) {
  var output = [];
  var existingItem = null;
  input.forEach(function (inputItem) {
    existingItem = _.find(output, function (outputItem) {
      return inputItem.name === outputItem.name;
    });
    existingItem ? existingItem.count += 1 : output.push({
      name: inputItem.name,
      count: 1
    });
    existingItem = null;
  });
  return output;
}

To make line #10 more clear: in the original array, count might be either non-existing or 1, hence I set it to 1.

5
  • I'm pretty sure I have seen this question on StackOverflow before. (Don't know what the answer is myself) Commented Jul 8, 2013 at 17:20
  • if you don't mind external lib. Try underscore.js uniq function. underscorejs.org/#uniq Commented Jul 8, 2013 at 17:22
  • @David, doesn't matter since I doubt the answer meets my criteria anyway. Commented Jul 8, 2013 at 17:23
  • what if the original array contains an object with "count" greater than 1? Commented Jul 8, 2013 at 22:21
  • @groovy It won't. In fact, it is without count initially. Commented Jul 9, 2013 at 4:17

6 Answers 6

2

I think the best way would be to hash each object if it does not already exist, and delete the ones that you found already hashed in your structure. This way, you'd be checking the existence of each object only 1 (depends on your hash scheme).

Sign up to request clarification or add additional context in comments.

Comments

2

Just a function if you would like to use.

function merge(arr) {    
   for(var o = {}, i; i=arr.shift(); o[i.name] = i.count + (o[i.name] || 0));
   for(i in o) arr.push({name:i, count:o[i]});
}

Calling :

var myArray = [{"name":"Kirk","count":1},
               {"name":"Spock","count":1},
               {"name":"Kirk","count":1}];

merge(myArray);   

// myArray is now :  [{"name":"Kirk","count":2}, {"name":"Spock","count":1}]

2 Comments

Not exactly what I was hoping for, but an interesting solution.
Very neat trick, won't work for an object with more than 2 properties.
1

You can use reduce which is actually a fold.

a.reduce(function(p, c) {
        var n = c.name;
        if (p[n])
            p[n].count++;
        else
            p[n] = c;
        return p;
    }, {})

will give you a object with "Kirk" and "Spock" as the key, what you want as values.

1 Comment

I actually want the output to be like in my example -- with name and count as the keys
1

I know this is an old question, but I couldn't resist trying to solve it. Instead of two maps and a filter, we use a sort and then a reduce. This was a fun one to sort out :-)

function mergeDuplicates(list, prop, cb){
  return list.sort(function(a,b){
    if(a[prop] < b[prop]){ return -1;}
    if(a[prop] > b[prop]){return 1;}
    return 0;
  }).reduce(function(acc, item, index, array){
    if(index > 0 && array[index-1][prop] === item[prop]){
      cb(acc[acc.length-1], item);
      return acc;
    }else{
      var newItem = Object.assign({}, item);
      cb(newItem);
      acc.push(newItem);
      return acc;
    }
  }, []);
}

Then use it like this:

var newList = mergeDuplicates(list, "name", function(item, dup){
    if(dup){
      item.count++;
    }else{
      item.count = 1;
    }        
});

EDIT: Here's another take at it using reduce and using an object as a hashmap to store duplicates (similar to some of the other answers). This one uses ramdajs

const mergeDups = (cb, prop, list) => R.pipe(
  R.reduce((acc, item) => (
    R.has(item[prop], acc) ?
      R.assoc(item[prop], cb(acc[item[prop]], item), acc) :
      R.assoc(item[prop], cb(item), acc)     
  ), {}), 
  R.values
)(list);

const cb = (i, d) => ( !R.isNil(d) ? 
  R.assoc('count', i.count + 1, i) :
  R.assoc('count', 1, i) )

mergeDups(cb, 'name', items);

Here it is in the repl on Ramda's site

Comments

0

Try this better i'll useful resolve your issues

cleanup(arrayOfObj, 'name');

function cleanup(arr, prop) {
  var new_arr = [];
  var lookup = {};
  for (var i in arr) {
   lookup[arr[i][prop]] = arr[i];
  }
  for (i in lookup) {
   new_arr.push(lookup[i]); 
  }
  return new_arr;
}

Comments

0

Yet another version using reduce function:

var items =
[
    {
        "name": "Kirk",
        "count": 1
    },
    {
        "name": "Spock",
        "count": 1
    },
    {
        "name": "Kirk",
        "count": 1
    }
];
    

var filtered = items.reduce(function(prev, current,index){ 
    if(!(current.name in prev.keys)) {
       prev.keys[current.name] = index;
       prev.result.push(current);
   } 
   else{
       prev.result[prev.keys[current.name]].count += current.count; 
   }
   return prev;
},{result: [], keys: []}).result;

    

document.getElementById("output").innerHTML = JSON.stringify(filtered,null,2);
<pre id='output' />

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.