2

I have an array of objects and two of the objects in the array are the same (last two):

 [
        {
            "facilities": 1,
            "place": "Campbellsville",
            "state": "KY",
            "lat": 37.34595018,
            "lon": -85.34544564
        },
        {
            "facilities": 1,
            "place": "Lexington",
            "state": "KY",
            "lat": 38.040584,
            "lon": -84.503716
        },
        {
            "facilities": 1,
            "place": "Hebron",
            "state": "KY",
            "lat": 39.066147,
            "lon": -84.703189
        },
        {
            "facilities": 1,
            "place": "Hebron",
            "state": "KY",
            "lat": 39.066147,
            "lon": -84.703189
        }
    ]

I want to combine the two objects that are the same into a single object with the 'facilities' key to be a sum of each of the 'facilities' values:

    [
        {
            "facilities": 2,
            "place": "Campbellsville",
            "state": "KY",
            "lat": 37.34595018,
            "lon": -85.34544564
        },
        {
            "facilities": 1,
            "place": "Lexington",
            "state": "KY",
            "lat": 38.040584,
            "lon": -84.503716
        },
        {
            "facilities": 2,
            "place": "Hebron",
            "state": "KY",
            "lat": 39.066147,
            "lon": -84.703189
        },
    ]

Is there a way to do this in javascript or using Node's underscore.js?

6 Answers 6

3

A solution in plain js:

We'll loop through the array of items and check for duplicates based on a specified key's value.

When we come across a value that we encountered earlier, this indicates we've found a duplicate. If an object is new, we store the occurrence. If we've seen it before, We'll merge the two objects and store our merged representation.

To be able to quickly check if we've seen an equal object before, we'll use a reference object. This object holds an item for each unique key.

In this example I define:

  • A merge strategy: this holds the logic of incrementing the facilities prop
  • A comparison key: this holds the name of the property that determines the "similarity" between two objects
  • A utility method to convert an object to an array

// Take an array of objects, compare them by `key`, merge if the `key`
// is not unique, return a new array.
var mergeDuplicatesByKey = function(items, mergeStrat, key) {
  return objValues(items.reduce(function(result, item) {
    var id = item[key];
    if (!result[id]) {
      result[id] = item;
    } else {
      result[id] = mergeStrat(result[id], item);
    }

    return result;
  }, {}));
};

// Our merge strategy: 
//  - create a new object
//  - add all of item1 and item2's properties
//  - sum the facilities prop
var merge = function(item1, item2) {
  return Object.assign({}, item1, item2, {
    facilities: item1.facilities + item2.facilities
  });
};

// The example data:
var data = [{
  "facilities": 1,
  "place": "Campbellsville",
  "state": "KY",
  "lat": 37.34595018,
  "lon": -85.34544564
}, {
  "facilities": 1,
  "place": "Lexington",
  "state": "KY",
  "lat": 38.040584,
  "lon": -84.503716
}, {
  "facilities": 1,
  "place": "Hebron",
  "state": "KY",
  "lat": 39.066147,
  "lon": -84.703189
}, {
  "facilities": 1,
  "place": "Hebron",
  "state": "KY",
  "lat": 39.066147,
  "lon": -84.703189
}];

// Call merge method with our own strategy, comparing items by `place`
console.log(mergeDuplicatesByKey(data, merge, "place"));


// Utils:
// Return the values for each key in in an object
function objValues(obj) {
  return Object.keys(obj).map(function(key) {
    return obj[key];
  });
};

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

2 Comments

Worth to mention that such solution requires ECMAScript 2015 (ES6), else Object.assign won't work (unless using a polyfill). Very nice solution anyway, +1. (also, pretty elastic one)
@briosheje Thanks for adding that! Anyone that needs to support older versions, you can find a polyfill here: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
1

One option (that would require two iterations) would be to reduce your array down to an object keyed off of place (so that you can sum up the facilities). You could stop there and just iterate over the produced object, or if you need it as a summed array run Object.keys and just return the object stored at each key.

Reduce to an object:

var reduced = facilities.reduce(function(p, c) {
  if (p[c.place]) p[c.place].facilities++;
  else p[c.place] = c;
  return p;
}, {});

Get the summed values:

Object.keys(reduced).map(function(item) { return reduced[item] });

var facilities =  [
  {
    "facilities": 1,
    "place": "Campbellsville",
    "state": "KY",
    "lat": 37.34595018,
    "lon": -85.34544564
  },
  {
    "facilities": 1,
    "place": "Lexington",
    "state": "KY",
    "lat": 38.040584,
    "lon": -84.503716
  },
  {
    "facilities": 1,
    "place": "Hebron",
    "state": "KY",
    "lat": 39.066147,
    "lon": -84.703189
  },
  {
    "facilities": 1,
    "place": "Hebron",
    "state": "KY",
    "lat": 39.066147,
    "lon": -84.703189
  }
];

var reduced = facilities.reduce((p, c) => {
  if (p[c.place]) p[c.place].facilities++;
  else p[c.place] = c;
  return p;
}, {});

console.log(reduced);

var summedArray = Object.keys(reduced).map(function(item) { return reduced[item];
});

console.log(summedArray);

1 Comment

Nice! FWIW I think you can get the summed values with _.values() (OP mentioned underscore.js). I would also key the items with lat+lon instead of place.
1

First, group by whatever the composite key is in your desired case:

var keyDel = '|';
var groups = _.groupBy(list, function(value){
    return value.place + keyDel + value.state + keyDel + value.lat + value.lon;
});

Then consolidate each group members into one, updating the count:

var data = _.map(groups, function(group){
    return {
        facilities: group.length,
        place: group[0].place,
        state: group[0].state,
        lat: group[0].lat,
        lon: group[0].lon
    }
});

Comments

0

This is a function I gleaned from another stack overflow question that addressed a similar issue. It's in javascript.

var compare = function (json1, json2) {
    var i, l, leftChain, rightChain;

    function compare2Objects (x, y) {
        var p;

        // remember that NaN === NaN returns false
        // and isNaN(undefined) returns true
        if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
            return true;
        }

        // Compare primitives and functions.
        // Check if both arguments link to the same object.
        // Especially useful on the step where we compare prototypes
        if (x === y) {
            return true;
        }

        // Works in case when functions are created in constructor.
        // Comparing dates is a common scenario. Another built-ins?
        // We can even handle functions passed across iframes
        if ((typeof x === 'function' && typeof y === 'function') ||
        (x instanceof Date && y instanceof Date) ||
        (x instanceof RegExp && y instanceof RegExp) ||
        (x instanceof String && y instanceof String) ||
        (x instanceof Number && y instanceof Number)) {
            return x.toString() === y.toString();
        }

        // At last checking prototypes as good as we can
        if (!(x instanceof Object && y instanceof Object)) {
            return false;
        }

        if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
            return false;
        }

        if (x.constructor !== y.constructor) {
            return false;
        }

        if (x.prototype !== y.prototype) {
            return false;
        }

        // Check for infinitive linking loops
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
            return false;
        }

        // Quick checking of one object being a subset of another.
        // todo: cache the structure of arguments[0] for performance
        for (p in y) {
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false;
            }
            else if (typeof y[p] !== typeof x[p]) {
                return false;
            }
        }

        for (p in x) {
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false;
            }
            else if (typeof y[p] !== typeof x[p]) {
                return false;
            }

            switch (typeof (x[p])) {
                case 'object':
                case 'function':

                    leftChain.push(x);
                    rightChain.push(y);

                    if (!compare2Objects (x[p], y[p])) {
                        return false;
                    }

                    leftChain.pop();
                    rightChain.pop();
                    break;

                default:
                    if (x[p] !== y[p]) {
                        return false;
                    }
                    break;
            }
        }

        return true;
    }
    leftChain = []; //Todo: this can be cached
    rightChain = [];
    return (!compare2Objects(json1, json2));
}

I use it to compare Backbone Models (which depends on underscore.js)

Comments

0

Simple and elegant. Can change or comment better if you want. I use a bit of es6.

var _ = require('underscore');

// Check if array arr contains object obj
function exists(arr, obj) {
    var found = false;

    arr.forEach((item) => {
        if (_.isMatch(item, obj)) {
            found = true;
        }
    });

    return found;
}

var final = [];

original.forEach((obj) => {

    if (exists(final, obj)) {
        var index = _.findIndex(final, finalObj => _.isMatch(finalObj, obj)) 
        ++final[index].facilities
    } else {
        final.push(obj);
    }

});

return final;

Comments

0

.uniq accepts a callback

var list = [
        {
            "facilities": 1,
            "place": "Campbellsville",
            "state": "KY",
            "lat": 37.34595018,
            "lon": -85.34544564
        },
        {
            "facilities": 1,
            "place": "Lexington",
            "state": "KY",
            "lat": 38.040584,
            "lon": -84.503716
        },
        {
            "facilities": 1,
            "place": "Hebron",
            "state": "KY",
            "lat": 39.066147,
            "lon": -84.703189
        },
        {
            "facilities": 1,
            "place": "Hebron",
            "state": "KY",
            "lat": 39.066147,
            "lon": -84.703189
        }
    ]

var uniqueList = _.uniq(list, function(item) { 
    return item.toString();
});

 /*
uniqueList will be:
            {
                "facilities": 1,
                "place": "Campbellsville",
                "state": "KY",
                "lat": 37.34595018,
                "lon": -85.34544564
            },
            {
                "facilities": 1,
                "place": "Lexington",
                "state": "KY",
                "lat": 38.040584,
                "lon": -84.503716
            },
            {
                "facilities": 1,
                "place": "Hebron",
                "state": "KY",
                "lat": 39.066147,
                "lon": -84.703189
            }
           */

If you convert the item to String, you should be able to detect duplication.

Notes:

  1. Callback return value used for comparison
  2. First comparison object with unique return value used as unique
  3. underscorejs.org demonstrates no callback usage
  4. lodash.com shows usage

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.