37

I am aware of this question, simplest code for array intersection but all the solutions presume the number of arrays is two, which cannot be certain in my case.

I have divs on a page with data that contains arrays. I want to find the values common to all arrays. I do not know how many divs/arrays I will have in advance. What is the best way to calculate values common to all arrays?

var array1 = ["Lorem", "ipsum", "dolor"];
var array2 = ["Lorem", "ipsum", "quick", "brown", "foo"];
var array3 = ["Jumps", "Over", "Lazy", "Lorem"];
var array4 = [1337, 420, 666, "Lorem"];
//Result should be ["Lorem"];

I found another solution elsewhere, using Underscore.js.

var arrayOfArrays = [[4234, 2323, 43], [1323, 43, 1313], [23, 34, 43]];
_.intersection.apply(_, arrayOfArrays)
//Result is [43]

I've tested this with simple dummy data at my end and it seems to work. But for some reason, some of the arrays I'm producing, which contain simple strings, also automatically include an added value, "equals: function":

["Dummy1", "Dummy2", "Dummy3", equals: function]

And whenever I use the Underscore.js intersection method, on an array of arrays, I always get [equals: function] in dev tools, and not - if "Dummy3" is common to all arrays - ["Dummy3"].

So TL;DR is there another solution to array intersection that would suit my case? And can anyone explain what [equals: function] means here? When I expand the item in the dev tools, it produces an empty array and a list of methods available on arrays (pop, push, shift etc), but these methods are all faded out, while equals: function is highlighted.

Community
  • 1
  • 1
  • The Underscore.js example seems a bit misleading? The last array does not contain 43. – Arg0n May 19 '16 at 10:22
  • kk I'll correct this :s –  May 19 '16 at 10:23
  • You have a `,` missing at the end of `array2`. I've just tried using `_.intersection(array1, array2, array3, array4)` in the lodash.com console and it works returning `"Lorem"` – Alberto Zaccagni May 19 '16 at 10:23
  • I don't know in advance how many divs with data I'll have. I open a window, and divs appear. So I can't just do _.intersection(array1... array4). I could loop through them and push them into a single array of arrays when the window loads, however, but that causes the problem I explain in the second half with [equals: function]. –  May 19 '16 at 10:25

18 Answers18

68

You could just use Array#reduce with Array#filter and Array#includes.

var array1 = ["Lorem", "ipsum", "dolor"],
    array2 = ["Lorem", "ipsum", "quick", "brown", "foo"],
    array3 = ["Jumps", "Over", "Lazy", "Lorem"],
    array4 = [1337, 420, 666, "Lorem"],
    data = [array1, array2, array3, array4],
    result = data.reduce((a, b) => a.filter(c => b.includes(c)));

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 3
    What's the mathematical complexity of this function? Looks like at least O(n²). – marked-down Mar 08 '19 at 03:36
  • @ReactingToAngularVues, it is not quadratic, because the part result might get shorter for every loop. – Nina Scholz Apr 05 '19 at 11:13
  • 2
    I think *O(n * log n)* is correct. I think one thing that would optimize this solution even more is to first find the shortest array and move it to the first index of the data array. In the reduce function we are using the first element in data as the starting point, and then checking every element in that array. If that initial array is the shortest possible, extra iterations are removed. For instance imagine our data array looked like this: [[1, 2, 3, .... , 10000], [1]] Swapping the order of those two elements would make a big difference. Great solution, @NinaScholz ! – Nathan May 20 '21 at 16:40
  • It’s Ω(mn) for n total elements after the first list and m final elements, which is the same kind of performance trap as typical accidentally quadratic things. Easily improved with a `Set`. – Ry- Nov 10 '21 at 05:54
  • @Nathan: It’s definitely not O(n log n). – Ry- Nov 10 '21 at 05:54
  • For runtime complexity you have to look at the worst case scenario. In this case let's assume our lists are all the same arbitrary length, and they have no elements in common. In order to find the intersection we must check every element in the first array against _every_ element in the next array. Repeat that for each list. So for each element in each array (n elements per array) we have to perform n checks. This is n^2. If we want to improve this, we can pre-sort the arrays and use binary search to determine whether to keep each element in the original array, bringing us down to nlogn – Elizabeth Loosevelt Dec 14 '21 at 21:40
  • If any of array is empty, for instance `array3=[]`, but others have elements. Does the code still apply? – Bigeyes Sep 27 '22 at 13:49
  • 1
    @Bigeyes, it would work, but returns an empty array. – Nina Scholz Oct 07 '22 at 20:20
10

I wrote a helper function for this:

function intersection() {
  var result = [];
  var lists;

  if(arguments.length === 1) {
    lists = arguments[0];
  } else {
    lists = arguments;
  }

  for(var i = 0; i < lists.length; i++) {
    var currentList = lists[i];
    for(var y = 0; y < currentList.length; y++) {
        var currentValue = currentList[y];
      if(result.indexOf(currentValue) === -1) {
        var existsInAll = true;
        for(var x = 0; x < lists.length; x++) {
          if(lists[x].indexOf(currentValue) === -1) {
            existsInAll = false;
            break;
          }
        }
        if(existsInAll) {
          result.push(currentValue);
        }
      }
    }
  }
  return result;
}

Use it like this:

intersection(array1, array2, array3, array4); //["Lorem"]

Or like this:

intersection([array1, array2, array3, array4]); //["Lorem"]

Full code here

UPDATE 1

A slightly smaller implementation here using filter

Arg0n
  • 8,283
  • 2
  • 21
  • 38
  • Thanks for this. I can follow it and it works with dummy data buuuut... I don't have separate arrays stored in variables like array1, and array2, and array3, because I don't know how many divs are on the page in advance. At the moment I have to loop through them and push them into another container array. So I could only do intersection(arrayContainingArrays) which wouldn't work with your code. That said this is a good solution in vanilla JavaScript that someone else could use. –  May 19 '16 at 10:48
  • :O Ok! Give me 5 minutes ish I will try this out –  May 19 '16 at 10:54
  • This works perfectly for dummy arrays but not the specific arrays I want. Which means there's something else at my end I'm missing. But this answer is a good solution that completely answers my question and will be useful to others. So you win the internet today. –  May 19 '16 at 11:09
  • with the data set: `var array1 = [786, 796] var array2 = [100]; var array3 = [1]; var array4 = [2,3,4,1];` The output is an empty array but it should be common values. Please correct me on this. Thank you. – NN796 Apr 13 '21 at 20:06
  • @NN796 Should be empty. No value exists in all arrays. – Arg0n Apr 15 '21 at 05:25
  • I forgot to add in the comment but tried in the all arrays as well. but the resultant is empty. – NN796 Apr 15 '21 at 19:15
  • here is the fiddle link which is not working. https://jsfiddle.net/nabeelnazir796/oqu94ktd/1/ – NN796 Apr 15 '21 at 22:55
  • This is really reallly really slow and CPU intensive on large arrays. So much so that it took down our production Kubernetes pods. Opt for https://github.com/lovasoa/fast_array_intersect and call it a day. 200x faster/ – briangonzalez Aug 06 '21 at 19:32
7

This can be done pretty succinctly if you fancy employing some recursion and the new ES2015 syntax:

const array1 = ["Lorem", "ipsum", "dolor"];
const array2 = ["Lorem", "ipsum", "quick", "brown", "foo"];
const array3 = ["Jumps", "Over", "Lazy", "Lorem"];
const array4 = [1337, 420, 666, "Lorem"];

const arrayOfArrays = [[4234, 2323, 43], [1323, 43, 1313], [23, 34, 43]];

// Filter xs where, for a given x, there exists some y in ys where y === x.
const intersect2 = (xs,ys) => xs.filter(x => ys.some(y => y === x));

// When there is only one array left, return it (the termination condition
// of the recursion). Otherwise first find the intersection of the first
// two arrays (intersect2), then repeat the whole process for that result
// combined with the remaining arrays (intersect). Thus the number of arrays
// passed as arguments to intersect is reduced by one each time, until
// there is only one array remaining.
const intersect = (xs,ys,...rest) => ys === undefined ? xs : intersect(intersect2(xs,ys),...rest);

console.log(intersect(array1, array2, array3, array4));
console.log(intersect(...arrayOfArrays));

// Alternatively, in old money,

var intersect2ES5 = function (xs, ys) {
    return xs.filter(function (x) {
        return ys.some(function (y) {
            return y === x;
        });
    });
};
    
// Changed slightly from above, to take a single array of arrays,
// which matches the underscore.js approach in the Q., and is better anyhow.
var intersectES5 = function (zss) {
    var xs = zss[0];
    var ys = zss[1];
    var rest = zss.slice(2);
    if (ys === undefined) {
        return xs;
    }
    return intersectES5([intersect2ES5(xs, ys)].concat(rest));
};

console.log(intersectES5([array1, array2, array3, array4]));
console.log(intersectES5(arrayOfArrays));
1983
  • 5,882
  • 2
  • 27
  • 39
  • Hunh. Never come across const or rest before. I'd better read up on ECMAScript 6 before my employer replaces me with a robot :o –  May 19 '16 at 16:34
2

Using a combination of ideas from several contributors and the latest ES6 goodness, I arrived at

const array1 = ["Lorem", "ipsum", "dolor"];
const array2 = ["Lorem", "ipsum", "quick", "brown", "foo"];
const array3 = ["Jumps", "Over", "Lazy", "Lorem"];
const array4 = [1337, 420, 666, "Lorem"];

Array.prototype.intersect = function intersect(a, ...b) {
    const c = function (a, b) {
        b = new Set(b);
        return a.filter((a) => b.has(a));
    };
    return undefined === a ? this : intersect.call(c(this, a), ...b);
};

console.log(array1.intersect(array2, array3, array4));
// ["Lorem"]
minipc
  • 69
  • 5
  • 1
    Not as inefficient as not creating a `Set`, but still 1) extends the prototype, creating a weird asymmetry besides being bad practice 2) involves an inner function `c` for no reason 3) slows itself down with recursion – Ry- Nov 10 '21 at 05:57
1

For anyone confused by this in the future,

_.intersection.apply(_, arrayOfArrays)

Is in fact the most elegant way to do this. But:

var arrayOfArrays = [[43, 34343, 23232], [43, 314159, 343], [43, 243]];
arrayOfArrays = _.intersection.apply(_, arrayOfArrays);

Will not work! Must do

var differentVariableName = _.intersection.apply(_,arrayOfArrays);
1

The cleanest way I've found to do this wasn't actually listed on this page, so here you are:

arrays[0].filter(elem => arrays.every(array => array.includes(elem)))

Reads like nice, clear english: every array includes the element. It assumes that you have at least 1 element in arrays, though. If you can't make this assumption, you can use optional chaining:

arrays?[0].filter(elem => arrays.every(array => array.includes(elem))) ?? []
thedayturns
  • 9,723
  • 5
  • 33
  • 41
0

Your code with _lodash is working fine.

As you can say in this fiddle:

this code:

var arrayOfArrays = [[4234, 2323, 43], [1323, 43, 1313], [23, 34, 43]];
var a = _.intersection.apply(_, arrayOfArrays);
console.log(a);
console.log(a.length);

Will have output:

[42]
1

Maybe you see

equals: function

because you are using kind of debugger.

Try to just print the array with console.log, you will get only 42.

Ygalbel
  • 5,214
  • 1
  • 24
  • 32
  • When you say using some kind of debugger what do you mean? Chrome dev tools? The weird result I'm getting is when I print the array with console.log. It could possibly be something else at my end interfering that I just can't see yet. :s –  May 19 '16 at 10:44
  • Can you post console.log() of the result? – Ygalbel May 19 '16 at 10:46
  • I open the window, two divs on the page. It loops through them and combines the arrays into a single array. Console logs the result: [equals, function] "Array of combined arrays" > 0: Array[3] > 1: Array[11] There IS common values in both these arrays but using intersection method returns this: Array[0] length: 0 > _proto_: Array[0] Can't screenshot it as it's on an offline machine. –  May 19 '16 at 10:52
  • 1
    It seems you print all array properties and fields. It's the reason you see "equal" method. Check array length, you will get 1. – Ygalbel May 19 '16 at 10:54
0

Small recursive divide and conquer solution that does not rely on es6 or any library.

It accepts an array of arrays which makes the code shorter and allows you to pass arguments by using map.

function intersection(a) {
    if (a.length > 2)
        return intersection([intersection(a.slice(0, a.length / 2)), intersection(a.slice(a.length / 2))]);

    if (a.length == 1)
        return a[0];

    return a[0].filter(function(item) {
        return a[1].indexOf(item) !== -1;
    });
}

var list1 = [ 'a', 'b', 'c' ];
var list2 = [ 'd', 'b', 'e' ];
var list3 = [ 'f', 'b', 'e' ];
console.log(intersection([list1, list2, list3]));
Dovev Hefetz
  • 1,346
  • 14
  • 21
0

If you can use ES6 Maps and your arrays items are scalar values (easily usable as Map keys), then you can try this (works in my case) :

const intersect_lists = (lists) => {
    const results = []
    const lookup = new Map()

    lists.map((list, idx) => {
        list.map((element) => {
            const count = lookup.get(element) || 0

            if(count === idx) {
                lookup.set(element, 1 + count)
            } else {
                lookup.delete(element)
            }
        })
    })

    // only elements present in all lists will have 
    // their respective counter equllling the total number of lists
    Array.from(lookup.keys()).map((key) => {
        if(lookup.get(key) === lists.length) {
            results.push(key)
        }
    })

    return results
}

Optionally you can pre-sort "lists" (of lists) by creasing length to avoid lots of iterations of the outer map() call, especially if lists lengths are heterogenous :

lists.sort((l1, l2) => l1.length - l2.length).map((list, idx) => { ... })
0

Sol with Maps
// nums1 = [1,2,2,1], nums2 = [2,2]

// n  m
// O(nm) + space O(min(n, m))

// preprocess nums2 to a Map<number, count>
// O(n + m) + space(min(n, m))
// process the shorter one

let preprocessTarget = nums1
let loopTarget = nums2

if (nums1.length > nums2.length) {
  preprocessTarget = nums2
  loopTarget = nums1
}

// Map<element, number>
const countMap = new Map()
for (let num of preprocessTarget) {
  if (countMap.has(num)) {
    countMap.set(num, countMap.get(num) + 1)
  } else {
    countMap.set(num, 1)
  }
}


const result = []

for (let num of loopTarget) {
  if (countMap.has(num)) {
    result.push(num)
    
    const count = countMap.get(num)
    if (count === 1) {
      countMap.delete(num)
    } else {
      countMap.set(num, count - 1)
    }
  }
}

return result
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 29 '22 at 20:58
0
const data = [array1, array2, array3, array4].filter(arr => arr.length > 0);
const result = [...new Set(data)];
let final = Array.of<any>();
for (const key of result) {
    final = final.concat(key);
}
console.log(final);
Bigeyes
  • 1,508
  • 2
  • 23
  • 42
-1

Lodash pure:

_.keys(_.pickBy(_.groupBy(_.flatten(arrays)), function (e) {return e.length > 1}))

Lodash with plain js:

var elements = {}, duplicates = {};
 _.each(arrays, function (array) {
     _.each(array, function (element) {
         if (!elements[element]) {
             elements[element] = true;
         } else {
             duplicates[element] = true;
         }
     });
 });
_.keys(duplicates);
neemanjabu
  • 199
  • 1
  • 4
-1

I manage to accomplish this with a reduce call:

var intersected = intersect([[1, 2, 3], [2, 3, 4], [3, 4, 5]]);
console.log(intersected); // [3]

function intersect(arrays) {
    if (0 === arrays.length) {
        return [];
    }
    return arrays.reduce((intersection, array) => {
        return intersection.filter(intersectedItem => array.some(item => intersectedItem === item));
    }, arrays[0]);
}
Qutory
  • 89
  • 6
-1

Intersection of a variable number of arrays.

This is how I do it:

function getArraysIntersection(list1, list2, ...otherLists) {
  const result = [];

  for (let i = 0; i < list1.length; i++) {
      let item1 = list1[i];
      let found = false;
      for (var j = 0; j < list2.length && !found; j++) {
          found = item1 === list2[j];
      }
      if (found === true) {
          result.push(item1);
      }
  }
  if (otherLists.length) {
    return getArraysIntersection(result, otherLists.shift(), ...otherLists);
  }
  else {
    return result;
  }
}

SNIPPET

function getArraysIntersection(list1, list2, ...otherLists) {
  const result = [];

  for (let i = 0; i < list1.length; i++) {
      let item1 = list1[i];
      let found = false;
      for (var j = 0; j < list2.length && !found; j++) {
          found = item1 === list2[j];
      }
      if (found === true) {
          result.push(item1);
      }
  }
  if (otherLists.length) {
    return getArraysIntersection(result, otherLists.shift(), ...otherLists);
  }
  else {
    return result;
  }
}

const a = {label: "a", value: "value_A"};
const b = {label: "b", value: "value_B"};
const c = {label: "c", value: "value_C"};
const d = {label: "d", value: "value_D"};
const e = {label: "e", value: "value_E"};

const arr1 = [a,b,c];
const arr2 = [a,b,c];
const arr3 = [c];

const t0 = performance.now();
const intersection = getArraysIntersection(arr1,arr2,arr3);
const t1 = performance.now();

console.log('This took t1-t0: ' + (t1-t0).toFixed(2) + ' ms');
console.log(intersection);
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
-1
const intersect = (arrayA, arrayB) => {
    return arrayA.filter(elem => arrayB.includes(elem));
};
const intersectAll = (...arrays) => {
    if (!Array.isArray(arrays) || arrays.length === 0) return [];
    if (arrays.length === 1) return arrays[0];
    return intersectAll(intersect(arrays[0], arrays[1]), ...arrays.slice(2));
};
Taaha
  • 1
  • 1
-1

For anyone who might need, this implements the intersection inside an array of arrays:

intersection(array) {
  if (array.length === 1)
    return array[0];
  else {
    array[1] = array[0].filter(value => array[1].includes(value));
    array.shift();
    return intersection(array);
  }
}
Torugo
  • 1
  • 1
    This answer might need further refinement as the initial question asks ```given an arbitrary array of arrays, what is the intersection of all the arrays```. This assumes that only two arrays are compared. – Travis Sharp Jun 30 '21 at 19:34
-1
function getIntersection(ar1,ar2,...arrays){
    if(!ar2) return ar1

    let intersection = ar1.filter(value => ar2.includes(value));

    if(arrays.length ===0 ) return intersection

    return getIntersection(intersection,...arrays)
}

console.log(getIntersection([1,2,3], [3,4], [5,6,3]) // [3]

scr2em
  • 974
  • 8
  • 17
-1

Function to calculate intersection of multiple arrays in JavaScript Write a method that creates an array of unique values that are included in all given arrays. Expected Result: ([1, 2], [2, 3]) => [2]

    const arr1 = [1, 2, 1, 2, 1, 2];
    const arr2 = [2, 3];
    const arr3 = ["a", "b"];
    const arr4 = ["b", "c"];
    const arr5 = ["b", "e", "c"];
    const arr6 = ["b", "b", "e"];
    const arr7 = ["b", "c", "e"];
    const arr8 = ["b", "e", "c"];
    
    const intersection = (...arrays) => {
      (data = [...arrays]),
        (result = data.reduce((a, b) => a.filter((c) => b.includes(c))));
      return [...new Set(result)];
    };
    
    console.log(intersection(arr1, arr2)); // [2]
    console.log(intersection(arr3, arr4, arr5)); // ['b']
    console.log(intersection(arr5, arr6, arr7, arr8)); // ['b', 'e']