最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Comparing Multiple Arrays Using Reduce - Stack Overflow

programmeradmin5浏览0评论

pretty new to Javascript and I've tried this question about 4 times now in a span of about a month and I am still unable to solve it.

So here is the question: Construct a function intersection that compares input arrays and returns a new array with elements found in all of the inputs. BONUS: Use reduce!

The format is:

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

Test Case: Should log [15, 5]

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

My current solution: Works for the case of only have two items to compare, but not for the third one, I could make it so that I would loop through and compare the obtained values with the next array but I don't think I am on the right path... Also, I am not using reduce to implement it... And I am not sure if I am supposed to be using 'arguments.' Any help is appreciated! Thank you so much.

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

pretty new to Javascript and I've tried this question about 4 times now in a span of about a month and I am still unable to solve it.

So here is the question: Construct a function intersection that compares input arrays and returns a new array with elements found in all of the inputs. BONUS: Use reduce!

The format is:

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

Test Case: Should log [15, 5]

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

My current solution: Works for the case of only have two items to compare, but not for the third one, I could make it so that I would loop through and compare the obtained values with the next array but I don't think I am on the right path... Also, I am not using reduce to implement it... And I am not sure if I am supposed to be using 'arguments.' Any help is appreciated! Thank you so much.

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;
}
Share Improve this question asked Jan 28, 2017 at 22:35 Kevin QiuKevin Qiu 2311 gold badge4 silver badges9 bronze badges 7
  • Why don't you use lodash (or underscore)? Seems crazy to reinvent the wheel for such a well trodden path in JavaScript. – chriskelly Commented Jan 28, 2017 at 23:25
  • @chriskelly: OTOH, why drag one of them in for such a simple function? (And I say that as the author of a similar library myself.) – Scott Sauyet Commented Jan 29, 2017 at 0:51
  • Thanks for all the responses guys! I'll look through them :D – Kevin Qiu Commented Jan 29, 2017 at 2:12
  • @ScottSauyet: I have often written my own lodash-like functions (to eliminated a dependency) only to have people waste time checking it or trying to figure it out what it does and if it does it right, later. if you have _.difference(a, b) in your code it will be far more readable, won't be questioned, and therefore saves time in the long run. – chriskelly Commented Jan 29, 2017 at 15:12
  • @chriskelly: Ah, we have very different experiences. I was not happy with the design or the public API of Underscore and lodash, and ended up starting my own library. So I guess I'm much more willing to reinvent the wheel. "Look, mine's rounder!" :-) – Scott Sauyet Commented Jan 29, 2017 at 20:00
 |  Show 2 more comments

9 Answers 9

Reset to default 6

Although, as several suggestions said, you could use underscore, lodash, or my personal favorite, Ramda (disclaimer: I'm one of the authors), this function should be straightforward enough that you wouldn't even consider a library for it. Here's a simple version:

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]

I would think that this is all you need, at least so long as you're worried only about reference/primitive equality and don't need to consider cases where you want to know that {x: 1} and {x: 1} are the same, even though they aren't the same reference. If you do need that, you might look to Ramda's intersection function.

Note that if includes were better supported, I would recommend this version instead, as it reads better:

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

Also, if you have no need for the binary function, you can make just a variadic version of it by combining the two above:

const intersection = (...xss) => xss.reduce((xs, ys) => xs.filter(x => ys.indexOf(x) > -1));

Maybe someone will finds it useful.

As an argument to the function you can give any number of arrays of any length and the function is compact, I think ;)

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

And how it works:
Ok, first u take rest parameters(...arrays) as parameter of function, so u have
arrays = [ [5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20] ]
then in first iteration of reduce we have
includ = [5, 10, 15, 20] and current = [15, 88, 1, 5, 7]
on this two we use filter, what give us [5,15], i use Set to make shure there is no repetition and make array back (Array.from()), which is passed to the next iteration of reduce as "includ", at the next iteration we have
incude = [5,15] and current = [1, 10, 15, 5, 20] and so on ...

We can even use it like this

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

Although not solving your problem directly, you can do what you're trying to do using the opensource library underscore.js.

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

You may be able to derive inspiration from the way that's been implemented. The above is the function call to their own _.intersection function which is also dependent on other underscore.js functions as you see below:

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

Here is a solution using reduce, with the empty array passed in as intersection as the initial value.

Iterate the numbers and check if each one appears in one of the subarrays.

If it doesn't, set the Boolean isPresentInAll to false.

If it does appear in all three and it's not already present in the intersection array, then push to the intersection array.

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

I think i got the right function for you. (Note: results are not sorted!)

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

Here's a version that uses 2 reduces.

The first iterates the arrays only once to create a hashmap object to track instance counts, the second to return values where counts match number of arguments

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

Could use some fine tuning to make sure there are enough arguments and that they are all arrays

Here's what I came up with using vanilla javascript and one call to 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]));
function intersection(arrays) {
  let common = arrays.reduce(function(accumulator, currentValue) {
    return accumulator.filter(function(x){
      return currentValue.indexOf(x) > -1;
    })
  })
  return common;
}

To optimize your answer that couldn't work on more than 2 subarrays and didn't use reduce, here's the code that works for however many subarrays you pass in.

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]
发布评论

评论列表(0)

  1. 暂无评论