How do I replace for loops with recursion in Javascript? - 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]));
Related
Javascript Sorting Matrix Diagonally
There is an algorithm question asking to sort a matrix diagonally. I'm not finding a solution for this quesiton. const matrix = [ [1, 3, 9, 4], [9, 5, 7, 7], [3, 6, 9, 7], [1, 2, 2, 2] ] Should output: [ [1, 9, 9, 7], [3, 5, 6, 9], [3, 4, 7, 7], [1, 2, 2, 2] ] My code so far: function diagonalSort(matrix) { const cols = matrix[0].length; const rows = matrix.length; const maxLength = Math.max(cols, rows); let temp; for (let k = 0; k <= 2 * (maxLength - 1); k++) { temp = []; for (let y = cols - 1; y >= 0; y--) { let x = k - y; // console.log(k, y, x); if (x >= 0 && x < rows) { temp.push(matrix[y][x]); } } if (temp.length > 0) { console.log(temp); } } } Which outputs this: [ 1 ] [ 3, 9 ] [ 3, 5, 9 ] [ 1, 4, 6, 7 ] [ 2, 7, 9 ] [ 2, 7 ] [ 2 ] But how to put it back together...?
This approach is also a gentle introduction to functional lenses in Javascript: const range = (lo, hi) => [...Array (hi - lo)] .map ((_, i) => i + lo) const diagonals = (rows, cols) => range (0, rows + cols - 1) .map ( d => range (Math.max (d - rows + 1, 0), Math.min (d, cols - 1) + 1) .map (x => [d - x, x]) ) const coordLens = (coords) => ({ get: (m) => coords .map (([x, y]) => m [x] [y]), set: (arr, m) => coords .reduce ( (m, [x, y], i) => {m [x] [y] = arr [i]; return m}, m .map (r => [... r]) // cheap clone ), }) const diagonalLenses = (rows, cols) => diagonals (rows, cols) .map (coordLens) const sortMatrix = (comparator) => (m) => diagonalLenses (m.length, m[0].length) .reduce ((m, lens) => lens.set (lens .get (m) .sort (comparator), m), m) const numericSorter = (a, b) => a - b console .log ( sortMatrix (numericSorter) ([ [1, 3, 9, 4], [9, 5, 7, 7], [3, 6, 9, 7], [1, 2, 2, 2] ]) .map (r => r .join (' ')) .join ('\n') ) After the minor helper function range (which works like range (3. 12) ==> [3, 4 , 5, 6, 7, 8, 9, 10, 11]) (including the start, excluding the stop) we have diagonals, which calculates the diagonals as arrays of [x, y] pairs, given the height and width of a matrix. The most important bit here is coordLens. A lens is a glorified getter/setter pair for some facet of a data structure. They turn out to be quite useful. Many of the standard introductions to them point to a single property or array index, or perhaps a nested path inside an object. These are slightly more interesting. coordLens takes an array of [x, y] coordinates and returns a lens that we can use to extract the values at those coordinates from a given matrix, or to set the values in (a copy of) a matrix from elements supplied as an array. On top of these two we build diagonalLenses, which takes the number of rows and columns and creates an array of coordinate lenses, one for each diagonal. We build the main function sortMatrix on these functions. We accept a standard comparator and return a function that takes a matrix and returns another of the same shape, with each of the southwest -> northeast diagonals sorted. We create an array of lenses using diagonalLenses. Then for each lens, we get the values, sort them and use the set method to update our cloned matrix with the sorted values. Note that there is little error-checking in any of this. The pieces will work together in the manner described here, but coordLens should, if used more generically, check that the array values it's trying to receive or to set are within bounds. Addendum - the use of lenses I originally wanted to explain the use of lenses in greater depth. But it was bedtime and I left it with just a brief description. There are many tutorials available to cover the basics. But very, very briefly, lenses are structures that focus on one aspect of a data structure. You create one by supplying a getter and a setter for your aspect, and then you can use a consistent interface to read, write, and modify data. (In the write/modify cases, this would involve creating a copy of the object. There's no mutation involved; we're not barbarians!) What I am taking advantage of here is that the lenses don't have to be as simple as tends to be shown in those tutorials. Instead of pointing to a single property of an object, they can do more. One of my favorite examples is that a weather app which -- as is only sensible -- reports temperatures in Celsius, but which has users who live in a benighted country like by own, who only know Fahrenheit. const fahrenLens = lens( obj => obj.temp * 9 / 5 + 32, (degF, obj) => ({...obj, temp: (degF - 32) * 5 / 9}) ) And it can be used like this: const nyc= {name: 'New York', lat: 40.730610, lng: -73.935242, temp: 25} view (fahrenLens, nyc) //=> 77 set (fahrenLens, 86, nyc) //=> {name: "New York", lat: 40.73061, lng: -73.935242, temp: 30} over (fahrenLens, t => t - 27, nyc) //=> {name: "New York", lat: 40.73061, lng: -73.935242, temp: 10} This uses three standard function for working with lenses, which, in our simple lens implementation might look like this: const view = (lens, o) => lens .get (o) const set = (lens, v, o) => lens .set (v, o) const over = (lens, f, o) => lens .set (f (lens .get (o)), o) In the answer above we use lenses to track multiple coordinates in an m x n grid, using this code: const coordLens = (coords) => ({ get: (m) => coords .map (([x, y]) => m [x] [y]), set: (arr, m) => coords .reduce ( (m, [x, y], i) => {m [x] [y] = arr [i]; return m}, m .map (r => [... r]) // cheap clone ), }) Given a set of coordinates, when we view a matrix through this coordinate lens, we get back an array of the values at those coordinates. When we set the matrix with an array of values through this lens, we fold the array of values into a new matrix by setting each coordinate to the corresponding array value, starting with a copy of the matrix. Using diagonalLenses, we create an array of coordinate lenses, one for each diagonal in the grid. (Here considering only on southwest -> northeast diagonals.) If we were to use our over function we could simplify further to const sortMatrix = (comparator) => (m) => diagonalLenses (m) .reduce ((m, lens) => over (lens, xs => xs.sort (comparator), m), m) And my own preference would be to introduce a sort function like const sort = (comparator) => (xs) => [...xs] .sort (comparator) which would allow us to simplify that further to const sortMatrix = (comparator) => (m) => diagonalLenses (m) .reduce ((m, lens) => over (lens, sort (comparator), m), m) Now a good question is how much this abstraction buys us. Using the same range and diagonals functions, we could write sortMatrix like this: const sortMatrix = (comparator) => (m) => diagonals (m) .reduce ( (m, diagonal) => { const vals = diagonal .map (([y, x]) => m [y] [x]) .sort (comparator) return diagonal .reduce ((m, [y, x], i) => {m [y] [x] = vals [i]; return m}, m) }, m .map (r => [...r]) ) which involves fewer lines of code than the combination of coordLens, diagLenses, and even our shortest earlier version of sortMatrix. One argument is that it adds nothing, that this version is fine. If you have to do nothing else with those diagonals, or with other collections of coordinates, this might be true. But these lenses can be very convenient. If we wanted to total up each diagonal for some reason, it's a one-liner on top of the response from diagonalLenses: const m = [ [1, 3, 9, 4], [9, 5, 7, 7], [3, 6, 9, 7], [1, 2, 2, 2] ] const myDiagonals = diagonalLenses (m .length, m [0] .length) const diagonalSums = myDiagonals .map (d => sum (view2 (d, m))) diagonalSums // => [1, 12, 17, 18, 18, 9, 2] for an obvious sum function over an array. And there are many more things we could do with them. One thing that I should make clear is that these lenses we create are not specific to a particular matrix. Our list of diagonal lenses for one m x n matrix are exactly the same for another m x n one. And the same is true for the underlying coordinate lenses. They have to do only with the shape of the matrix. We can easily imagine a sudoku solver which has a lens for each row, column, and box, which we then test for a correct solution with something like const gridCompleted = (grid) => lenses .every (lens => completeGroup (view (lens, grid)))
Here we go: function* enumerateDiagonals(xLength, yLength) { for (let y = 0; y < yLength; y++) { yield { x: 0, y: y }; } for (let x = 1; x < xLength; x++) { yield { x: x, y: yLength - 1 }; } } function sliceMatrix(matrix, diagonal) { const slice = []; for (let y = diagonal.y, x = diagonal.x; y >=0 && x<matrix[y].length ; y--, x++) { slice.push(matrix[y][x]) } return slice } function emplaceIntoMatrix(slice, matrix, diagonal) { for (let i=0; i < slice.length; i++) { matrix[diagonal.y - i][diagonal.x + i] = slice[i]; } } const matrix = [ [1, 3, 9, 4], [9, 5, 7, 7], [3, 6, 9, 7], [1, 2, 2, 2] ]; const Ylength = matrix.length const Xlength = matrix[0].length console.log(Ylength) console.log(Xlength) const sortedMatrix = [ ]; for(let i=0; i<Ylength; i++) { sortedMatrix[i] = new Array(Xlength); } for (const diagonal of enumerateDiagonals(Xlength, Ylength)) { var slice = sliceMatrix(matrix, diagonal); slice.sort(); emplaceIntoMatrix(slice, sortedMatrix, diagonal); } console.log(sortedMatrix);
const cols = matrix[0].length; const rows = matrix.length; const maxLength = Math.max(cols, rows); let tempArr = []; let result = []; for (let k = 0; k <= 2 * (maxLength - 1); k++) { let temp = []; for (let y = cols - 1; y >= 0; y--) { let x = k - y; // console.log(k, y, x); if (x >= 0 && x < rows) { temp.push(matrix[y][x]); } } if (temp.length > 0) { // console.log(temp.sort()); tempArr.push(temp.sort()); } } for (let k = 0; k <= maxLength - 1; k++) { let tempResult = []; let arr = tempArr.filter((a) => a.length > 0); // console.log(arr); for (let i = 0; i <= maxLength - 1; i++) { const length = arr[i].length; tempResult.push(arr[i][length - 1]); tempArr[tempArr.indexOf(arr[i])].pop(); } result.push(tempResult); } return result;
How to double these numbers in an array using recursion, rest/spread operators and destructuring?
I recently completed a JavaScript challenge asking to return a new array with all the values of the initial array doubled. const numbers = [1, 2, 3]; function double() { } Except that I was to include some ES6 topics of de-structuring and rest/spread operators as well as recursion. Well, I completed as best as I could to come to a solution. This was my solution: const numbers = [1, 2, 3]; function double(arr){ const doubledNumbers = []; for (var i = 0; i < arr.length; i ++){ const dubba = arr[i]; const bubba = dubba * 2; doubledNumbers.push(bubba); } return doubledNumbers; } The other requirement was to not use any array helper method (map, reduce etc), so I did not use map(), but instead a for loop. However, I could not wrap my head around implementing de-structuring or rest/spread operators, concepts I thought I knew pretty well, never-mind recursion.
Here's one possible implementation - destructure the parameter of double, taking out the first number in the array, and use rest syntax to put the rest of the numbers into another array. Then, double the rest of the array, and spread it into a new (returned) array, headed by the first number times 2: const numbers = [1, 2, 3]; function double([firstNum, ...rest]) { const restDoubled = rest.length ? double(rest) : []; return [firstNum * 2, ...restDoubled]; } console.log(double(numbers));
Here's a few alternatives - const None = Symbol("None") const double = ([ x = None, ...more ]) => x === None ? [] : [ x * 2, ...double(more) ] console.log(double([ 1, 2, 3, 4 ])) // [ 2, 4, 6, 8 ] Without destructuring - const double = (nums = [], i = 0) => i >= nums.length ? [] : [ nums[i] * 2, ...double(nums, i + 1) ] console.log(double([ 1, 2, 3, 4 ])) // [ 2, 4, 6, 8 ] Using higher-order functions (functions that accept or return other functions) - const None = Symbol("None") const identity = x => x const map = ([ x = None, ...more ], f = identity) => x === None ? [] : [ f(x), ...map(more, f) ] const double = (nums = []) => map(nums, n => n * 2) console.log(double([ 1, 2, 3, 4 ])) // [ 2, 4, 6, 8 ] Using continuation passing style - const None = Symbol("None") const identity = x => x const double = ([ x = None, ...more ], then = identity) => x === None ? then([]) : double(more, r => then([ x * 2, ...r ])) double([ 1, 2, 3, 4 ], console.log) // [ 2, 4, 6, 8 ]
Why to use recursion when we can do this using simple code as below numbers = numbers.map(x => 2*x)
My solution is below - const numbers = [1, 2, 3]; let finalResults = []; function double(numbers) { const [ number, ...rest ] = numbers; if(number === undefined) { return finalResults; } else { finalResults.push(number*2); return double([...rest]); } }
A another way: const numbers = [1, 2, 3]; const arr = []; function double([x, ...some]) { arr.push(x*2); if(!some.length) return arr; return double(some); } double(1,2,3) will return double(2,3) which in turn will return double(3) and finally double(3) is going to return array [2,4,6]
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
Comparing 2 arrays and finding equal values with Ramda
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]