如何在JavaScript函数式编程中使用reduce函数来查找数组集合的交集/并集?

4

我已经在JS中重新创建了foreach + map + reduce函数:

function forEach(array, callback) {
 for (var i=0;i<array.length;i++) {
   callback(array[i])
  }

}
function mapWith(array, callback) {
  var output= [];
    forEach(array , function(el){
    return output.push(callback(el))
  });
  return output;

}
function reduce(array, callback, initialValue) {
  mapWith(array, function(el){
    return initialValue = callback(initialValue, el);
  })
  return initialValue;

}

现在我该如何使用reduce方法来找到一组数组的交集呢?
function intersection(arrays) {

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

另外,我该如何比较输入数组并返回包含所有元素的新数组。如果有重复元素,则仅在新数组中添加一次。元素顺序从第一个输入数组的第一个元素开始保留。
function union() {
}

// console.log(union([5, 10, 15], [15, 88, 1, 5, 7], [100, 15, 10, 1, 5]));
// should log: [5, 10, 15, 88, 1, 7, 100]

顺便提一下,以上内容来自:http://csbin.io/callbacks - Sahil Tandon
7个回答

4

我认为威尔·桑坦斯也会同意这个观点 :)

const union = (arrays) => { return arrays.reduce((a, b) => Array.from(new Set(a.concat(b)))) };

const intersection = (arrays) => { return arrays.reduce((a, b) => a.filter(ele => b.includes(ele))) };

Well Sentance 是一个很棒的 gem。在 Frontend Masters 上学到了很多东西。感谢这个解决方案。 - Aakash Thakur

3
现在我该如何使用reduce来找到一组数组之间的交集?
使用reduce的实现方式是依次取出每个数组,如果元素不在该数组中,则从结果中消除(过滤掉)。
function intersection(arrays) {
  return reduce(arrays, (result, array) => 
    filter(result, e => array.includes(e)));
};

假设您已经编写了自己的过滤器:
function filter(array, callback) {
  var output= [];
  forEach(array , function(el) {
    if (callback(el)) output.push(el);
  });
  return output;

}

另一个想法是先将所有数组连接起来:
function concat(arrays) { return [].concat(...arrays); }

然后筛选只包含在所有数组中出现的元素:

function intersection(arrays) {
  return concat(arrays).filter(e => arrays.every(a => a.includes(e));
}

如果您不想使用内置的Array#every,并且继续编写自己的代码,可以参考以下内容:
function every(array, callback) {
 for (var i = 0; i < array.length; i++) 
   if (!callback(array[i])) return false;
 return true;
}

使用该方法和你自己的过滤器intersect函数就变成了:

function intersection(arrays) {
  return filter(concat(arrays), e => every(arrays, a => a.includes(e)));
}

Array#includes 是 ES7 标准,可能不被你常用的浏览器支持。在这种情况下,可以使用 a.indexOf(e) !== -1 来替代,或者自己编写代码。

有些人可能更倾向于以更加语义化的方式书写:

function intersection(arrays) {
  const inAll = e => every(arrays, a => a.includes(e));

  return filter(concat(arrays), inAll);
}

另外,如何比较输入数组并返回一个包含所有元素的新数组。如果有重复元素,则只添加一次到新数组中。从第一个输入数组的第一个元素开始保留元素的顺序。
我不知道你所说的“比较”是什么意思。无论如何,要做你想要的事情,请将它们连接起来并应用类似于uniq的实用程序:
function union(arrays) {
  return uniq(concat(arrays));
}

有很多uniq的实现方法。这里是一个非常简单的实现:

function uniq(arr) {
  return arr.filter((elt, i) => arr.indexOf(elt) === i);
}

3
使用自定义的reduce和foreach
function forEach(array, callback) {
    for(i = 0; i < array.length; i++){
        callback(array[i])
    }
}

function reduce(array, callback, initialValue) {
    for(let i of array){
        initialValue = callback(initialValue, i)
    }
    return initialValue
}

function intersection(init, ...arrays) {
    return reduce(arrays, (current, next) => {
        const filtered = []
        forEach(next, (el) => {
            if(current.includes(el)) filtered.push(el)
        })
        return filtered
    }, init)

}

使用内置的reduce和forEach替代

function intersection(...arrays) {

    return arrays.reduce((current, next) => {
        const filtered = []
        next.forEach((el) => {
            if(current.includes(el)) filtered.push(el)
        })
        return filtered
    }) 
}

参考规范以了解未指定initialValue的reduce函数定义方式。

https://tc39.es/ecma262/#sec-array.prototype.reduce

在没有定义初始数组的情况下,我并不希望它能够工作。

确保使用多个测试用例来测试您的实现。我的最初解决方案可以生成正确的答案,但甚至没有查看最后一个数组。我的reduce中有一个错误。


2
  1. 比较第一个数组和第二个数组,返回一个新数组,其中包含公共元素,继续将返回的数组与其余数组逐个比较,并每次返回一个包含公共元素的新数组。

  2. 如何比较两个数组的公共元素?使用过滤器方法。

    function intersection(...arrays) {
      return arrays.reduce((resultArray, currentArray) => {
        return resultArray.filter(el => currentArray.includes(el))
      }) 
    }

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


1
尝试这个。
const a = [1,2,3,4,5,6];
const b = [1,2,3,9,45,15];
const c = [13,2,5,3,10];

function intersection (...lists){
    const all = [...new Set([].concat(...lists))]; //to remove duplicates
    return all.reduce( ( accumulator, currentValue ) => {
        if(lists.every( list => list.includes(currentValue) )){
            return [...accumulator, currentValue];
        }else{
            return [...accumulator]
        }
    }, [])
}
console.log( 'intersection', intersection(a,b,c) ); //returns  [2,3]

0

你可以尝试这个

 // spread operator returns an array of arrays 
function intersection(...arrays) {
 // setting output to array
  const output = [] 
 // initial value is of accumulator is an object 
  reduce(arrays,function(acc,currentArray){ 
 // for each item check if item exist in accumulator(object)
        forEach(currentArray,function(item){
 // if it does 
          if(item in acc) {
  // increment its value 
                    acc[item]++
          }else {
   // else  make it a property of accumulator(object) and set value to 1  
            acc[item] = 1;
          }
   // if accumulator property of current item is === length of arrays i.e appears the same amount of times as total amount of arrays in arrays 
          if(acc[item] === arrays.length){
   //  push item onto output 
            output.push(item)
    }
        })
    // returns accumulator into reduce function
    return acc
    },{})
 //returns output as the intersection
 return output
}

交集和并集的区别在于最后一个if语句,我们不再检查数组中数组的总数,而是检查其值是否等于1(===1),现在看代码:
  // spread operator returns an array of arrays 
    function union(...arrays) {
     // setting output to array
      const output = [] 
     // initial value is of accumulator is an object 
      reduce(arrays,function(acc,currentArray){ 
     // for each item check if item exist in accumulator(object)
            forEach(currentArray,function(item){
     // if it does 
              if(item in acc) {
      // increment its value 
                        acc[item]++
              }else {
       // else  make it a property of accumulator(object) and set value to 1  
                acc[item] = 1;
              }
       // if accumulator property of current item is === length of arrays i.e appears once  
              if(acc[item] === 1){
       //  push item onto output 
                output.push(item)
        }
            })
        // returns accumulator into reduce function
        return acc
        },{})
     //returns output as the union
     return output
    }

希望有人会发现这个有用。

0
如果您想在不使用filter等内置函数的情况下解决此问题。
function intersection(arrays) {
  return reduce(arrays, function(arrA, arrB) {
    forEach(arrA, function(item) {
      if (arrB.indexOf(item) == -1) {
        arrA.splice(arrA.indexOf(item), 1);
      }
    });
    return arrA;
  }, arrays[0]);
}

function union(arrays) {
  return reduce(arrays, function(arrA, arrB) {
    forEach(arrB, function(item) {
      if (arrA.indexOf(item) == -1) {
        arrA.push(item);
      }
    });
    return arrA;
  }, arrays[0]);
}

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