I am trying to turn an array of numbers into steps of the value of the Non-Zero integer element i.e
spread([0,0,n,0,0] returns =>
[0 + n-2, 0 + n-1, n, 0 +
One fairly simple technique, if not the most efficient, is to create a square grid with a row for each value, either all zeros or descending from a single non-zero value as described and then simply add the columns.
Here is my version:
const pop = arr => arr.map(
(n, i) => n == 0
? Array(arr.length).fill(0)
: arr.map((_, j) => Math.max(n - Math.abs(j - i), 0))
).reduce((as, bs) => as.map((a, i) => a + bs[i]))
console.log(...pop([0, 0, 2, 0, 0])) //~> [0, 1, 2, 1, 0]
console.log(...pop([3, 0, 0, 0])) //~> [3, 2, 1, 0]
console.log(...pop([0, 0, 0, 3, 0, 2, 0])) //~> [0, 1, 2, 3, 3, 3, 1]
console.log(...pop([0, 0, 0, 4, 0, 0, 0])) //~> [1, 2, 3, 4, 3, 2, 1]
console.log(...pop([0, 0, 0, 0, 4, 0, 0, 3, 0])) //~> [0, 1, 2, 3, 4, 4, 4, 4, 2]
Note that the intermediate result (after the map
, before the reduce
) for that last one looks like this:
[
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 2, 3, 4, 3, 2, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 2, 3, 2],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
]
From there it's just a matter of adding the columns, a simple reduce
call.
NinaScholz's comment made me rethink a bit, and I realized that it's just as easy to do an initial reduce
rather than a map
and only create the arrays that are necessary. This change should be more efficient:
const spread = arr => arr.reduce(
(a, n, i) => n == 0
? a
: a.concat([arr.map((_, j) => Math.max(n - Math.abs(j - i), 0))]),
[]
).reduce((as, bs) => as.map((a, i) => a + bs[i]))
console.log(...spread([0, 0, 2, 0, 0])) //~> [0, 1, 2, 1, 0]
console.log(...spread([3, 0, 0, 0])) //~> [3, 2, 1, 0]
console.log(...spread([0, 0, 0, 3, 0, 2, 0])) //~> [0, 1, 2, 3, 3, 3, 1]
console.log(...spread([0, 0, 0, 4, 0, 0, 0])) //~> [1, 2, 3, 4, 3, 2, 1]
console.log(...spread([0, 0, 0, 0, 4, 0, 0, 3, 0])) //~> [0, 1, 2, 3, 4, 4, 4, 4, 2]
With this change, the intermediate result (between the two reduce
calls now) would only consist of
[
[0, 1, 2, 3, 4, 3, 2, 1, 0],
[0, 0, 0, 0, 0, 1, 2, 3, 2],
]
Seeing this again, I realize that the last change didn't go far enough. We can eliminate all the intermediate arrays except for one used as a reduce
accumulator simply by adding to the current value as we go.
Here is what I hope is my final version:
const spread = arr => arr.reduce(
(a, n, i) => n == 0
? a
: arr.map((_, j) => a[j] + Math.max(n - Math.abs(j - i), 0)),
Array(arr.length).fill(0)
)
console.log(...spread([0, 0, 2, 0, 0])) //~> [0, 1, 2, 1, 0]
console.log(...spread([3, 0, 0, 0])) //~> [3, 2, 1, 0]
console.log(...spread([0, 0, 0, 3, 0, 2, 0])) //~> [0, 1, 2, 3, 3, 3, 1]
console.log(...spread([0, 0, 0, 4, 0, 0, 0])) //~> [1, 2, 3, 4, 3, 2, 1]
console.log(...spread([0, 0, 0, 0, 4, 0, 0, 3, 0])) //~> [0, 1, 2, 3, 4, 4, 4, 4, 2
This has no intermediate data structures except for that accumulator. It does only the arithmetic necessary. AFAICT, this is as efficient as it can get, modulo working with reduce
rather than a primitive for
-loop. I'd love to know if I'm missing something, though.