llamaro25
llamaro25

Reputation: 692

How do I return 2 values from two arrays when using filter

I have a Javascript code that uses forEach to loop through each array to check if it fulfils a condition. Then, it calls for another function. Note: by looping through every element in arr1 and arr2, I'm checking if any elements in arr1 are colliding with any elements in arr2. isCollision is a function which calculates their distance if they intersect

arr1.forEach(x => arr2.forEach(y => isCollision(x,y)? explode(x,y): undefined));

Right now, I do not want it to return undefined. So I was wondering is there a way to get the x & y values so that I can map it to another function. I tried this

arr1.filter(x => arr2.filter(y => isCollision(x,y)).map(x,y => //do something)

But right now it's only returning the value of y. Is there a way for it to return both x and y?

Upvotes: 1

Views: 90

Answers (3)

Exodus 4D
Exodus 4D

Reputation: 2822

You could use reduce() instead of: filter() + map().

So you don´t have the 2nd loop of chaining methods:

let a1 = [
  { x: 1, y: "y1" },
  { x: 2, y: "y2" },
  { x: 3, y: "y3" },
  { x: 4, y: "y4" },
  { x: 5, y: "y5" }
];

let a2 = [
  { x: 2, y: "y2" },
  { x: 4, y: "y4" },
  { x: 7, y: "y7" }
];

let isCollision = (
  {x: x1, y: y1}, 
  {x: x2, y: y2}
) => x1 === x2 && y1 === y2;

// Custom logic:
let explode = ({x, y}) => console.log(`Explode {x: %d, y: %o}`, x, y) || {x,y};

// You could use .reduce() instead of .filter().map() combo:
let collisions = a1.reduce((acc, p1) => a2.some(p2 => isCollision(p1, p2)) ? [...acc, explode(p1)] : acc, []);

// Explode {x: 2, y: "y2"}
// Explode {x: 4, y: "y4"}

Upvotes: 0

Steven Spungin
Steven Spungin

Reputation: 29159

Try this:

arr1
 .map(x => ({x, y:arr2.find(y => isCollision(x,y)}))
 .filter(pair => pair.y !== undefined)

First map to {x:y} and then remove not found items.

To cast to a non-undefined type, use a another map.

arr1
 .map(x => ({x, y:arr2.find(y => isCollision(x,y)}))
 .filter(pair => pair.y !== undefined)
 .map(pair => pair as {x:Elem, y:Elem})

Upvotes: 2

VLAZ
VLAZ

Reputation: 29088

If you want to find all pairs of objects from arr1 and arr2 that match some requirement (isCollision) using only array methods, that's probably not going to be as pretty as doing a pair of loops. But still, it's doable - the basic idea is that you want to check each pair of objects from arr1 and arr2 against your isCollision. And to do that you need to go over all possible combinations between the two arrays. So, the basic steps are:

  1. Generate all combinations of pairs from the two arrays
  2. Filter the pairs by using isCollision
  3. Anything you get will be colliding, so you can run whatever you want with those.

So, here is a simplified representation of the objects - they only have a coord property which shows their position (one dimensional for simplicity) and a value property. isCollision checks if any two objects take the exact same space. Your logic and objects would be different but that should't matter for the execution steps you need to take

const arr1 = [
  { coord: 1, value: "x1" },
  { coord: 2, value: "x2" },
  { coord: 3, value: "x3" },
  { coord: 4, value: "x4" },
  { coord: 5, value: "x5" }
];

const arr2 = [
  { coord: 2, value: "y2" },
  { coord: 4, value: "y4" }
];

function isCollision(x, y) {
  return x.coord === y.coord;
}

function explode(x, y) {
  console.log(`${x.value} explodes, as does ${y.value}`);
}

arr1
  .flatMap(x => arr2.map(y => [x, y]))  //1. generate all combinations of pairs
  .filter(pair => isCollision(...pair)) //2. get only the colliding objects
  .forEach(pair => explode(...pair));   //3. run them  through the explode() function

Here, I use Array#flatMap to generate pairs - the function given to flatMap will generate an array of pairs itself, so if we just had .map, that will return

[
  [[x1, y2], [x1, y4]],
  [[x2, y2], [x2, y4]],
  [[x3, y2], [x3, y4]],
  [[x4, y2], [x4, y4]],
  [[x5, y2], [x5, y4]]
]

instead of

[
  [x1, y2],
  [x1, y4],
  [x2, y2],
  [x2, y4],
  [x3, y2],
  [x3, y4],
  [x4, y2],
  [x4, y4],
  [x5, y2],
  [x5, y4]
]

Inside Array#filter and Array#forEach the spread syntax is used to turn the pair of objects into arguments to the functions.

This can be be slightly more elegant by using array destructuring for the parameters of the functions:

const arr1 = [
  { coord: 1, value: "x1" },
  { coord: 2, value: "x2" },
  { coord: 3, value: "x3" },
  { coord: 4, value: "x4" },
  { coord: 5, value: "x5" }
];

const arr2 = [
  { coord: 2, value: "y2" },
  { coord: 4, value: "y4" }
];

function isCollision([x, y]) { //<-- destructuring
  return x.coord === y.coord;
}

function explode([x, y]) { //<-- destructuring
  console.log(`${x.value} explodes, as does ${y.value}`);
}

arr1
  .flatMap(x => arr2.map(y => [x, y]))
  .filter(isCollision) // <--------------------------------------|
  .forEach(explode);// <-- just passing the function reference --|

Anything you do will have the same complexity, since you still need to iterate through each of arr1 and arr2 anyway - you could only .flatMap directly only collisions, skipping the .filter step...but that just means that you need to do the .filter as part of the flatmap. So, this shows off all operations you'd need to do.

In this case, it might be easier to just keep your original code. It might look slightly less elegant in some ways but it works and it still does the same steps - goes through each pair of x, y, checks if the pair collides and it executes a function if it does.

const arr1 = [
  { coord: 1, value: "x1" },
  { coord: 2, value: "x2" },
  { coord: 3, value: "x3" },
  { coord: 4, value: "x4" },
  { coord: 5, value: "x5" }
];

const arr2 = [
  { coord: 2, value: "y2" },
  { coord: 4, value: "y4" }
];

function isCollision(x, y) {
  return x.coord === y.coord;
}

function explode(x, y) {
  console.log(`${x.value} explodes, as does ${y.value}`);
}

arr1
  .forEach(x => arr2.forEach(y => {
    if (isCollision(x,y)) {
      explode(x,y)
    }
  }));

Upvotes: 0

Related Questions