Reputation: 131
I need to call function with all avaliable pairs of array elements. Like this:
[1, 2, 3].pairs(function (pair) {
console.log(pair); //[1,2], [1,3], [2,3]
});
Upvotes: 10
Views: 24928
Reputation: 8771
Interesting problem, here is one way to do it:
Array.prototype.pairs = function (func) {
for (var i = 0; i < this.length - 1; i++) {
for (var j = i; j < this.length - 1; j++) {
func([this[i], this[j+1]]);
}
}
}
var list = [1, 2, 3];
list.pairs(function(pair){
console.log(pair); // [1,2], [1,3], [2,3]
});
Upvotes: 14
Reputation: 647
The top answer is great, but I had to modify it to be more inline with .map:
Array.prototype.pairs = function (callback) {
if (this.length < 2) {
return [];
}
const arrSize = this.length * (this.length - 1) / 2
const newArray = new Array(arrSize);
let index = 0;
for (var i = 0; i < this.length - 1; i++) {
for (var j = i; j < this.length - 1; j++) {
newArray[index] = callback([this[i], this[j+1]], index, newArray);
index++;
}
}
return newArray;
}
Calling it:
var list = [1, 2, 3];
const pairs = list.pairs(function(pair, index, array){
console.log(`pair: ${pair} index: ${index} array: ${array}`);
return pair;
});
console.log(pairs)
Result:
pair: 1,2 index: 0 array: ,,
pair: 1,3 index: 1 array: 1,2,,
pair: 2,3 index: 2 array: 1,2,1,3,
0: (2) [1, 2]
1: (2) [1, 3]
2: (2) [2, 3]
And for typescript you can silence the interpreter by defining the type:
declare global {
interface Array<T> {
pairs<U>(callbackfn: (value: T[], index: number, array: T[][]) => U): U[];
}
}
Upvotes: 0
Reputation: 151
Here is the cleanest solution that I could find using ES6. It generates unique pairs based on the index and using the flatMap function.
const uniquePairs = (arr) =>
arr.flatMap((item1, index1) =>
arr.flatMap((item2, index2) =>
(index1 > index2) ? [[item1,item2]] : []
)
)
uniquePairs([1,2,3])
// [[2, 1], [3, 1], [3, 2]]
flatMap
- returns elements into one array, and has the benefit of being able to remove empty arrays entirely. This is what allows our ternary with a value of []
to just disappear!
Like map
, it can return the index, and by comparing the index of both items, we can avoid duplicates (the 1st index must be greater than the 2nd for us to add an element).
The ternary lets us do a comparison and returns an empty array if false (which get's completely ignored by the 2nd flatMap).
By changing each flatMap
into a map
you can have a better idea of what each flatMap
is accomplishing- so I encourage you to try altering it!
Upvotes: 2
Reputation: 4835
With flatMap
now available in ECMAScript, there's a really simple and readable option to find pairs of items in a list in Javascript with no loops:
const pairs = (a) => {
return a.flatMap( (x) => {
return a.flatMap( (y) => {
return (x != y) ? [[x,y]] : []
});
});
}
calling pairs([1,2,3])
will output:
[ [ 1, 2 ], [ 1, 3 ], [ 2, 1 ], [ 2, 3 ], [ 3, 1 ], [ 3, 2 ] ]
Simple, readable and functional.
EDIT: I originally read this question as "how to get all pairs" which I assumed included reversed pairs, which the above example does. To return the list without the reversed order pairs, we can take these out with reduce()
:
const isInArray = (a, value) => {
if (
a.map((x) => {
if (x.toString() == value.toString()) return true;
}).includes(true)
){
return true;
}
};
const reducedPairs = (a) => {
return a.flatMap( (x) => {
return a.flatMap( (y) => {
return (x != y) ? [[x,y]] : []
});
}).reduce( (unique, current) => {
if (!isInArray(unique, current.slice().reverse())) unique.push(current);
return unique;
}, []);
}
Note that this uses string comparisons to check for array equality since [1,2] == [1,2]
is false
. This works for the use case in the original question, but for a more complex example an alternative method of checking for duplicates may be required.
Upvotes: 3
Reputation: 14318
Thanks to the continuous evolvement of the ECMAScript standard ..
let pairs = (arr) => arr.map( (v, i) => arr.slice(i + 1).map(w => [v, w]) ).flat();
pairs([1, 2, 3, 4, 5]);
Upvotes: 14
Reputation: 4101
In case you're looking for reversed pairs as well - going off of @Oriol's answer above - here's a version that includes all original pairs + all original pairs reversed:
function allPairs (arr) {
let pairs = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
pairs.push([arr[i], arr[j]]);
}
}
let copy = pairs;
pairs = JSON.stringify(pairs);
let reversed = [];
copy.forEach((value) => {
reversed.push(value.reverse());
})
let final = [];
final.push(JSON.parse(pairs).concat(reversed));
return final[0];
}
console.log(allPairs([1, 2, 3, 4, 5]));
Upvotes: 0
Reputation: 1055
Here is a variant in ES6 style without mutations:
const pairsOfArray = array => (
array.reduce((acc, val, i1) => [
...acc,
...new Array(array.length - 1 - i1).fill(0)
.map((v, i2) => ([array[i1], array[i1 + 1 + i2]]))
], [])
)
const pairs = pairsOfArray(['a', 'b', 'c', 'd', 'e'])
console.log(pairs)
// => [['a','b'], ['a','c'], ['a','d'],['a','e'],['b','c'],['b','d'],['b','e'],['c','d'],['c','e'],['d','e']]
Upvotes: 3
Reputation: 288470
function pairs(arr) {
var res = [],
l = arr.length;
for(var i=0; i<l; ++i)
for(var j=i+1; j<l; ++j)
res.push([arr[i], arr[j]]);
return res;
}
pairs([1, 2, 3]).forEach(function(pair){
console.log(pair);
});
Upvotes: 5