Как рассчитать пересечение нескольких массивов в JavaScript? А что означает [равно: функция]?

Я знаю этот вопрос, простейший код для пересечения массива, но все решения предполагают, что количество массивов равно двум, который не может быть уверен в моем случае.

У меня есть divs на странице с данными, которые содержат массивы. Я хочу найти значения, общие для всех массивов. Я не знаю, сколько div/массивов у меня будет заблаговременно. Каков наилучший способ вычисления значений, общих для всех массивов?

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"];

Я нашел другое решение в другом месте, используя Underscore.js.

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

Я тестировал это с помощью простых фиктивных данных в конце и, похоже, работал. Но по какой-то причине некоторые из массивов, которые я создаю, которые содержат простые строки, также автоматически включают добавленное значение, "equals: function":

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

И всякий раз, когда я использую метод пересечения Underscore.js, в массиве массивов я всегда получаю [equals: function] в инструментах dev, а не - если "Dummy3" является общим для всех массивов - [ "Dummy3" ].

Итак, TL; DR есть другое решение для пересечения массива, которое подойдет моему делу? И может ли кто-нибудь объяснить, что означает [equals: function] здесь? Когда я разворачиваю элемент в инструментах dev, он создает пустой массив и список доступных методов на массивах (поп, push, shift и т.д.), Но все эти методы исчезают, а функция equals: выделена.

Ответ 1

Я написал вспомогательную функцию для этого:

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;
}

Используйте это так:

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

Или вот так:

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

Полный код здесь

ОБНОВЛЕНИЕ 1

Немного меньшая реализация здесь с использованием filter

Ответ 2

Вы можете просто использовать Array#reduce с Array#filter Array#includes и 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);

Ответ 3

Это можно сделать довольно лаконично, если вы хотите использовать некоторую рекурсию и новый синтаксис ES2015:

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));

Ответ 4

Используя комбинацию идей от нескольких участников и новейшую доброту ES6, я пришел к

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"]

Ответ 5

Ваш код с _lodash работает нормально.

Как вы можете сказать в this скрипт:

этот код:

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

Будет выводиться:

[42]
1

Возможно, вы видите

равно: функция

потому что вы используете отладчик.

Попробуйте просто напечатать массив с помощью console.log, вы получите только 42.

Ответ 6

Для всех, кого это смущает в будущем,

_.intersection.apply(_, arrayOfArrays)

На самом деле это самый элегантный способ сделать это. Но:

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

Не получится! Необходимо сделать

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

Ответ 7

Lodash pure:

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

Лодаш с простым js:

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

Ответ 8

Небольшое рекурсивное решение "разделяй и властвуй", которое не зависит от es6 или какой-либо библиотеки.

Он принимает массив массивов, что делает код короче и позволяет передавать аргументы с помощью 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]));

Ответ 9

Ответ Arg0n великолепен, но на всякий случай, если кто-то ищет пересечение массивов объектов, я немного изменил ответ Arg0n, чтобы разобраться с ним.

function getIntersectedData() {
    var lists = [[{a:1,b:2},{a:2,b:2}],[{a:1,b:2},{a:3,b:3}],[{a:1,b:2},{a:4,b:4}]];
    var result = [];
    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 (customIndexOf(result,currentValue)) {
                var existsInAll = true;
                for (var x = 0; x < lists.length; x++) {
                    if(customIndexOf(lists[x],currentValue)){
                        existsInAll = false;
                        break;
                    }
                }
                if (existsInAll) {
                    result.push(currentValue);
                }
            }
        }
        return result;
    }
}

function customIndexOf(array,value){
    var notFind = true;
    array.forEach(function(element){
        if(element.a === value.a){
            notFind = false;
        }
    });
    return notFind;
}

Ответ 10

Мне удается сделать это с помощью reduce вызова:

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]);
}