使用Reduce进行多个数组的比较

5

我对JavaScript还不是很熟悉,我已经在一个月内尝试了4次这个问题,但仍然无法解决。

所以问题是:

构建一个名为 intersection 的函数来比较输入的数组,并返回一个新数组,其中包含在所有输入中都找到的元素。奖励:使用 reduce!

格式如下:

function intersection(arrays) {
  // Your Code Goes Here
}

测试用例:应记录[15, 5]

console.log('Extensions 3 Test: ' + intersection([5, 10, 15, 20], [15, 88, 1, 5, 7]/*, [1, 10, 15, 5, 20]*/));

我的现有解决方案:只适用于比较两个项目的情况,但不适用于第三个项目,我可以循环遍历并将获取到的值与下一个数组进行比较,但我认为我没有走在正确的道路上……同时,我没有使用reduce来实现它……而且我不确定是否应该使用“arguments”。任何帮助都将不胜感激!非常感谢。

function intersection(arrays) {
  array = [];
  for (var i = 0; i < arguments.length; i++)
    array.push(arguments[i]);

  var result = [];

  for(var i = 0; i < array.length - 1; i++) {
    for(var j = 0; j < array[i].length; j++) {
      if (array[i+1].includes(array[i][j]))
        result.push(array[i][j]);
    }
  }

  return result;
}

为什么不使用lodash(或underscore)呢?在JavaScript中,为这样一个经过深入探索的领域重新发明轮子似乎有点疯狂。 - chriskelly
@chriskelly:另一方面,为什么要为这样一个简单的函数拖入其中之一?(我自己是类似库的作者,也是这么说的。) - Scott Sauyet
谢谢大家的回复!我会仔细查看它们 :D - Kevin Qiu
@chriskelly:啊,我们的经历差别很大。我对Underscore和lodash的设计和公共API都不满意,结果开始了我的自己的库。所以我猜我更愿意重新发明轮子。"看,我的更圆!" :-) - Scott Sauyet
@chriskelly:很高兴你喜欢它。Gitter上经常有活跃的讨论,问题通常会很快得到回复...尽管不一定会很快关闭! :-( - Scott Sauyet
显示剩余2条评论
9个回答

6
尽管有几个建议说可以使用underscorelodash或者我个人最喜欢的Ramda(免责声明:我是作者之一),但这个函数应该足够简单,以至于您甚至不会考虑使用库。以下是一个简单的版本:
const intersection = (xs, ys) => xs.filter(x => ys.indexOf(x) > -1);
intersection([5, 10, 15, 20, 3], [15, 88, 3, 1, 5, 7]); //=> [5, 15, 3]

const intersectAll = (...xss) => xss.reduce(intersection);
intersectAll([5, 10, 15, 20, 3], [15, 88, 3, 1, 5, 7],  [1, 10, 15, 5, 20]); //=> [5, 15]

我认为这就是你所需要的,至少只要你只关心引用/基本类型的相等性,并且不需要考虑想知道{x:1}{x:1}是相同的情况,尽管它们不是同一个引用。如果确实需要这样,你可以查看Ramda的intersection函数。

请注意,如果includes得到了更好的支持,我将建议使用此版本,因为它更易读:

const intersection = (xs, ys) => xs.filter(x => ys.includes(x));

此外,如果您不需要二进制函数,可以通过组合上述两个函数来创建一个可变参数版本:
const intersection = (...xss) => xss.reduce((xs, ys) => xs.filter(x => ys.indexOf(x) > -1));

3
也许有人会觉得这很有用。

作为函数的参数,您可以提供任意数量的任意长度的数组,我认为该函数很简洁;)

const findSimilar = (...arrays) => {   
  return arrays.reduce((includ, current) =>
    Array.from(new Set(includ.filter((a) => current.includes(a))))
  );
};

console.log(
  findSimilar([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20])
);


它的工作原理如下:
首先,您需要将剩余参数(...arrays)作为函数的参数,这样您就会有
arrays = [[5, 10, 15, 20],[15, 88, 1, 5, 7],[1, 10, 15, 5, 20]]
然后在reduce的第一次迭代中,我们有
includ = [5, 10, 15, 20]和current = [15, 88, 1, 5, 7]
我们使用filter对这两个进行操作,得到[5,15],我使用Set确保没有重复项并将其转换回数组(Array.from()),然后将其作为“includ”传递给reduce的下一次迭代,在下一次迭代中,我们有
incude = [5,15]和current = [1, 10, 15, 5, 20]等等...

我们甚至可以这样使用它

let result = [
  [5, 10, 15, 20],
  [15, 88, 1, 5, 7],
  [1, 10, 15, 5, 20]
].reduce((includ, current) =>
  Array.from(new Set(includ.filter((a) => current.includes(a))))
);

console.log(result);


1
请在回答中提供一些背景和解释,使其更有价值。此外,在回答一个已经有8个答案的3年前的问题时,您可能需要提供一些回答的理由。这个回答做了什么其他8个答案没有做到的呢? - miken32
@miken32:根据您的提醒,我添加了一个小的解释。 - Draco

1
这里有一个使用reduce的解决方案,将空数组作为初始值传入交集。
遍历数字并检查每个数字是否出现在子数组中。
如果没有出现,则将布尔值isPresentInAll设置为false。
如果它确实出现在所有三个子数组中,并且尚未出现在交集数组中,则将其推入交集数组。
function intersection(arrayOfArrays) {
  return arrayOfArrays.reduce(function(intersection, subArray) {
    subArray.forEach(function(number) {
      var isPresentInAll = true;
      for (var i = 0; i < arrayOfArrays.length; i++) {
        if (arrayOfArrays[i].indexOf(number) === -1) {
          isPresentInAll = false;
        }
      }
      if (isPresentInAll === true && intersection.indexOf(number) === -1) {
        intersection.push(number);
      }
    });
    return intersection;
  }, []);
}

1

虽然不能直接解决你的问题,但你可以使用开源库underscore.js来完成你想做的事情。

_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2]

你可能会从它的实现方式中获得灵感。上述是调用他们自己的_.intersection函数,该函数还依赖于其他underscore.js函数,如下所示:
  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    if (array == null) return [];
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = array.length; i < length; i++) {
      var item = array[i];
      if (_.contains(result, item)) continue;
      for (var j = 1; j < argsLength; j++) {
        if (!_.contains(arguments[j], item)) break;
      }
      if (j === argsLength) result.push(item);
    }
    return result;
  };

0

我觉得我为你找到了合适的函数。 (注意:结果未排序!)

var intersection = function() {
    // merge deduped arrays from arguments
    var arrays = Array.prototype.reduce.call(arguments, function(carry, array) {
        return [].concat(carry, array.filter(function(item, index, origin) {
            return origin.indexOf(item) === index;
        }));
    }, []);

    var results = arrays.reduce(function(carry, item, index, arr) {
        if(
            // just select items, which have more then 1 occurance
            arr.filter(function(fItem) {
                return fItem === item;
            }).length > 1 &&
            // ... and which are not already in results
            !~carry.indexOf(item)
        ) {
            carry = [].concat(carry,item);
        }
        return carry;
    }, []);

    return results;
};

0
这里有一个使用两个reduce的版本。
第一个迭代数组仅一次以创建哈希映射对象来跟踪实例计数,第二个则返回计数与参数数量匹配的值。

function intersection(){
  // convert arguments to array of arrays
  var arrays = [].slice.call(arguments);
  // create an object that tracks counts of instances and is type specific
  // so numbers and strings would not be counted as same
  var counts= arrays.reduce(function(a,c){
     // iterate sub array and count element instances
     c.forEach(function(val){
        var propName =  typeof val + '|' + val;
        // if array value not previously encountered add a new property        
        a[propName] = a[propName] || {count:0, value: val};       
        // increment count for that property
        a[propName].count++;
     });
     return a;
  },{});

  // iterate above object to return array of values where count matches total arrays length
  return Object.keys(counts).reduce(function(resArr, propName){
    if(counts[propName].count === arrays.length){
      resArr.push(counts[propName].value);
    }
    return resArr;
  },[]);
  
}



console.log(intersection([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20]))

可能需要进行微调,以确保有足够的参数,并且它们都是数组


谢谢!我想这就是我要找的!我需要一些时间来消化它,但感谢您使用我知道的词汇解决答案:D! - Kevin Qiu
建议在循环内添加一些控制台日志语句,以更好地了解每个变量的值。并检查 console.log(counts) - charlietfl

0
function intersection(arrays) {
  let common = arrays.reduce(function(accumulator, currentValue) {
    return accumulator.filter(function(x){
      return currentValue.indexOf(x) > -1;
    })
  })
  return common;
}

0
这是我用原生JavaScript编写的代码,通过一次reduce调用实现的。
function intersection(){
 var arrays = [].slice.call(arguments);
 var first = arrays[0];
 var rest = arrays.slice(1);

 return first.reduce(function(all, item, index){  
  var push = rest.every(function(subArray){
      return subArray.indexOf(item) > -1; 
    });
  if(push){
    all.push(item);
   }
  return all; 
 },[])

}

console.log(intersection([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20]));

0
为了优化您的答案,使其适用于超过2个子数组并且不使用reduce的情况,这里提供一段代码,可以适用于您传入的任意数量的子数组。
function intersection(arr1, arr2, arr3){
  let ans = arr1[0]; // ans = [5,10,15,20]
  for(let i = 0; i < ans.length; i++){ // i = 0...3
    for(let j = 1; j < arr1.length; j++){ // j = 1...2
      if(!(arr1[j].includes(ans[i]))){ // if the new subarray doesn't include an element in the ans
        ans.splice(i, 1); // delete the element from ans
        }
    }
  }
  return ans;
}

const arr1 = [5, 10, 15, 20];
const arr2 = [15, 88, 1, 5, 7];
const arr3 = [1, 10, 15, 5, 20];
console.log(intersection([arr1, arr2, arr3])); // should log: [5, 15]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接