Is there an elegant, functional way to turn this array:
[ 1, 5, 9, 21 ]
into this
[ [1, 5], [5, 9], [9, 21] ]
I kn
I noticed that the current solutions, in a way, all look ahead or behind (arr[i + 1]
or arr[i - 1]
).
It might be useful to also explore an approach that uses reduce
and an additional array, defined within a function's closure, to store a to-be-completed partition.
Notes:
part
doesn't have to be an array when working with only 2 items, but by using an array, we extend the method to work for n
-sized sets of itemsshift
, you can use a combination of slice
and redefine part
, but I think it's safe here.length
less than the required number of elements are not returnedconst partition = partitionSize => arr => {
const part = [];
return arr.reduce((parts, x) => {
part.push(x);
if (part.length === partitionSize) {
parts.push(part.slice());
part.shift();
}
return parts;
}, []);
};
const makePairs = partition(2);
const makeTrios = partition(3);
const pairs = makePairs([1,2,3,4,5,6]);
const trios = makeTrios([1,2,3,4,5,6]);
console.log("partition(2)", JSON.stringify(pairs));
console.log("partition(3)", JSON.stringify(trios));
If you're willing to use another functional library 'ramda', aperture is the function you're looking for.
Example usage taken from the ramda docs:
R.aperture(2, [1, 2, 3, 4, 5]); //=> [[1, 2], [2, 3], [3, 4], [4, 5]]
R.aperture(3, [1, 2, 3, 4, 5]); //=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
R.aperture(7, [1, 2, 3, 4, 5]); //=> []
You may do as follows with just a sinle liner of .reduce()
with no initial;
var arr = [ 1, 5, 9, 21 ],
pairs = arr.reduce((p,c,i) => i == 1 ? [[p,c]] : p.concat([[p[p.length-1][1],c]]));
console.log(pairs);
A fast approach using map
would be:
const arr = [ 1, 5, 9, 21 ];
const grouped = arr.map((el, i) => [el, arr[i+1]]).slice(0, -1);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could map a spliced array and check the index. If it is not zero, take the predecessor, otherwise the first element of the original array.
var array = [1, 5, 9, 21],
result = array.slice(1).map((a, i, aa) => [i ? aa[i - 1] : array[0], a]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
An even shorter version, as suggested by Bergi:
var array = [1, 5, 9, 21],
result = array.slice(1).map((a, i) => [array[i], a]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's slide
which has two parameters to control the size of the slice and how many elements are dropped between slices
slide
differs from other answers here by giving you these control parameters. other answers here are limited to producing only a slices of 2, or incrementing the slice by 1 each time
// take :: (Int, [a]) -> [a]
const take = (n, xs) =>
xs.slice(0, n)
// drop :: (Int, [a]) -> [a]
const drop = (n, xs) =>
xs.slice(n)
// slice :: (Int, Int, [a]) -> [[a]]
const slide = (m, n, xs) =>
xs.length > m
? [take(m, xs), ...slide(m, n, drop(n, xs))]
: [xs]
const arr = [0,1,2,3,4,5,6]
// log helper improves readability of output in stack snippet
const log = x => console.log(JSON.stringify(x))
log(slide(1, 1, arr))
// [[0],[1],[2],[3],[4],[5],[6]]
log(slide(1, 2, arr))
// [[0],[2],[4],[6]]
log(slide(2, 1, arr))
// [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6]]
log(slide(2, 2, arr))
// [[0,1],[2,3],[4,5],[6]]
log(slide(3, 1, arr))
// [[0,1,2],[1,2,3],[2,3,4],[3,4,5],[4,5,6]]
log(slide(3, 2, arr))
// [[0,1,2],[2,3,4],[4,5,6]]
log(slide(3, 3, arr))
// [[0,1,2],[3,4,5],[6]]
If for some reason you didn't want slide
to include partial slices, (slices smaller than m
), we could edit it as such
// slice :: (Int, Int, [a]) -> [[a]]
const slide = (m, n, xs) =>
xs.length > m
? [take(m, xs), ...slide(m, n, drop(n, xs))]
: [] // <- return [] instead of [xs]
log(slide(2, 2, arr))
// now prints: [[0,1],[2,3],[4,5]]
// instead of: [[0,1],[2,3],[4,5],[6]]