11

So after searching the interwebz for a few hours I have not found the solution I am looking for.

I have two arrays that contain game objects with a lot of information inside. (e.g. title, slug, thumbnail, summary, genre, release date...).

The Array 1 is a collection of objects that match user's interests specified during the registration.

The Array 2 is a collection of objects that match purchased games of similar users. (Similar users are those that share common interests)

Problem: It is possible, and what is happening in my case, there are two identical games - the game in Array 1 is also in Array 2. In the first array the game is there because it matches user's interests. In the second array the game is there because a similar user has bought that game.

Question: Underscore.js has a nice little function union() http://underscorejs.org/#union that gives you a union of two arrays, but it does not work with an array of objects, only on primitive values. How could I make it work give me a union of array of objects?

Ashwani Panwar
  • 3,819
  • 3
  • 46
  • 66
Sahat Yalkabov
  • 32,654
  • 43
  • 110
  • 175
  • So what you need is a deep merge and extend? https://gist.github.com/1868955 http://stackoverflow.com/questions/7549574/merge-js-objects-without-overwriting http://stackoverflow.com/questions/896043/how-can-i-merge-2-javascript-objects-populating-the-properties-in-one-if-they-d – jcolebrand Nov 10 '12 at 04:10
  • http://phrogz.net/JS/ArraySetMath.js – Phrogz Nov 10 '12 at 04:19
  • 1
    I know this isn't your question, but have you considered using unique gameIds and using key:value object for your collections? – cbayram Nov 10 '12 at 04:33
  • @cbayram I am using MongoDB. The reason for getting the game twice in this case is because the game matches user's interests query and I have a reference to Game collection inside User.purchasedGames array: purchasedGames: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Game' }}] which is another nested query. So I get separate results from 2 queries and then union them together. – Sahat Yalkabov Nov 10 '12 at 04:49

9 Answers9

22

You could implement your own pretty easily. In this case, we make the function generic, so that it can take arrays of any data type(s) and union them using the comparator function provided.

// arr1 and arr2 are arrays of any length; equalityFunc is a function which
// can compare two items and return true if they're equal and false otherwise
function arrayUnion(arr1, arr2, equalityFunc) {
    var union = arr1.concat(arr2);

    for (var i = 0; i < union.length; i++) {
        for (var j = i+1; j < union.length; j++) {
            if (equalityFunc(union[i], union[j])) {
                union.splice(j, 1);
                j--;
            }
        }
    }

    return union;
}

function areGamesEqual(g1, g2) {
    return g1.title === g2.title;
}

// Function call example
arrayUnion(arr1, arr2, areGamesEqual);

Refer to Object comparison in JavaScript for various object comparison implementations.

Community
  • 1
  • 1
Chris Hayes
  • 11,471
  • 4
  • 32
  • 47
  • Beat me to it. Nice implementation. – L0j1k Nov 10 '12 at 04:18
  • Hmm no luck getting it to work. Returns undefined. https://gist.github.com/4049901 – Sahat Yalkabov Nov 10 '12 at 04:30
  • Yeah, that's unsurprising when I forgot to `return` a value. :) Editing. – Chris Hayes Nov 10 '12 at 04:31
  • 2
    Nice. You could speed it up by having the outer loop stop at `arr1.length` and have the inner loop start at `arr1.length` (assuming we know that array 1 only has unique values and array 2 only has unique values). @TwilightPonyInc. - implementing `are_games_same()` is up to you depending on the structure of the objects, perhaps something like `return g1.gameID === g2.gameID;`. – nnnnnn Nov 10 '12 at 04:37
  • you need to add `j -= 1;` after splicing. https://gist.github.com/dandybreath/85aa931c979f20d639f0 – dandice Jul 01 '15 at 16:00
  • @chocolateentities Actually I needed two changes - we should `splice` at `j` rather than at `i`. Thanks for catching that! – Chris Hayes Jul 01 '15 at 18:20
5

You can do it using the underscore way:

// collectionUnion(*arrays, iteratee)
function collectionUnion() {
    var args = Array.prototype.slice.call(arguments);
    var it = args.pop();

    return _.uniq(_.flatten(args, true), it);
}

It just an improvment of the original function _.union(*arrays), adding an iteratee to work collection (array of object).

Here how to use it:

var result = collectionUnion(a, b, c, function (item) {
    return item.id;
});

The original function which is just working with array, looks like that:

_.union = function() {
  return _.uniq(flatten(arguments, true, true));
};

And in bonus a full example:

// collectionUnion(*arrays, iteratee)
function collectionUnion() {
    var args = Array.prototype.slice.call(arguments);
    var it = args.pop();

    return _.uniq(_.flatten(args, true), it);
}

var a = [{id: 0}, {id: 1}, {id: 2}];
var b = [{id: 2}, {id: 3}];
var c = [{id: 0}, {id: 1}, {id: 2}];

var result = collectionUnion(a, b, c, function (item) {
    return item.id;
});

console.log(result); // [ { id: 0 }, { id: 1 }, { id: 2 }, { id: 3 } ]
Arnaud Valensi
  • 156
  • 1
  • 3
  • In the collection union (last bonus a full example) i will use: `_.uniqBy(_.flatten(args, true), 'prop-id')` https://lodash.com/docs/4.17.4#uniqBy – Crisboot Nov 19 '17 at 01:23
3

Set (ES6/ES2015) will help you.

const info1 = {id: 1}
const info2 = {id: 2}
const info3 = {id: 3}

const array1 = [info1, info2]
const array2 = [info1, info3]

const union = [...new Set([...array1, ...array2])]

console.log(union)
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
  • When using TypeScript and you don't want to turn on `--downLevelIteration` you can do `const union = [...Array.from(new Set([...array1, ...array2]))]` – mtpultz Jul 13 '18 at 06:28
1

jQuery has the method extend:

$.extend(true, object1, object2);
  • So does underscore.js, but I was looking for a union operation. Extend from what I understand is for merging two objects. I want to eliminate an object entirely if it's already in the array. I guess you could use extend(), it just wasn't the first thing that came to my mind. – Sahat Yalkabov Nov 10 '12 at 04:42
1

Using underscore extend, you can do:

var objects = [{ bar : 1, nuts : 2} , {foo : 3, nuts : 4}]
_.extend.apply(this,objects)

If the list can be empty, make sure to append an empty object to it.

John Salvatier
  • 3,077
  • 4
  • 26
  • 31
1

Here is better solution

function arrayUnion() {
    var args = Array.prototype.slice.call(arguments);
    var it = args.pop();

    return _.uniq(_.flatten(args, true), it);
}

var a = [{id: 0}, {id: 1}, {id: 2}];
var b = [{id: 2}, {id: 3}];
var c = [{id: 0}, {id: 1}, {id: 2}];

var result = arrayUnion(a, b, c, function (item) {
    return item.id;
});

console.log(result); 
1

It is really easy to do it in typescript using the set function.

const firstArr = [
    {
        id: '1',
        winner: 'Captain America',
    },
    {
        id: '4',
        winner: 'Aquaman',
    },
    {
        id: '2',
        winner: 'Captain America',
    },
    {
        id: '3',
        winner: 'Aquaman',
    },
]

const secondArr = [
    {
        id: '1',
        winner: 'Wonder women',
    },
    {
        id: '2',
        winner: 'Param',
        strenths: ['fly', 'fight', 'speed'],
    },
]
const mergedArray = [...secondArr, ...firstArr]

let set = new Set()
let unionArray = mergedArray.filter((item) => {
    if (!set.has(item.id)) {
        set.add(item.id)
        return true
    }
    return false
}, set)
 
console.log(unionArray);
DinoMyte
  • 8,737
  • 1
  • 19
  • 26
gem007bd
  • 1,085
  • 13
  • 11
0

const a=[
  {
    id:1,
    label:"mnbmnbm1"
  },
  {
    id:2,
    label:"mnbmnbm2"
  },
  {
    id:3,
    label:"mnbmnbm3"
  }
];
const b=[
  {
    id:1,
    label:"mnbmnbm1"
  },
  {
    id:2,
    label:"mnbmnbm2"
  },
  {
    id:4,
    label:"mnbmnbm5"
  }
];
const c=[
  {
    id:1,
    label:"mnbmnbm1"
  },
  {
    id:5,
    label:"mnbmnbm5"
  },
  {
    id:6,
    label:"mnbmnbm6"
  }
];

function arrayUnion(...args){
  return arrayUnique(flatten(...args));
}

function flatten(...args){
  return args.reduce((acc,cur)=>{
     if(Array.isArray(acc) && Array.isArray(cur)){
        acc=[...acc,...cur];
        return acc;
     }
  });
}

function arrayUnique(arr){
    let map={};
    
    return arr.filter(obj =>{
      let objID=obj.id;
      
      if(!map[objID]){
        map[objID]=true;
        return obj;
      }
    });
}

console.log("result",arrayUnion(a,b,c));
Rimika
  • 167
  • 1
  • 8
-1

You can use flat and Set:

const union = (...arr) => [...new Set(arr.flat())];

console.log(union([1, 2], [2, 3], [3]));
Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87