Comparing 2 arrays and finding equal values with Ramda - javascript

I recently fell in love with functional programming, and started learning ramda.js, but i can't seem to get into the functional mindset just yet. I have 2 arrays of strings(they are actually splitted strings) and i want to find how many characters in the first string are equal to the ones in the same position in the second string.
Imperatively i would do something really simple along the lines of:
let counter = 0
for(let i = 0; i < array.length; i++){
if(firstArray[i] === secondArray[i]) counter++
}
but how would i do it using ramda?

"... using rambda"
Here's one way you can do it – there are countless other ways ...
const countEqualChars = (a, b) =>
R.sum(R.zipWith(R.equals, a, b))
countEqualChars(
[ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ],
[ 'a', 'b', 'c', 'x', 'y', 'z', 'g' ]
)
// 4
countEqualChars('abcdefg', 'abcxyzg')
// 4
But ...
That's basically the wrong way for you to approach functional programming. Forget Rambda until you have a good sense for how to reason about programs in a functional way. You'll never be able to appreciate Rambda's convenience if you don't know how things are working at a fundamental level.
Start by learning about recursion as an alternative to for/while loops. There's nothing wrong with loops, but recursion will allow you to express things in a nicer way sometimes ...
const a =
'abcdefg'
const b =
'abcxyzg'
const countEqualChars = ([ x, ...xs ], [ y, ...ys ]) =>
{ if (x === undefined || y === undefined)
return 0
else if (x === y)
return 1 + countEqualChars (xs, ys)
else
return countEqualChars (xs, ys)
}
console.log (countEqualChars (a, b))
// 4
Now we see that this function is doing quite a bit. We're inspecting arrays one element at a time, doing some comparison, and some counting. Maybe we could break that up into some separate tasks so that our function is a little more maintainable over time. Let's start with a function that allows us to pair up equal indices of two arrays ...
const zip = ([ x, ...xs ], [ y, ...ys ]) =>
x === undefined && y === undefined
? []
: [ [ x, y ], ...zip (xs, ys) ]
console.log (zip ([ 1, 2, 3 ], [ 'a', 'b', 'c' ]))
// [ [ 1, 'a' ]
// , [ 2, 'b' ]
// , [ 3, 'c' ]
// ]
Next, we can use the built-in filter function to make a new array containing only the things we want
const xs =
[ 1, 1, 2, 2, 3, 3, 4, 4 ]
const justThrees =
xs.filter (x => x === 3)
console.log (justThrees)
// [ 3, 3 ]
Combining those zip and filter strats, we can pair up each index from the strings, then remove the pairs that don't match...
const zip = ([ x, ...xs ], [ y, ...ys ]) =>
x === undefined && y === undefined
? []
: [ [ x, y ], ...zip (xs, ys) ]
const eq = (x, y) =>
x === y
const apply = f => xs =>
f (...xs)
const a =
'abcdefg'
const b =
'abcxyzgh'
const matches =
zip (a, b) .filter (apply (eq))
console.log (matches)
// [ [ 'a', 'a' ]
// , [ 'b', 'b' ]
// , [ 'c', 'c' ]
// , [ 'g', 'g' ]
// ]
Now all that's left is counting up the matching pairs. We'll make the whole thing into a function too so that you can re-use it as needed
const zip = ([ x, ...xs ], [ y, ...ys ]) =>
x === undefined && y === undefined
? []
: [ [ x, y ], ...zip (xs, ys) ]
const eq = (x, y) =>
x === y
const apply = f => xs =>
f (...xs)
const countEqualChars = (a, b) =>
zip (a, b)
.filter (apply (eq))
.length
console.log (countEqualChars ('abcdefg', 'abcxyzgh'))
// 4
most importantly ...
You'll see that the solution we derived by hand is slightly different than the one that we used with Rambda. That's because programming isn't magic and you'll be inventing a lot of your own tools before you realize what other tools even exist or understand what they do.
There's hundreds of way to solve the problem I just did, and there's trade-offs I considered with every choice I made. Your whole goal is to become able to break your program down into its constituent parts and learn the trade-offs over time.
Compare that to trying to understand why some function exists in some library just so you can guess where to use it... You'll get good at functional programming by doing it, not by copy/pasting people's clever Rambda one-liners. Once you understand why equals, zipWith, and sum exist, you'll know when to use them.

I can't compete with the other, excellent answer, but don't forget the array prototype has many useful methods such as filter or reduce!
var countMatches = (a, b) => a.reduce((sum, c, i) =>
b[i] === c
? ++sum
: sum
, 0);
var set1 = "1234678".split("");
var set2 = "1234568".split("");
console.log(countMatches(set1, set2));

Might want to take a look at intersection if you're just looking to see what elements the two arrays have in common.
R.intersection([1,2,3,4], [7,6,5,4,3]); //=> [4, 3]

Related

improve function that all unique combinations between set values ​result in the sum of the target number

I made this function to get all possible combinations of a set that sum to a target value, its works, but it is not as organize/efficient as possible yet.
After fry my brain trying to optimize this function i ran out of ideas and a litle bit blind, so i came here to get somes adivices and ideas for what more i can do.
const combinate = (set, target) => {
const result = []
set.forEach((element) => {
let division = Math.floor(target / element)
let remainder = target % element
while (remainder !== target) {
let combinations = []
for (let index = 0; index < division; index++) {
combinations.push(element)
}
if (remainder === 0) {
result.push(combinations.sort())
break
} else if (set.includes(remainder)) {
combinations.push(remainder)
result.push(combinations.sort())
break
} else {
division--
remainder += element
}
}
})
return result
}
Here I have some examples of expected outcomes for how this function it should work.
combinate([2, 3, 5], 8) -> [[2,2,2,2],[2,3,3],[3,5]]
I think your algorithm is fine.
I personally would structure the code differently, as I'm a fan of expression-only coding. My version might look like this:
// Ex: countDown (6) //=> [6, 5, 4, 3, 2, 1, 0]
const countDown = (n) =>
n < 0 ? [] : [n, ...countDown (n - 1)]
const subsetSum = ([n, ...ns], t) =>
n == undefined
? t == 0 ? [[]] : []
: countDown (Math.floor (t / n)) .flatMap (
(k) => subsetSum (ns, t - k * n) .map (ss => [...Array (k) .fill (n), ...ss])
)
console .log (subsetSum ([2, 3, 5], 8))
.as-console-wrapper {max-height: 100% !important; top: 0}
I count down rather than up just so the results come in the same order yours did. If I counted up, they would show up as [[3, 5], [2, 3, 3], [2, 2, 2, 2]].
But this is essentially the same algorithm as yours. If it's ill-performant, then I might look at a dynamic programming version where we calculate the results for each lower total, and then for our target total, we look up the values found by subtracting each of the number in our set, and for each of those results, we add one of that number. Here's one version:
const countUp = (n) =>
(n < 1) ? [] : [... countUp (n - 1), n]
const oneMore = (i) => (s) =>
s .split ('-') .map (Number) .map ((n, j) => j == i ? n + 1 : n) .join ('-')
const collect = (ns, t) =>
countUp (t) .reduce (
(rs, v) => [
...rs,
new Set (ns .flatMap ((n, i) => ([...rs [v - n] || []]) .map (oneMore (i))))
],
[new Set([ns .map (n => 0) .join ('-')])]
)
const subsetSum = (ns, t) =>
[...collect (ns, t) [t]]
.map (s => s.split ('-') .map(Number) .flatMap ((c, i) => Array (c) .fill (ns[i])))
console .log (subsetSum ([2, 3, 5], 8))
.as-console-wrapper {max-height: 100% !important; top: 0}
The main function here, collect accepts, say [2, 3, 5] and 8, and returns something like
[
new Set (['0-0-0']), // 0
new Set ([]), // 1
new Set (['1-0-0']), // 2
new Set (['0-1-0']), // 3
new Set (['2-0-0']), // 4
new Set (['1-1-0', '0-0-1']), // 5
new Set (['3-0-0', '0-2-0']), // 6
new Set (['2-1-0', '1-0-1']), // 7
new Set (['4-0-0', '1-2-0', '0-1-1']), // 8
]
where, say '1-1-0' represents one 2 and one 3 and zero 5s, which add up to 5, or '0-1-1' represents zero 2s and one 3 and one 5, which add up to 8. In retrospect, a better string format would probably have been the JSON-stringified version of something like {2: 1, 3: 1, 5: 0} But I'll leave that as an exercise.
The values are stored as Strings in Sets to eliminate duplicates as we go. For instance, when we hit 5, we can add a 2 to '0-1-0' or a 3 to '1-0-0', both of which end up as '1-1-0'. But we only want a single copy of that result.
We use two minor helper functions. countUp for instance, turns 7 into [1, 2, 3, 4, 5, 6, 7]. oneMore handles the string to Array of numbers back to string conversion such that
oneMore (0) ('1-7-4') //=> '2-7-4'
oneMore (1) ('1-7-4') //=> '1-8-4'
oneMore (2) ('1-7-4') //=> '1-7-5'
The main function simply extracts the last value computed by collect and then for each of the Strings in the set, converts that back into a proper array.
I have not tested for performance, but there's a real chance that this will be faster than the original algorithm. If nothing else, it demonstrates a substantially different technique.

How do I replace for loops with recursion in Javascript?

I have 2 for loops that work well to create a grid with rows and columns, but I would like to improve the solution using recursion as it is more cleaner and recommended (in Functional Programming).
The desired output is a single array of pairs used in css grid
const createGrid = (rows,columns) => {
let grid=[]
for (let y = 3; y <= rows; y += 2){
let row = []
for (let x = 3; x <= columns; x += 2){
let col = [y, x]
row = [...row,col]
}
grid =[...grid, ...row]
}
return grid
}
Are there also any guidelines as to how to convert for loops to recursion solutions when possible?
primitive recursion
Here's one possible way to make a grid using recursion -
const makeGrid = (f, rows = 0, cols = 0) =>
rows <= 0
? []
: [ ...makeGrid(f, rows - 1, cols), makeRow(f, rows, cols) ]
const makeRow = (f, row = 0, cols = 0) =>
cols <= 0
? []
: [ ...makeRow(f, row, cols - 1), f(row, cols) ]
const g =
makeGrid((x, y) => ({ xPos: x, yPos: y }), 2, 3)
console.log(JSON.stringify(g))
// [ [ {"xPos":1,"yPos":1}
// , {"xPos":1,"yPos":2}
// , {"xPos":1,"yPos":3}
// ]
// , [ {"xPos":2,"yPos":1}
// , {"xPos":2,"yPos":2}
// , {"xPos":2,"yPos":3}
// ]
// ]
The functional param f allows us to construct grid cells in a variety of ways
const g =
makeGrid((x, y) => [ x - 1, y - 1 ], 3, 2)
console.log(JSON.stringify(g))
// [ [ [ 0, 0 ]
// , [ 0, 1 ]
// ]
// , [ [ 1, 0 ]
// , [ 1, 1 ]
// ]
// , [ [ 2, 0 ]
// , [ 2, 1 ]
// ]
// ]
work smarter, not harder
Per Bergi's comment, you can reduce some extra argument passing by using a curried cell constructor -
const makeGrid = (f, rows = 0, cols = 0) =>
rows <= 0
? []
: [ ...makeGrid(f, rows - 1, cols), makeRow(f(rows), cols) ]
const makeRow = (f, cols = 0) =>
cols <= 0
? []
: [ ...makeRow(f, cols - 1), f(cols) ]
const g =
makeGrid
( x => y => [ x, y ] // "curried" constructor
, 2
, 3
)
console.log(JSON.stringify(g))
// [ [ [ 1, 1 ]
// , [ 1, 2 ]
// , [ 1, 3 ]
// ]
// , [ [ 2, 1 ]
// , [ 2, 2 ]
// , [ 2, 3 ]
// ]
// ]
have your cake and eat it too
Alternatively, we can incorporate the suggestion and still accept a binary function at the call site using partial application -
const makeGrid = (f, rows = 0, cols = 0) =>
rows <= 0
? []
: [ ...makeGrid(f, rows - 1, cols)
, makeRow(_ => f(rows, _), cols) // <-- partially apply f
]
const makeRow = (f, cols = 0) =>
cols <= 0
? []
: [ ...makeRow(f, cols - 1), f(cols) ]
const g =
makeGrid
( (x,y) => [ x, y ] // ordinary constructor
, 2
, 3
)
console.log(JSON.stringify(g))
// [ [ [ 1, 1 ]
// , [ 1, 2 ]
// , [ 1, 3 ]
// ]
// , [ [ 2, 1 ]
// , [ 2, 2 ]
// , [ 2, 3 ]
// ]
// ]
Nth dimension
Above we are limited to 2-dimensional grids. What if we wanted 3-dimensions or even more?
const identity = x =>
x
const range = (start = 0, end = 0) =>
start >= end
? []
: [ start, ...range(start + 1, end) ] // <-- recursion
const map = ([ x, ...more ], f = identity) =>
x === undefined
? []
: [ f(x), ...map(more, f) ] // <-- recursion
const makeGrid = (r = [], d = 0, ...more) =>
d === 0
? r
: map(range(0, d), x => makeGrid(r(x), ...more)) // <-- recursion
const g =
makeGrid
( x => y => z => [ x, y, z ] // <-- constructor
, 2 // <-- dimension 1
, 2 // <-- dimension 2
, 3 // <-- dimension 3
, // ... <-- dimension N
)
console.log(JSON.stringify(g))
Output
[ [ [ [0,0,0]
, [0,0,1]
, [0,0,2]
]
, [ [0,1,0]
, [0,1,1]
, [0,1,2]
]
]
, [ [ [1,0,0]
, [1,0,1]
, [1,0,2]
]
, [ [1,1,0]
, [1,1,1]
, [1,1,2]
]
]
]
any dimensions; flat result
Per you comment, you want a flat array of pairs. You can achieve that by simply substituting map for flatMap, as demonstrated below -
const identity = x =>
x
const range = (start = 0, end = 0) =>
start >= end
? []
: [ start, ...range(start + 1, end) ]
const flatMap = ([ x, ...more ], f = identity) =>
x === undefined
? []
: [ ...f(x), ...flatMap(more, f) ] // <-- flat!
const makeGrid = (r = [], d = 0, ...more) =>
d === 0
? r
: flatMap(range(0, d), x => makeGrid(r(x), ...more))
const g =
makeGrid
( x => y => [{ x, y }] // <-- constructor
, 2 // <-- dimension 1
, 2 // <-- dimension 2
, // ... <-- dimension N
)
console.log(JSON.stringify(g))
// [ { x: 0, y: 0 }
// , { x: 0, y: 1 }
// , { x: 1, y: 0 }
// , { x: 1, y: 1 }
// ]
The functional constructor demonstrates its versatility again -
const g =
makeGrid
( x => y =>
[[ 3 + x * 2, 3 + y * 2 ]] // whatever you want
, 3
, 3
)
console.log(JSON.stringify(g))
// [[3,3],[3,5],[3,7],[5,3],[5,5],[5,7],[7,3],[7,5],[7,7]]
learn more
As other have show, this particular version of makeGrid using flatMap is effectively computing a cartesian product. By the time you've wrapped your head around flatMap, you already know the List Monad!
more cake, please!
If you're hungry for more, I want to give you a primer on one of my favourite topics in computational study: delimited continuations. Getting started with first class continuations involves developing an intuition on some ways in which they are used -
reset
( call
( (x, y) => [[ x, y ]]
, amb([ 'J', 'Q', 'K', 'A' ])
, amb([ '♡', '♢', '♤', '♧' ])
)
)
// [ [ J, ♡ ], [ J, ♢ ], [ J, ♤ ], [ J, ♧ ]
// , [ Q, ♡ ], [ Q, ♢ ], [ Q, ♤ ], [ Q, ♧ ]
// , [ K, ♡ ], [ K, ♢ ], [ K, ♤ ], [ K, ♧ ]
// , [ A, ♡ ], [ A, ♢ ], [ A, ♤ ], [ A, ♧ ]
// ]
Just like the List Monad, above amb encapsulates this notion of ambiguous (non-deterministic) computations. We can easily write our 2-dimensional simpleGrid using delimited continuations -
const simpleGrid = (f, dim1 = 0, dim2 = 0) =>
reset
( call
( f
, amb(range(0, dim1))
, amb(range(0, dim2))
)
)
simpleGrid((x, y) => [[x, y]], 3, 3)
// [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]]
Creating an N-dimension grid is a breeze thanks to amb as well. The implementation has all but disappeared -
const always = x =>
_ => x
const multiGrid = (f = always([]), ...dims) =>
reset
( apply
( f
, dims.map(_ => amb(range(0, _)))
)
)
multiGrid
( (x, y, z) => [[ x, y, z ]] // <-- not curried this time, btw
, 3
, 3
, 3
)
// [ [0,0,0], [0,0,1], [0,0,2]
// , [0,1,0], [0,1,1], [0,1,2]
// , [0,2,0], [0,2,1], [0,2,2]
// , [1,0,0], [1,0,1], [1,0,2]
// , [1,1,0], [1,1,1], [1,1,2]
// , [1,2,0], [1,2,1], [1,2,2]
// , [2,0,0], [2,0,1], [2,0,2]
// , [2,1,0], [2,1,1], [2,1,2]
// , [2,2,0], [2,2,1], [2,2,2]
// ]
Or we can create the desired increments and offsets using line in the cell constructor -
const line = (m = 1, b = 0) =>
x => m * x + b // <-- linear equation, y = mx + b
multiGrid
( (...all) => [ all.map(line(2, 3)) ] // <-- slope: 2, y-offset: 3
, 3
, 3
, 3
)
// [ [3,3,3], [3,3,5], [3,3,7]
// , [3,5,3], [3,5,5], [3,5,7]
// , [3,7,3], [3,7,5], [3,7,7]
// , [5,3,3], [5,3,5], [5,3,7]
// , [5,5,3], [5,5,5], [5,5,7]
// , [5,7,3], [5,7,5], [5,7,7]
// , [7,3,3], [7,3,5], [7,3,7]
// , [7,5,3], [7,5,5], [7,5,7]
// , [7,7,3], [7,7,5], [7,7,7]
// ]
So where do reset, call, apply, and amb come from? JavaScript does not support first class continuations, but nothing stops us from implementing them on our own -
const call = (f, ...values) =>
({ type: call, f, values }) //<-- ordinary object
const apply = (f, values) =>
({ type: call, f, values }) //<-- ordinary object
const shift = (f = identity) =>
({ type: shift, f }) //<-- ordinary object
const amb = (xs = []) =>
shift(k => xs.flatMap(x => k(x))) //<-- returns ordinary object
const reset = (expr = {}) =>
loop(() => expr) //<-- ???
const loop = f =>
// ... //<-- follow the link!
Given the context of your question, it should be obvious that this is a purely academic exercise. Scott's answer offers sound rationale on some of the trade-offs we make. Hopefully this section shows you that higher-powered computational features can easily tackle problems that initially appear complex.
First class continuations unlock powerful control flow for your programs. Have you ever wondered how JavaScript implements function* and yield? What if JavaScript didn't have these powers baked in? Read the post to see how we can make these (and more) using nothing but ordinary functions.
continuations code demo
See it work in your own browser! Expand the snippet below to generate grids using delimited continuations... in JavaScript! -
// identity : 'a -> 'a
const identity = x =>
x
// always : 'a -> 'b -> 'a
const always = x =>
_ => x
// log : (string, 'a) -> unit
const log = (label, x) =>
console.log(label, JSON.stringify(x))
// line : (int, int) -> int -> int
const line = (m, b) =>
x => m * x + b
// range : (int, int) -> int array
const range = (start = 0, end = 0) =>
start >= end
? []
: [ start, ...range(start + 1, end) ]
// call : (* -> 'a expr, *) -> 'a expr
const call = (f, ...values) =>
({ type: call, f, values })
// apply : (* -> 'a expr, * array) -> 'a expr
const apply = (f, values) =>
({ type: call, f, values })
// shift : ('a expr -> 'b expr) -> 'b expr
const shift = (f = identity) =>
({ type: shift, f })
// reset : 'a expr -> 'a
const reset = (expr = {}) =>
loop(() => expr)
// amb : ('a array) -> ('a array) expr
const amb = (xs = []) =>
shift(k => xs .flatMap (x => k (x)))
// loop : (unit -> 'a expr) -> 'a
const loop = f =>
{ // aux1 : ('a expr, 'a -> 'b) -> 'b
const aux1 = (expr = {}, k = identity) =>
{ switch (expr.type)
{ case call:
return call(aux, expr.f, expr.values, k)
case shift:
return call
( aux1
, expr.f(x => trampoline(aux1(x, k)))
, identity
)
default:
return call(k, expr)
}
}
// aux : (* -> 'a, (* expr) array, 'a -> 'b) -> 'b
const aux = (f, exprs = [], k) =>
{ switch (exprs.length)
{ case 0:
return call(aux1, f(), k) // nullary continuation
case 1:
return call
( aux1
, exprs[0]
, x => call(aux1, f(x), k) // unary
)
case 2:
return call
( aux1
, exprs[0]
, x =>
call
( aux1
, exprs[1]
, y => call(aux1, f(x, y), k) // binary
)
)
case 3: // ternary ...
case 4: // quaternary ...
default: // variadic
return call
( exprs.reduce
( (mr, e) =>
k => call(mr, r => call(aux1, e, x => call(k, [ ...r, x ])))
, k => call(k, [])
)
, values => call(aux1, f(...values), k)
)
}
}
return trampoline(aux1(f()))
}
// trampoline : * -> *
const trampoline = r =>
{ while (r && r.type === call)
r = r.f(...r.values)
return r
}
// simpleGrid : ((...int -> 'a), int, int) -> 'a array
const simpleGrid = (f, dim1 = 0, dim2 = 0) =>
reset
( call
( f
, amb(range(0, dim1))
, amb(range(0, dim2))
)
)
// multiGrid : (...int -> 'a, ...int) -> 'a array
const multiGrid = (f = always([]), ...dims) =>
reset
( apply
( f
, dims.map(_ => amb(range(0, _)))
)
)
// : unit
log
( "simple grid:"
, simpleGrid((x, y) => [[x, y]], 3, 3)
)
// : unit
log
( "multiGrid:"
, multiGrid
( (...all) => [ all.map(line(2, 3)) ]
, 3
, 3
, 3
)
)
At first, on reading your code, I thought you generated one style of grid, so that makeGrid (7, 9) would result in something like this:
[
[[3, 3], [3, 5], [3, 7], [3, 9]],
[[5, 3], [5, 5], [5, 7], [5, 9]],
[[7, 3], [7, 5], [7, 7], [7, 9]]
]
Instead, it returns a single array of pairs:
[[3, 3], [3, 5], [3, 7], [3, 9], [5, 3], [5, 5], [5, 7], [5, 9], [7, 3], [7, 5], [7, 7], [7, 9]]
I'm pretty sure I'm not the only one. Bergi suggested a fix in the comments to change it to the former. (That's what changing grid =[...grid, ...row] to grid =[...grid, row] would do.) And the wonderful answer from Thankyou is predicated on the same assumption.
This is a problem.
When the reader can't quickly understand what your code does, it becomes much harder to maintain... even for yourself just a few weeks later.
The reason you may hear advice to replace loops with recursion is related to this. Loops are all about explicit imperative instructions to get what you want, depending on mutating variables, which then you have to keep track of, and easily subject to off-by-one errors. Recursion is usually more declarative, a way of saying that the result you're looking for is just a matter of combining these simpler results with our current data, and pointing out how to get the simpler results, through either a base case or a recursive call.
The advantage in readability and understandability, though, is the key, not the fact that the solution is recursive.
Don't get me wrong, recursion is one of my favorite programming techniques. The answer from Thankyou is beatiful and elegant. But it's not the only technique which will fix the problems that explicit for-loops present. Usually one of the first things I do when trying to move junior programmer to intermediate and beyond is to replace for-loops with more meaningful constructs. Most loops are trying to do one of a few things. They're trying to convert every element of a list into something new (map), trying to choose some important subset of the elements (filter), trying to find the first important element (find), or trying to combine all the elements into a single value (reduce). By using these instead, the code become more explicit.
Also important, as seen in the answer from Thankyou, is splitting out reusable pieces of the code so that your main function can focus on the important parts. The version below extracts a function rangeBy, which adds a step parameter to my usual range function. range creates a range of integers so that, for instance, range (3, 12) yields [3, 4, 5, 6, 7, 8, 9, 10, 11, 12] rangeBy adds an initial step parameter, so that range (2) (3, 12) yields [3, 5, 7, 9, 11].
We use that rangeBy function along with a map, and its cousin, flatMap to make a more explicit version of your looped function:
const rangeBy = (step) => (lo, hi) =>
[... Array (Math .ceil ((hi - lo + 1) / step))]
.map ((_, i) => i * step + lo)
const createGrid = (rows, columns) =>
rangeBy (2) (3, rows) .flatMap (y =>
rangeBy (2) (3, columns) .map (x =>
[y, x]
)
)
console .log (createGrid (7, 9))
Knowing what rangeBy does, we can mentally read this as
const createGrid = (rows, columns) =>
[3, 5, 7, ..., rows] .flatMap (y =>
[3, 5, 7, ..., columns] .map (x =>
[y, x]
)
)
Note that if you want the behavior I was expecting, you can achieve it just by replacing flatMap with map in createGrid. Also, if you do so, it's trivial to add the more generic behavior that Thankyou offers, by replacing [y, x] with f (x, y) and passing f as a parameter. What remains hard-coded in this version is the conversion of rows and columns into arrays of odd numbers starting with 3. We could make the actual arrays the arguments to our function, and applying rangeBy outside. But at that point, we're probably looking at a different function ideally named cartesianProduct.
So recursion is an amazing and useful tool. But it's a tool, not a goal. Simple, readable code, however, is an important goal.
Update
I meant to mention this originally and simply forgot. The following version demonstrates that the currying in rangeBy is far from fundamental. We can use a single call easily:
const rangeBy = (step, lo, hi) =>
[... Array (Math .ceil ((hi - lo + 1) / step))]
.map ((_, i) => i * step + lo)
const createGrid = (rows, columns) =>
rangeBy (2, 3, rows) .flatMap (y =>
rangeBy (2, 3, columns) .map (x =>
[y, x]
)
)
console .log (createGrid (7, 9))
The main rationale for currying rangeBy is that when it's written like this:
const rangeBy = (step) => (lo, hi) =>
[... Array (Math .ceil ((hi - lo + 1) / step))]
.map ((_, i) => i * step + lo)
we can write the more common range by simply applying 1 to the above. That is,
const range = rangeBy (1)
range (3, 12) //=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
This is so useful that it's become my usual style for writing functions. But it is not a significant part of the simplification of your problem.
Functional programming is more about higher-order functions than direct recursion. I believe the following is equivalent to your example, using _.range from underscore.js and map and flatMap from the standard library.
const rowRange = _.range(3, rows + 1, 2);
const colRange = _.range(3, columns + 1, 2);
return rowRange.flatMap(row => colRange.map(col => [col, row]));

One-Dimensional Array Iteration Using Recursion

I'm trying to iterate over a simple array using recursion. For this specific case, I'm trying to recreate .map() using recursion (without using .map()!. I currently am only pushing the last element in the original array, but I want to push all into the array.
function recursiveMap (arr, func) {
let newArr = [];
if (arr.length === 1){
newArr.push(func(arr));
}
else {
newArr.push(...recursiveMap(arr.slice(1),func));
}
return newArr;
}
You need to to use func on the current item, and spread the result of calling the function on the rest of the array:
function recursiveMap(arr, func) {
return arr.length ? [func(arr[0]), ...recursiveMap(arr.slice(1), func)] : [];
}
const arr = [1, 2, 3];
const result = recursiveMap(arr, n => n * 2);
console.log(result);
Your base case seems wrong. You will need to check for an empty array:
function recursiveMap (arr, func) {
let newArr = [];
if (arr.length === 0) {
// do nothing
} else {
newArr.push(func(arr[0]));
newArr.push(...recursiveMap(arr.slice(1),func));
}
return newArr;
}
Instead you will need to call func (on the first item) when there is at least one element.
With recursion, I find it is helpful to have the base case be the very first thing you check in your function, and short the execution there. The base case for map is if the array has 0 items, in which case you would return an empty array.
if you haven't seen it before let [a, ...b] is array destructuring and a becomes the first value with b holding the remaining array. You could do the same with slice.
function recursiveMap(arr, func){
if(arr.length == 0) return [];
let [first, ...rest] = arr;
return [func(first)].concat(recursiveMap(rest, func));
}
let test = [1,2,3,4,5,6,7];
console.log(recursiveMap(test, (item) => item * 2));
EDIT
Going back to your sample I see you clearly have seen destructuring before xD, sorry. Leaving it in the answer for future readers of the answer though.
Below are a few alternatives. Each recursiveMap
does not mutate input
produces a new array as output
produces a valid result when an empty input is given, []
uses a single pure, functional expression
Destructuring assignment
const identity = x =>
x
const recursiveMap = (f = identity, [ x, ...xs ]) =>
x === undefined
? []
: [ f (x), ...recursiveMap (f, xs) ]
const square = (x = 0) =>
x * x
console.log (recursiveMap (square, [ 1, 2, 3, 4, 5 ]))
// [ 1, 4, 9, 16, 25 ]
Array slice
const identity = x =>
x
const recursiveMap = (f = identity, xs = []) =>
xs.length === 0
? []
: [ f (xs[0]), ...recursiveMap (f, xs.slice (1)) ]
const square = (x = 0) =>
x * x
console.log (recursiveMap (square, [ 1, 2, 3, 4, 5 ]))
// [ 1, 4, 9, 16, 25 ]
Additional parameter with default assignment – creates fewer intermediate values
const identity = x =>
x
const recursiveMap = (f = identity, xs = [], i = 0) =>
i >= xs.length
? []
: [ f (xs[i]) ] .concat (recursiveMap (f, xs, i + 1))
const square = (x = 0) =>
x * x
console.log (recursiveMap (square, [ 1, 2, 3, 4, 5 ]))
// [ 1, 4, 9, 16, 25 ]
Tail recursive (and cute)
const identity = x =>
x
const prepend = x => xs =>
[ x ] .concat (xs)
const compose = (f, g) =>
x => f (g (x))
const recursiveMap = (f = identity, [ x, ...xs ], then = identity) =>
x === undefined
? then ([])
: recursiveMap
( f
, xs
, compose (then, prepend (f (x)))
)
const square = (x = 0) =>
x * x
console.log (recursiveMap (square, [ 1, 2, 3, 4, 5 ]))
// [ 1, 4, 9, 16, 25 ]
// => undefined
recursiveMap (square, [ 1, 2, 3, 4, 5 ], console.log)
// [ 1, 4, 9, 16, 25 ]
// => undefined
recursiveMap (square, [ 1, 2, 3, 4, 5 ])
// => [ 1, 4, 9, 16, 25 ]
Derived from tail-recursive foldl – Note foldl chooses a similar technique used above: additional parameter with default assignment.
const identity = x =>
x
const foldl = (f = identity, acc = null, xs = [], i = 0) =>
i >= xs.length
? acc
: foldl
( f
, f (acc, xs[i])
, xs
, i + 1
)
const recursiveMap = (f = identity, xs = []) =>
foldl
( (acc, x) => acc .concat ([ f (x) ])
, []
, xs
)
const square = (x = 0) =>
x * x
console.log (recursiveMap (square, [ 1, 2, 3, 4, 5 ]))
// [ 1, 4, 9, 16, 25 ]
You could take another approach by using a third parameter for the collected values.
function recursiveMap(array, fn, result = []) {
if (!array.length) {
return result;
}
result.push(fn(array[0]));
return recursiveMap(array.slice(1), fn, result);
}
console.log(recursiveMap([1, 2, 3, 4, 5], x => x << 1));
console.log(recursiveMap([], x => x << 1));
welcome to Stack Overflow. You could either pass result to itself like in the following example:
function recursiveMap (arr, func,result=[]) {
if (arr.length === 0){
return result;
}
return recursiveMap(
arr.slice(1),
func,
result.concat([func(arr[0])])
);
}
console.log(recursiveMap([1,2,3,4],x=>(x===3)?['hello','world']:x+2));
Or define a recursive function in your function:
function recursiveMap (arr, func) {
const recur = (arr, func,result=[])=>
(arr.length === 0)
? result
: recur(
arr.slice(1),
func,
result.concat([func(arr[0])])
);
return recur(arr,func,[])
}
console.log(recursiveMap([1,2,3,4],x=>(x===3)?['hello','world']:x+2));
Add newArr.push(func(arr[0])); before calling again the function
function recursiveMap (arr, func) {
let newArr = [];
if (arr.length === 1){
newArr.push(func(arr));
}
else {
newArr.push(func(arr[0]));
newArr.push(...recursiveMap(arr.slice(1),func));
}
return newArr;
}
console.log(recursiveMap([1,2,3], function(a){return +a+2}))
Same but modified answer with bugs corrected
function recursiveMap (arr, func) {
let newArr = [];
if(arr.length){
newArr.push(func(arr[0]));
if(arr.length > 1){
newArr.push(...recursiveMap(arr.slice(1),func));
}
}
return newArr;
}
console.log(recursiveMap([1,2,3], function(a){return a+2}))

Recursion - Sum Nested Array

I'm trying to sum a nested array [1,2,[3,4],[],[5]] without using loops but I don't see what's wrong with what I have so far.
function sumItems(array) {
let sum = 0;
array.forEach((item) => {
if (Array.isArray(item)) {
sumItems(item);
} else {
sum += item;
}
});
return sum;
}
try with
function sumItems(array) {
let sum = 0;
array.forEach((item) => {
if(Array.isArray(item)) {
sum += sumItems(item);
} else {
sum += item;
}
})
return sum;
}
recursion is a functional heritage
Recursion is a concept that comes from functional style. Mixing it with imperative style is a source of much pain and confusion for new programmers.
To design a recursive function, we identify the base and inductive case(s).
base case - the list of items to sum is empty; ie, item is Empty. return 0
inductive case 1 - the list of items is not empty; ie, there must be at least one item. if the item is a list, return its sum plus the sum of the rest of the items
inductive case 2 - there is at least one item that is not an array. return this item plus the sum of the rest of the items
const Empty =
Symbol ()
const sumDeep = ([ item = Empty, ...rest ] = []) =>
item === Empty
? 0
: Array.isArray (item)
? sumDeep (item) + sumDeep (rest)
: item + sumDeep (rest)
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
As a result of this implementation, all pain and suffering are removed from the program. We do not concern ourselves with local state variables, variable reassignment, or side effects like forEach and not using the return value of a function call.
recursion caution
And a tail-recursive version which can be made stack-safe. Here, we add a parameter cont to represent our continuation which effectively allows us sequence the order of + operations without growing the stack – changes in bold
const identity = x =>
x
const sumDeep = ([ item = Empty, ...rest ] = [], cont = identity) =>
item === Empty
? cont (0)
: Array.isArray (item)
? sumDeep (item, a =>
sumDeep (rest, b =>
cont (a + b)))
: sumDeep (rest, a =>
cont (item + a))
Usage is identitcal
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
performance enhancement
As #גלעד ברקן points out, array destructuring syntax used above (eg ...rest) create copies of the input array. As demonstrated in his/her answer, an index parameter can be used which will avoid creating copies. This variation shows how the index technique can also be used in a tail-recursive way
const identity = x =>
x
const sumDeep = (items = [], i = 0, cont = identity) =>
i >= items.length
? cont (0)
: Array.isArray (items [i])
? sumDeep (items [i], 0, a =>
sumDeep (items, i + 1, b =>
cont (a + b)))
: sumDeep (items, i + 1, a =>
cont (items [i] + a))
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
Here's a version without using loops:
function f(arr, i){
if (i == arr.length)
return 0;
if (Array.isArray(arr[i]))
return f(arr[i], 0) + f(arr, i + 1);
return arr[i] + f(arr, i + 1);
}
console.log(f([1,2,[3,4],[],[5]], 0));
You could define a callback for using with Array#reduce, which check if an item is an array and uses this function again for that array.
function add(s, v) {
return Array.isArray(v)
? v.reduce(add, s)
: s + v;
}
var array = [1, 2, [3, 4], [], [5]];
console.log(array.reduce(add, 0));
You may do as follows;
var sumNested = ([a,...as]) => (as.length && sumNested(as)) + (Array.isArray(a) ? sumNested(a) : a || 0);
console.log(sumNested([1,2,3,[4,[5,[6]]],7,[]]));
The function argument designation [a,…as] means that when the function is fed with a nested array like [1,2,3,[4,[5,[6]]],7,[]] then a is assigned to the head which is 1 and as is assigned to the tail of the initial array which is [2,3,[4,[5,[6]]],7,[]]. The rest should be easy to understand.
function arraySum (array) {
if (array.length > 0) {
return arraySum(array[0]) + arraySum(array.slice(1));
}
if (array.length === 0) {
return 0;
} else {
return array;
}
};
This is similar to some of the other solutions but might be easier for some to read:
function Sum(arr) {
if (!arr.length) return 0;
if (Array.isArray(arr[0])) return Sum(arr[0]) + Sum(arr.slice(1));
return arr[0] + Sum(arr.slice(1));
}
console.log(Sum([[1],2,[3,[4,[5,[6,[7,[8,9,10],11,[12]]]]]]])) // 78

Javascript equivalent of Python's zip function

Is there a javascript equivalent of Python's zip function? That is, given multiple arrays of equal lengths create an array of pairs.
For instance, if I have three arrays that look like this:
var array1 = [1, 2, 3];
var array2 = ['a','b','c'];
var array3 = [4, 5, 6];
The output array should be:
var outputArray = [[1,'a',4], [2,'b',5], [3,'c',6]]
2016 update:
Here's a snazzier Ecmascript 6 version:
zip= rows=>rows[0].map((_,c)=>rows.map(row=>row[c]))
Illustration equiv. to Python{zip(*args)}:
> zip([['row0col0', 'row0col1', 'row0col2'],
['row1col0', 'row1col1', 'row1col2']]);
[["row0col0","row1col0"],
["row0col1","row1col1"],
["row0col2","row1col2"]]
(and FizzyTea points out that ES6 has variadic argument syntax, so the following function definition will act like python, but see below for disclaimer... this will not be its own inverse so zip(zip(x)) will not equal x; though as Matt Kramer points out zip(...zip(...x))==x (like in regular python zip(*zip(*x))==x))
Alternative definition equiv. to Python{zip}:
> zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]))
> zip( ['row0col0', 'row0col1', 'row0col2'] ,
['row1col0', 'row1col1', 'row1col2'] );
// note zip(row0,row1), not zip(matrix)
same answer as above
(Do note that the ... syntax may have performance issues at this time, and possibly in the future, so if you use the second answer with variadic arguments, you may want to perf test it. That said it's been quite a while since it's been in the standard.)
Make sure to note the addendum if you wish to use this on strings (perhaps there's a better way to do it now with es6 iterables).
Here's a oneliner:
function zip(arrays) {
return arrays[0].map(function(_,i){
return arrays.map(function(array){return array[i]})
});
}
// > zip([[1,2],[11,22],[111,222]])
// [[1,11,111],[2,22,222]]]
// If you believe the following is a valid return value:
// > zip([])
// []
// then you can special-case it, or just do
// return arrays.length==0 ? [] : arrays[0].map(...)
The above assumes that the arrays are of equal size, as they should be. It also assumes you pass in a single list of lists argument, unlike Python's version where the argument list is variadic. If you want all of these "features", see below. It takes just about 2 extra lines of code.
The following will mimic Python's zip behavior on edge cases where the arrays are not of equal size, silently pretending the longer parts of arrays don't exist:
function zip() {
var args = [].slice.call(arguments);
var shortest = args.length==0 ? [] : args.reduce(function(a,b){
return a.length<b.length ? a : b
});
return shortest.map(function(_,i){
return args.map(function(array){return array[i]})
});
}
// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222]]]
// > zip()
// []
This will mimic Python's itertools.zip_longest behavior, inserting undefined where arrays are not defined:
function zip() {
var args = [].slice.call(arguments);
var longest = args.reduce(function(a,b){
return a.length>b.length ? a : b
}, []);
return longest.map(function(_,i){
return args.map(function(array){return array[i]})
});
}
// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222],[null,null,333]]
// > zip()
// []
If you use these last two version (variadic aka. multiple-argument versions), then zip is no longer its own inverse. To mimic the zip(*[...]) idiom from Python, you will need to do zip.apply(this, [...]) when you want to invert the zip function or if you want to similarly have a variable number of lists as input.
addendum:
To make this handle any iterable (e.g. in Python you can use zip on strings, ranges, map objects, etc.), you could define the following:
function iterView(iterable) {
// returns an array equivalent to the iterable
}
However if you write zip in the following way, even that won't be necessary:
function zip(arrays) {
return Array.apply(null,Array(arrays[0].length)).map(function(_,i){
return arrays.map(function(array){return array[i]})
});
}
Demo:
> JSON.stringify( zip(['abcde',[1,2,3,4,5]]) )
[["a",1],["b",2],["c",3],["d",4],["e",5]]
(Or you could use a range(...) Python-style function if you've written one already. Eventually you will be able to use ECMAScript array comprehensions or generators.)
Check out the library Underscore.
Underscore provides over 100 functions that support both your favorite workaday functional helpers: map, filter, invoke — as well as more specialized goodies: function binding, javascript templating, creating quick indexes, deep equality testing, and so on.
– Say the people who made it
I recently started using it specifically for the zip() function and it has left a great first impression. I am using jQuery and CoffeeScript, and it just goes perfectly with them. Underscore picks up right where they leave off and so far it hasn't let me down. Oh by the way, it's only 3kb minified.
Check it out:
_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
Modern ES6 example with a generator:
function *zip (...iterables){
let iterators = iterables.map(i => i[Symbol.iterator]() )
while (true) {
let results = iterators.map(iter => iter.next() )
if (results.some(res => res.done) ) return
else yield results.map(res => res.value )
}
}
First, we get a list of iterables as iterators. This usually happens transparently, but here we do it explicitly, as we yield step-by-step until one of them is exhausted. We check if any of results (using the .some() method) in the given array is exhausted, and if so, we break the while loop.
In addition to ninjagecko's excellent and comprehensive answer, all it takes to zip two JS-arrays into a "tuple-mimic" is:
//Arrays: aIn, aOut
Array.prototype.map.call( aIn, function(e,i){return [e, aOut[i]];})
Explanation:
Since Javascript doesn't have a tuples type, functions for tuples, lists and sets wasn't a high priority in the language specification.
Otherwise, similar behavior is accessible in a straightforward manner via Array map in JS >1.6. (map is actually often implemented by JS engine makers in many >JS 1.4 engines, despite not specified).
The major difference to Python's zip, izip,... results from map's functional style, since map requires a function-argument. Additionally it is a function of the Array-instance. One may use Array.prototype.map instead, if an extra declaration for the input is an issue.
Example:
_tarrin = [0..constructor, function(){}, false, undefined, '', 100, 123.324,
2343243243242343242354365476453654625345345, 'sdf23423dsfsdf',
'sdf2324.234dfs','234,234fsf','100,100','100.100']
_parseInt = function(i){return parseInt(i);}
_tarrout = _tarrin.map(_parseInt)
_tarrin.map(function(e,i,a){return [e, _tarrout[i]]})
Result:
//'('+_tarrin.map(function(e,i,a){return [e, _tarrout[i]]}).join('),\n(')+')'
>>
(function Number() { [native code] },NaN),
(function (){},NaN),
(false,NaN),
(,NaN),
(,NaN),
(100,100),
(123.324,123),
(2.3432432432423434e+42,2),
(sdf23423dsfsdf,NaN),
(sdf2324.234dfs,NaN),
(234,234fsf,234),
(100,100,100),
(100.100,100)
Related Performance:
Using map over for-loops:
See: What is the most efficient way of merging [1,2] and [7,8] into [[1,7], [2,8]]
Note: the base types such as false and undefined do not posess a prototypal object-hierarchy and thus do not expose a toString function. Hence these are shown as empty in the output.
As parseInt's second argument is the base/number radix, to which to convert the number to, and since map passes the index as the second argument to its argument-function, a wrapper function is used.
Along other Python-like functions, pythonic offers a zip function, with the extra benefit of returning a lazy evaluated Iterator, similar to the behaviour of its Python counterpart:
import {zip, zipLongest} from 'pythonic';
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd', 'e'];
for (const [first, second] of zip(arr1, arr2))
console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d
for (const [first, second] of zipLongest(arr1, arr2))
console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d
// first: undefined, second: e
// unzip
const [arrayFirst, arraySecond] = [...zip(...zip(arr1, arr2))];
Disclosure I'm author and maintainer of Pythonic
Python has two function to zip sequences: zip and itertools.zip_longest. An implementation in Javascript for the same functionality is this:
Implementation of Python`s zip on JS/ES6
const zip = (...arrays) => {
const length = Math.min(...arrays.map(arr => arr.length));
return Array.from({ length }, (value, index) => arrays.map((array => array[index])));
};
Results in:
console.log(zip(
[1, 2, 3, 'a'],
[667, false, -378, '337'],
[111],
[11, 221]
));
[ [ 1, 667, 111, 11 ] ]
console.log(zip(
[1, 2, 3, 'a'],
[667, false, -378, '337'],
[111, 212, 323, 433, '1111']
));
[ [ 1, 667, 111 ], [ 2, false, 212 ], [ 3, -378, 323 ], [ 'a',
'337', 433 ] ]
console.log(zip(
[1, 2, 3, 'a'],
[667, false, -378, '337'],
[111],
[]
));
[]
Implementation of Python`s zip_longest on JS/ES6
(https://docs.python.org/3.5/library/itertools.html?highlight=zip_longest#itertools.zip_longest)
const zipLongest = (placeholder = undefined, ...arrays) => {
const length = Math.max(...arrays.map(arr => arr.length));
return Array.from(
{ length }, (value, index) => arrays.map(
array => array.length - 1 >= index ? array[index] : placeholder
)
);
};
Results:
console.log(zipLongest(
undefined,
[1, 2, 3, 'a'],
[667, false, -378, '337'],
[111],
[]
));
[ [ 1, 667, 111, undefined ], [ 2, false, undefined, undefined ],
[ 3, -378, undefined, undefined ], [ 'a', '337', undefined,
undefined ] ]
console.log(zipLongest(
null,
[1, 2, 3, 'a'],
[667, false, -378, '337'],
[111],
[]
));
[ [ 1, 667, 111, null ], [ 2, false, null, null ], [ 3, -378,
null, null ], [ 'a', '337', null, null ] ]
console.log(zipLongest(
'Is None',
[1, 2, 3, 'a'],
[667, false, -378, '337'],
[111],
[]
));
[ [ 1, 667, 111, 'Is None' ], [ 2, false, 'Is None', 'Is None' ],
[ 3, -378, 'Is None', 'Is None' ], [ 'a', '337', 'Is None', 'Is
None' ] ]
You can make utility function by using ES6.
console.json = obj => console.log(JSON.stringify(obj));
const zip = (arr, ...arrs) =>
arr.map((val, i) => arrs.reduce((a, arr) => [...a, arr[i]], [val]));
// Example
const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];
console.json(zip(array1, array2)); // [[1,"a"],[2,"b"],[3,"c"]]
console.json(zip(array1, array2, array3)); // [[1,"a",4],[2,"b",5],[3,"c",6]]
However, in above solution length of the first array defines the length of the output array.
Here is the solution in which you have more control over it. It's bit complex but worth it.
function _zip(func, args) {
const iterators = args.map(arr => arr[Symbol.iterator]());
let iterateInstances = iterators.map((i) => i.next());
ret = []
while(iterateInstances[func](it => !it.done)) {
ret.push(iterateInstances.map(it => it.value));
iterateInstances = iterators.map((i) => i.next());
}
return ret;
}
const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];
const zipShort = (...args) => _zip('every', args);
const zipLong = (...args) => _zip('some', args);
console.log(zipShort(array1, array2, array3)) // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]
console.log(zipLong([1,2,3], [4,5,6, 7]))
// [
// [ 1, 4 ],
// [ 2, 5 ],
// [ 3, 6 ],
// [ undefined, 7 ]]
1. Npm Module: zip-array
I found an npm module that can be used as a javascript version of python zip:
zip-array - A javascript equivalent of Python's zip function. Merges together the values of each of the arrays.
https://www.npmjs.com/package/zip-array
2. tf.data.zip() in Tensorflow.js
Another alternate choice is for Tensorflow.js users: if you need a zip function in python to work with tensorflow datasets in Javascript, you can use tf.data.zip() in Tensorflow.js.
tf.data.zip() in Tensorflow.js documented at here
Original answer (see update below)
I modified flm's nifty answer to take an arbitrary number of arrays:
function* zip(arrays, i = 0) {
while (i<Math.min(...arrays.map(({length})=>length))) {
yield arrays.map((arr, j) => arr[j < arrays.length - 1 ? i : i++])
}
}
Updated answer
As pointed out by Tom Pohl this function can't deal with arrays with falsy values in. Here is an updated/improved version that can deal with any types and also unequal length arrays:
function* zip(arrays, i = 0) {
while (i<Math.min(...arrays.map(arr=>arr.length))) {
yield arrays.map((arr, j) => arr[j < arrays.length - 1 ? i : i++])
}
}
const arr1 = [false,0,1,2]
const arr2 = [100,null,99,98,97]
const arr3 = [7,8,undefined,"monkey","banana"]
console.log(...zip([arr1,arr2,arr3]))
Not built-in to Javascript itself. Some of the common Javascript frameworks (such as Prototype) provide an implementation, or you can write your own.
Like #Brandon, I recommend Underscore's zip function. However, it acts like zip_longest, appending undefined values as needed to return something the length of the longest input.
I used the mixin method to extend underscore with a zipShortest, which acts like Python's zip, based off of the library's own source for zip.
You can add the following to your common JS code and then call it as if it were part of underscore: _.zipShortest([1,2,3], ['a']) returns [[1, 'a']], for example.
// Underscore library addition - zip like python does, dominated by the shortest list
// The default injects undefineds to match the length of the longest list.
_.mixin({
zipShortest : function() {
var args = Array.Prototype.slice.call(arguments);
var length = _.min(_.pluck(args, 'length')); // changed max to min
var results = new Array(length);
for (var i = 0; i < length; i++) {
results[i] = _.pluck(args, "" + i);
}
return results;
}});
A variation of the lazy generator solution:
function* iter(it) {
yield* it;
}
function* zip(...its) {
its = its.map(iter);
while (true) {
let rs = its.map(it => it.next());
if (rs.some(r => r.done))
return;
yield rs.map(r => r.value);
}
}
for (let r of zip([1,2,3], [4,5,6,7], [8,9,0,11,22]))
console.log(r.join())
// the only change for "longest" is some -> every
function* zipLongest(...its) {
its = its.map(iter);
while (true) {
let rs = its.map(it => it.next());
if (rs.every(r => r.done))
return;
yield rs.map(r => r.value);
}
}
for (let r of zipLongest([1,2,3], [4,5,6,7], [8,9,0,11,22]))
console.log(r.join())
And this is the python's classic "n-group" idiom zip(*[iter(a)]*n):
triples = [...zip(...Array(3).fill(iter(a)))]
ES2020 shortest variant:
function * zip(arr1, arr2, i = 0) {
while(arr1[i] || arr2[i]) yield [arr1[i], arr2[i++]].filter(x => !!x);
}
[ ...zip(arr1, arr2) ] // result
You could reduce the array of arrays and map new array by taking the result of the index of the inner array.
var array1 = [1, 2, 3],
array2 = ['a','b','c'],
array3 = [4, 5, 6],
array = [array1, array2, array3],
transposed = array.reduce((r, a) => a.map((v, i) => (r[i] || []).concat(v)), []);
console.log(transposed);
Fun with spread.
const
transpose = (r, a) => a.map((v, i) => [...(r[i] || []), v]),
array1 = [1, 2, 3],
array2 = ['a','b','c'],
array3 = [4, 5, 6],
transposed = [array1, array2, array3].reduce(transpose, []);
console.log(transposed);
I took a run at this in pure JS wondering how the plugins posted above got the job done. Here's my result. I'll preface this by saying that I have no idea how stable this will be in IE and the like. It's just a quick mockup.
init();
function init() {
var one = [0, 1, 2, 3];
var two = [4, 5, 6, 7];
var three = [8, 9, 10, 11, 12];
var four = zip(one, two, one);
//returns array
//four = zip(one, two, three);
//returns false since three.length !== two.length
console.log(four);
}
function zip() {
for (var i = 0; i < arguments.length; i++) {
if (!arguments[i].length || !arguments.toString()) {
return false;
}
if (i >= 1) {
if (arguments[i].length !== arguments[i - 1].length) {
return false;
}
}
}
var zipped = [];
for (var j = 0; j < arguments[0].length; j++) {
var toBeZipped = [];
for (var k = 0; k < arguments.length; k++) {
toBeZipped.push(arguments[k][j]);
}
zipped.push(toBeZipped);
}
return zipped;
}
It's not bulletproof, but it's still interesting.
A generator approach to pythons zip function.
function* zip(...arrs){
for(let i = 0; i < arrs[0].length; i++){
a = arrs.map(e=>e[i])
if(a.indexOf(undefined) == -1 ){yield a }else{return undefined;}
}
}
// use as multiple iterators
for( let [a,b,c] of zip([1, 2, 3, 4], ['a', 'b', 'c', 'd'], ['hi', 'hello', 'howdy', 'how are you']) )
console.log(a,b,c)
// creating new array with the combined arrays
let outputArr = []
for( let arr of zip([1, 2, 3, 4], ['a', 'b', 'c', 'd'], ['hi', 'hello', 'howdy', 'how are you']) )
outputArr.push(arr)
I have created a simple function to do so with a option to provide an zipper function
function zip(zipper, ...arrays) {
if (zipper instanceof Array) {
arrays.unshift(zipper)
zipper = (...elements) => elements
}
const length = Math.min(...arrays.map(array => array.length))
const zipped = []
for (let i = 0; i < length; i++) {
zipped.push(zipper(...arrays.map(array => array[i])))
}
return zipped
}
https://gist.github.com/AmrIKhudair/4b740149c29c492859e00f451832975b
I'm not a javascript guy but I feel like many of these answers are trying to find the cutest and most clever solution using Array.map which is fine, but for someone like me that doesn't use javascript every day here are some alternatives that might possibly be a bit more readable.
Maybe a way to avoid some cute and clever code would be:
function zip(a,b){
// pre-allocate an array to hold the results
rval=Array(Math.max(a.length, b.length));
for(i=0; i<rval.length; i++){
rval[i]=[a[i],b[i]]
}
return rval
}
If you like generators:
function* _zip(a,b){
len = Math.max(a.length, b.length) // handle different sized arrays
for(i=0; i<len; i++) { yield [a[i],b[i]] }
}
Or if you really want to use Array.map:
function map(a,b){
x = a.length > b.length ? a : b // call map on the biggest array
return x.map((_,i)=>[a[i],b[i]])
}
As I said, I'm not an everyday javascript guy so, these aren't going to be the most elegant solutions but they are readable to me.
Below is a fast and efficient way of doing this, using iter-ops library, operator zip:
const {pipe, zip} = require('iter-ops');
const i = pipe(array1, zip(array2, array3));
console.log(...i); //=> [ 1, 'a', 4 ] [ 2, 'b', 5 ] [ 3, 'c', 6 ]
The library processes all inputs as iterables, so they are iterated over just once. And it can handle, in the same way, all types of iterable objects - Iterable, AsyncIterable, Iterator, AsyncIterator.
P.S. I'm the author of iter-ops.
The Mochikit library provides this and many other Python-like functions. developer of Mochikit is also a Python fan, so it has the general style of Python, and also the wraps the async calls in a twisted-like framework.
There is no equivalent function. If you have only a few arrays you should use a for loop to get an index and then use the index to access the arrays:
var array1 = [1, 2, 3];
var array2 = ['a','b','c'];
for (let i = 0; i < Math.min(array1.length, array2.length); i++) {
doStuff(array1[i], array2[i]);
}
You can have an inner loop over the arrays if you have more.
Here is my solution
let zip = (a, b) => (a.length < b.length
? a.map((e, i) => [e, b[i]])
: b.map((e, i) => [a[i], e]))
This shaves a line off Ddi's iterator-based answer:
function* zip(...toZip) {
const iterators = toZip.map((arg) => arg[Symbol.iterator]());
const next = () => toZip = iterators.map((iter) => iter.next());
while (next().every((item) => !item.done)) {
yield toZip.map((item) => item.value);
}
}
If you are fine with ES6:
const zip = (arr,...arrs) =>(
arr.map(
(v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))

Categories