Javascript Sorting Matrix Diagonally - javascript

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;

Related

Select and pair leftmost and rightmost elements in an array

I have an array e.g [0,1,2,3,4,5] the length will always be even.
How do I select and pair starting from the leftmost and rightmost elements all the way to the center position?
In the above array it should result [[0,5],[1,4],[2,3]]
I have tried this so far... here
const arr = [0,1,2,3,4,5]
const result = []
const possibleMatches = arr.length / 2
for (let x = 0; x < possibleMatches; x++) {
result.push([arr[x], arr[arr.length - x - 1]])
}
console.log(result)
//[ [ 0, 5 ], [ 1, 4 ], [ 2, 3 ] ]
However, I think they must be better approach than for loop? like using one line arrow function e.t.c?
You can split the array in half (with Array.splice), then map over the array and use the same logic as you did in your for loop to get the item on the "right" side:
const arr = [0, 1, 2, 3, 4, 5]
const result = arr.splice(0, arr.length / 2).map((e, i) => [e, arr[arr.length - i - 1]])
console.log(result)
You can use Array.from() to create an array with half the size of the original, and take the values from the start and end of the original array:
const arr = [0,1,2,3,4,5]
const result = Array.from(
{ length: Math.ceil(arr.length / 2) },
(_, i) => [arr[i], arr[arr.length - 1 - i]]
)
console.log(result)
You can use Array.at() (if supported) to reduce the need for length calculations:
const arr = [0,1,2,3,4,5]
const result = Array.from(
{ length: Math.ceil(arr.length / 2) },
(_, i) => [arr.at(i), arr.at(-i-1)]
)
console.log(result)
take the second half of the array, flip it and merge it with the first half.
here's the one liner.
const arr = [0,1,2,3,4,5]
const result = arr.slice(arr.length / 2 * -1).reverse().map((element, index) => [arr[index],element])
const possibleMatches = arr.length / 2
console.log(result)
Using a simple for loop iterating from both ends
const arr = [0, 1, 2, 3, 4, 5];
let res = [],
i = 0,
j = arr.length - 1;
for (; i < j; i++, j--) {
res.push([arr[i], arr[j]]);
}
console.log(JSON.stringify(res));
or while
const arr = [0, 1, 2, 3, 4, 5];
const result = [];
while (arr.length > 1) {
result.push([arr.shift(), arr.pop()]);
}
console.log(JSON.stringify(result));

Chunking an array by value with odds and evens

I'm trying to create a function that groups an array of numbers based on a length parameter. The length represents the max length of each sub-array. The code works as it is meant to for getting the sub arrays, but what I'd like to do is make it sort by odd and even.
function myFunctionA(myArr1, myVal) {
newArr = [];
for ( x = 0; x < myArr1.length; x += myVal) {
newArr.push(myArr1.slice(x, x + myVal));
}
return newArr;
}
Console.log(myfunction([1,2,3,4,5,6,7,8,9,10],3))
This returns [[1,2,3],[4,5,6],[7,8,9],[10]]
What I'd like to do is go through each sub array at a time until the sub arrays are the correct length and add any leftover values to a sub array/s
This would look like
[[1,3,5][2,4,6][7,9][8,10]]
Since arr 0 and arr 1 are the correct length that we have stated in the console.log statement, 7 8 9 and 10 are left over. But since the can't create a full sub array and they are odds and even, they form two sub arrays with a side of 2.
Other examples:
myfunction([1,2,3,4,5,6,7],2)
Should return [[1,3],[2,4],[5,7],[6]]
myfunction([1,2,3,4,5,6,7,8],1)
Should return [[1][2][3][4][5][6][7][8]]
You could take an array for collecting all odd and even values and then push the group if it has zero items. By having the wanted size, create a new array.
function chunkenator(array, size, fn) {
let groups = [],
result = [];
for (let value of array) {
const group = fn(value);
if (!groups[group]) groups[group] = [];
if (!groups[group].length) result.push(groups[group]);
groups[group].push(value);
if (groups[group].length === size) groups[group] = [];
}
return result;
}
console.log(chunkenator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3, x => x % 2));
console.log(chunkenator([1, 3, 5, 7, 8, 9, 11, 13, 15], 3, x => x % 2));
One possibility would be to first seperate the numbers into even and odd numbers and then just loop over it, pushing the numbers into a new array switching between even and odd numbers.
It's not the cleanest piece of code, but it works.
function myfunction(arr, n) {
const evenOdd = arr.reduce((acc, e) => {
const ind = +(e % 2 === 0);
acc[ind] = acc[ind] || [];
acc[ind].push(e);
return acc;
}, []);
let ind = 0, res = [[]];
while (evenOdd[0].length || evenOdd[1].length) {
for (let i = n; i--;) {
const val = evenOdd[ind].shift();
if (val) res[res.length - 1].push(val)
}
ind = (ind + 1) % 2
res.push([])
}
res.pop()
return res;
}
for (const n of [1, 2, 3]) {
console.log(n,
myfunction([1, 2, 3, 4, 5, 6, 7, 8], n)
)
}

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}))

Average of bidimensional array's columns with array.map()

I have an array that looks like this:
var array = [ [1,3,9],
[4,6,8],
[3,7,5],
[2,8,4] ];
I want to get the average number of each column, but for now I was just trying to sum them. This is my code:
var sum = function(arr) {
return arr.reduce(function(a, b) { return a + b; }, 0);
};
var tests = array.map(function(v, i) {
return sum(array.map(function(v) { return v[i]; }))
});
return tests;
The output turns the sum correctly, but it seems to be doing as many sums as there are rows (4 rows), instead of 3 corresponding to the columns. This is the output:
tests = [10, 24, 26, NULL]
Any idea why is this happening? How can I perform the calculation only for as many columns as there are instead of rows?
EDIT:
I'm using Nenad's answer which gives the correct result. But I need to implement it on Google Sheets's Script Editor, which doesn't seem to understand the shortened functions with "=>". I replaced the shortened pieces for the longer version, but I'm not getting the same result.
var array = [ [1,3,9],
[4,6,8],
[3,7,5],
[2,8,4] ];
var sums = array.reduce(function(r, e, i) {
e.forEach(function(a,j) { r[j] = (r[j] || 0) + a;
if (i == array.length-1) { r = r.map(function(el){ return el/array.length; }); }
});
return r;
}, [])
console.log(sums);
I don't see any difference between this and the shortened version, yet this one returns:
sums = [0.15625, 0.75, 1.34375];
Instead of:
sums = [2.5, 6, 6.5];
The sum is done correctly, but when I divide "el/array.length" or even "el/4", the result are these 3 weird numbers. I don't understand where are those coming from. Where did I go wrong?
You can use reduce() and forEach() to return result.
var array = [
[1, 3, 9],
[4, 6, 8],
[3, 7, 5],
[2, 8, 4]
];
var sums = array.reduce(function(r, e, i) {
e.forEach((a, j) => r[j] = (r[j] || 0) + a)
return r;
}, [])
console.log(sums)
To calculate avg for each column you can add map() on last iteration of array.
var array = [
[1, 3, 9],
[4, 6, 8],
[3, 7, 5],
[2, 8, 4]
];
var sums = array.reduce(function(r, e, i) {
e.forEach((a, j) => r[j] = (r[j] || 0) + a)
if (i == array.length - 1) r = r.map(el => el / array.length);
return r;
}, [])
console.log(sums)
To get the average of each column you'd first have to know the amount of columns. Then grab each column with map() and to sum everything with reduce()
Now we have the column, the sum and then just divide by column length.
const arrayColumn = (a, n) => a.map(x => x[n]);
const arraySum = (a) => a.reduce((b,c) => b + c);
var arr = [
[1,3,9],
[4,6,8],
[3,7,5],
[2,8,4]
];
for(i=0; i<arr[0].length; i++){
console.log(arraySum((col = arrayColumn(arr, i))) / col.length);
}
This will convert the 2d array of rows into a 2d array of columns and then maps each inner array of columns to an average. There is a little bit of boilerplate to make the inner reduce immutable you could use lodash/fp or another library to clean this up.
const array = [
[1,3,9],
[4,6,8],
[3,7,5],
[2,8,4]
];
const averageColumns = array => array.reduce((acc, row) => {
return row.reduce((accRow, col, index) => {
const cols = accRow[index] || [];
return [...accRow.slice(0, index), cols.concat(col), ...accRow.slice(index + 1)];
}, acc);
}, []).map(avg);
const avg = array => array.reduce((acc, next) => acc + next, 0) / array.length;
console.log(averageColumns(array));
map() + reduce() solution
var array = [ [1,3,9], [4,6,8], [3,7,5], [2,8,4] ];
array.map(function(item, i, arr) {
arr[i] = item.reduce((a, b) => a + b, 0) / 2;
console.log(arr[i])
});
I'm a little fix up your code
you have mistake here return sum(array.map(function(v) { return v[i]; }))
var array = [ [1,3,9],
[4,6,8],
[3,7,5],
[2,8,4] ];
function sum(arr) {
return arr.reduce(function(a, b) { return a + b; }, 0);
};
var tests = array.map(function(v, i, arr) {
return sum(arr[i])
});
tests;
You can transpose the array of arrays,
popular utility libraries (ramda for ex) have a transpose implementation,
but it's easy to implement your own:
const trans = arr => arr[0].map((_,i) => arr.map(x => x[i]))
var array = [
[1,3,9],
[4,6,8],
[3,7,5],
[2,8,4]
];
const res = trans(array)
console.log(res)
// to get the sum you can use reduce
console.log(res.map( x => x.reduce((a,b) => a + b )))
I would do as follows;
var array = [ [1,3,9],
[4,6,8],
[3,7,5],
[2,8,4] ];
result = array.map(a => a.reduce((p,c,_,a) => p + c/a.length,0));
console.log(result);
As per the comments... Yes they are right, the right solution should be through a switch of the .map() and .reduce();
var array = [ [1,3,9],
[4,6,8],
[3,7,5],
[2,8,4] ],
result = array.reduce((p,c) => p.map((n,i) => n + c[i]/array.length), Array(array[0].length).fill(0));
console.log(result);

Categories