Reputation: 692
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
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
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
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:
isCollision
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