Breaking an integer up into chunks, functionally - javascript

Consider the problem of decomposing milliseconds into readable time units. Imagine you had a function that did that
> breakupMillis(100000000)
Array [ 0, 40, 46, 3, 1 ]
meaning that 100 million milliseconds is 1 day, 3 hours, 46 minutes, and 40 seconds, exactly.
The function could be generalized by accepting an array of moduli, like this
> breakup(100000000, [1000, 60, 60, 24])
Array [ 0, 40, 46, 3, 1 ]
That function could be used (hypothetically) for other things:
> breakup(1000, [8, 8, 8])
Array [ 0, 5, 7, 1 ]
meaning that 1000 in decimal is 01750 in octal.
Here is the function I wrote to do this:
const breakup = (n, l) => l.map(p =>
{ const q = n % p; n = (n - q) / p; return q; }).concat(n);
This function is fine, it's even referentially transparent, but I have two, entirely esthetic, complaints.
the map. This feels like a job for reduce, though I don't see how.
rewriting the variable n. I don't like to use var at all; using a secret var makes it worse.
My question is only about the second. How do I re-write the function so it uses no variable (that actually vary)? If the map disappears, I'll take that as gravy.

Here's another way you can do it using a recursive procedure and a little helper quotrem – which given a numerator n, and a denominator d, returns [<quotient>, <remainder>]
const quotrem = (n, d) => [n / d >> 0, n % d]
const breakup = (n, [x,...xs]) => {
if (x === undefined) {
return [n]
}
else {
let [q, r] = quotrem(n, x)
return [r, ...breakup(q, xs)]
}
}
console.log(breakup(1000, [8, 8, 8]))
// [ 0, 5, 7, 1 ]
console.log(breakup(100000000, [1000, 60, 60, 24]))
// [ 0, 40, 46, 3, 1 ]
If you're not particularly comfortable with the destructured array, you can add a few more helpers (isEmpty, head, and tail) to interact with the array in a more explicit way
const isEmpty = xs => xs.length === 0
const head = xs => xs[0]
const tail = xs => xs.slice(1)
const quotrem = (n, d) => [n / d >> 0, n % d]
const breakup = (n, xs) => {
if (isEmpty(xs)) {
return [n]
}
else {
let [q, r] = quotrem(n, head(xs))
return [r, ...breakup(q, tail(xs))]
}
}
console.log(breakup(1000, [8, 8, 8]))
// [ 0, 5, 7, 1 ]
console.log(breakup(100000000, [1000, 60, 60, 24]))
// [ 0, 40, 46, 3, 1 ]

This feels like a job for reduce, though I don't see how.
Everything that iterates an array can be done with reduce :-)
We need to pass two things through (for the accumulator): the number that we still have the break, and the list of results. We can use ES6 destructuring and an array as a tuple:
function breakup(n, units) {
const [n, res] = units.reduce(([n, res], u) => {
const q = n % u;
res.push((n-q) / u);
return [q, res];
}, [n, units]);
return [n, ...res];
}
But that push is still ugly. Not only because it mutates (where we could have used concat as well), but what we really want is a function that abstracts this away. Unfortunately these don't exists in JS - we'd be looking for a scan or mapping accumulation. We could write
function breakup(n, units) {
const [rest, res] = units.mapAccum((n, u) => {
const q = n % u;
return [q, (n-q) / u];
}, [n, units]);
return [...res, rest];
}
function breakup(n, units) {
const mods = units.scan((n, u) => Math.floor(n/u), units);
return mods.map((q, i) => i<units.length ? q % units[i] : q);
}
I'll leave the (functional, efficient, readable, etc) implementation of those as an exercise to the reader.

I would suggest this code:
const breakup = (n, l) =>
l.reduce( ([n, ...res], p) => ([(n - n % p) / p, n % p, ...res]), [n])
.reverse();
// Demo call
console.log(breakup(100000000, [1000, 60, 60, 24]));

Related

How to convert an integer N to an array of Size N in Javascript? [duplicate]

I'm looking for any alternatives to the below for creating a JavaScript array containing 1 through to N where N is only known at runtime.
var foo = [];
for (var i = 1; i <= N; i++) {
foo.push(i);
}
To me it feels like there should be a way of doing this without the loop.
In ES6 using Array from() and keys() methods.
Array.from(Array(10).keys())
//=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Shorter version using spread operator.
[...Array(10).keys()]
//=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Start from 1 by passing map function to Array from(), with an object with a length property:
Array.from({length: 10}, (_, i) => i + 1)
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
You can do so:
var N = 10;
Array.apply(null, {length: N}).map(Number.call, Number)
result: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
or with random values:
Array.apply(null, {length: N}).map(Function.call, Math.random)
result: [0.7082694901619107, 0.9572225909214467, 0.8586748542729765,
0.8653848143294454, 0.008339877473190427, 0.9911756622605026, 0.8133423360995948, 0.8377588465809822, 0.5577575915958732, 0.16363654541783035]
Explanation
First, note that Number.call(undefined, N) is equivalent to Number(N), which just returns N. We'll use that fact later.
Array.apply(null, [undefined, undefined, undefined]) is equivalent to Array(undefined, undefined, undefined), which produces a three-element array and assigns undefined to each element.
How can you generalize that to N elements? Consider how Array() works, which goes something like this:
function Array() {
if ( arguments.length == 1 &&
'number' === typeof arguments[0] &&
arguments[0] >= 0 && arguments &&
arguments[0] < 1 << 32 ) {
return [ … ]; // array of length arguments[0], generated by native code
}
var a = [];
for (var i = 0; i < arguments.length; i++) {
a.push(arguments[i]);
}
return a;
}
Since ECMAScript 5, Function.prototype.apply(thisArg, argsArray) also accepts a duck-typed array-like object as its second parameter. If we invoke Array.apply(null, { length: N }), then it will execute
function Array() {
var a = [];
for (var i = 0; i < /* arguments.length = */ N; i++) {
a.push(/* arguments[i] = */ undefined);
}
return a;
}
Now we have an N-element array, with each element set to undefined. When we call .map(callback, thisArg) on it, each element will be set to the result of callback.call(thisArg, element, index, array). Therefore, [undefined, undefined, …, undefined].map(Number.call, Number) would map each element to (Number.call).call(Number, undefined, index, array), which is the same as Number.call(undefined, index, array), which, as we observed earlier, evaluates to index. That completes the array whose elements are the same as their index.
Why go through the trouble of Array.apply(null, {length: N}) instead of just Array(N)? After all, both expressions would result an an N-element array of undefined elements. The difference is that in the former expression, each element is explicitly set to undefined, whereas in the latter, each element was never set. According to the documentation of .map():
callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.
Therefore, Array(N) is insufficient; Array(N).map(Number.call, Number) would result in an uninitialized array of length N.
Compatibility
Since this technique relies on behaviour of Function.prototype.apply() specified in ECMAScript 5, it will not work in pre-ECMAScript 5 browsers such as Chrome 14 and Internet Explorer 9.
Multiple ways using ES6
Using spread operator (...) and keys method
[ ...Array(N).keys() ].map( i => i+1);
Fill/Map
Array(N).fill().map((_, i) => i+1);
Array.from
Array.from(Array(N), (_, i) => i+1)
Array.from and { length: N } hack
Array.from({ length: N }, (_, i) => i+1)
Note about generalised form
All the forms above can produce arrays initialised to pretty much any desired values by changing i+1 to expression required (e.g. i*2, -i, 1+i*2, i%2 and etc). If expression can be expressed by some function f then the first form becomes simply
[ ...Array(N).keys() ].map(f)
Examples:
Array.from({length: 5}, (v, k) => k+1);
// [1,2,3,4,5]
Since the array is initialized with undefined on each position, the value of v will be undefined
Example showcasing all the forms
let demo= (N) => {
console.log(
[ ...Array(N).keys() ].map(( i) => i+1),
Array(N).fill().map((_, i) => i+1) ,
Array.from(Array(N), (_, i) => i+1),
Array.from({ length: N }, (_, i) => i+1)
)
}
demo(5)
More generic example with custom initialiser function f i.e.
[ ...Array(N).keys() ].map((i) => f(i))
or even simpler
[ ...Array(N).keys() ].map(f)
let demo= (N,f) => {
console.log(
[ ...Array(N).keys() ].map(f),
Array(N).fill().map((_, i) => f(i)) ,
Array.from(Array(N), (_, i) => f(i)),
Array.from({ length: N }, (_, i) => f(i))
)
}
demo(5, i=>2*i+1)
If I get what you are after, you want an array of numbers 1..n that you can later loop through.
If this is all you need, can you do this instead?
var foo = new Array(45); // create an empty array with length 45
then when you want to use it... (un-optimized, just for example)
for(var i = 0; i < foo.length; i++){
document.write('Item: ' + (i + 1) + ' of ' + foo.length + '<br/>');
}
e.g. if you don't need to store anything in the array, you just need a container of the right length that you can iterate over... this might be easier.
See it in action here: http://jsfiddle.net/3kcvm/
Arrays innately manage their lengths. As they are traversed, their indexes can be held in memory and referenced at that point. If a random index needs to be known, the indexOf method can be used.
This said, for your needs you may just want to declare an array of a certain size:
var foo = new Array(N); // where N is a positive integer
/* this will create an array of size, N, primarily for memory allocation,
but does not create any defined values
foo.length // size of Array
foo[ Math.floor(foo.length/2) ] = 'value' // places value in the middle of the array
*/
ES6
Spread
Making use of the spread operator (...) and keys method, enables you to create a temporary array of size N to produce the indexes, and then a new array that can be assigned to your variable:
var foo = [ ...Array(N).keys() ];
Fill/Map
You can first create the size of the array you need, fill it with undefined and then create a new array using map, which sets each element to the index.
var foo = Array(N).fill().map((v,i)=>i);
Array.from
This should be initializing to length of size N and populating the array in one pass.
Array.from({ length: N }, (v, i) => i)
In lieu of the comments and confusion, if you really wanted to capture the values from 1..N in the above examples, there are a couple options:
if the index is available, you can simply increment it by one (e.g., ++i).
in cases where index is not used -- and possibly a more efficient way -- is to create your array but make N represent N+1, then shift off the front.
So if you desire 100 numbers:
let arr; (arr=[ ...Array(101).keys() ]).shift()
In ES6 you can do:
Array(N).fill().map((e,i)=>i+1);
http://jsbin.com/molabiluwa/edit?js,console
Edit:
Changed Array(45) to Array(N) since you've updated the question.
console.log(
Array(45).fill(0).map((e,i)=>i+1)
);
Use the very popular Underscore _.range method
// _.range([start], stop, [step])
_.range(10); // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_.range(1, 11); // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_.range(0, 30, 5); // => [0, 5, 10, 15, 20, 25]
_.range(0, -10, -1); // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
_.range(0); // => []
function range(start, end) {
var foo = [];
for (var i = start; i <= end; i++) {
foo.push(i);
}
return foo;
}
Then called by
var foo = range(1, 5);
There is no built-in way to do this in Javascript, but it's a perfectly valid utility function to create if you need to do it more than once.
Edit: In my opinion, the following is a better range function. Maybe just because I'm biased by LINQ, but I think it's more useful in more cases. Your mileage may vary.
function range(start, count) {
if(arguments.length == 1) {
count = start;
start = 0;
}
var foo = [];
for (var i = 0; i < count; i++) {
foo.push(start + i);
}
return foo;
}
the fastest way to fill an Array in v8 is:
[...Array(5)].map((_,i) => i);
result will be: [0, 1, 2, 3, 4]
Performance
Today 2020.12.11 I performed tests on macOS HighSierra 10.13.6 on Chrome v87, Safari v13.1.2 and Firefox v83 for chosen solutions.
Results
For all browsers
solution O (based on while) is the fastest (except Firefox for big N - but it's fast there)
solution T is fastest on Firefox for big N
solutions M,P is fast for small N
solution V (lodash) is fast for big N
solution W,X are slow for small N
solution F is slow
Details
I perform 2 tests cases:
for small N = 10 - you can run it HERE
for big N = 1000000 - you can run it HERE
Below snippet presents all tested solutions A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
function A(N) {
return Array.from({length: N}, (_, i) => i + 1)
}
function B(N) {
return Array(N).fill().map((_, i) => i+1);
}
function C(N) {
return Array(N).join().split(',').map((_, i) => i+1 );
}
function D(N) {
return Array.from(Array(N), (_, i) => i+1)
}
function E(N) {
return Array.from({ length: N }, (_, i) => i+1)
}
function F(N) {
return Array.from({length:N}, Number.call, i => i + 1)
}
function G(N) {
return (Array(N)+'').split(',').map((_,i)=> i+1)
}
function H(N) {
return [ ...Array(N).keys() ].map( i => i+1);
}
function I(N) {
return [...Array(N).keys()].map(x => x + 1);
}
function J(N) {
return [...Array(N+1).keys()].slice(1)
}
function K(N) {
return [...Array(N).keys()].map(x => ++x);
}
function L(N) {
let arr; (arr=[ ...Array(N+1).keys() ]).shift();
return arr;
}
function M(N) {
var arr = [];
var i = 0;
while (N--) arr.push(++i);
return arr;
}
function N(N) {
var a=[],b=N;while(b--)a[b]=b+1;
return a;
}
function O(N) {
var a=Array(N),b=0;
while(b<N) a[b++]=b;
return a;
}
function P(N) {
var foo = [];
for (var i = 1; i <= N; i++) foo.push(i);
return foo;
}
function Q(N) {
for(var a=[],b=N;b--;a[b]=b+1);
return a;
}
function R(N) {
for(var i,a=[i=0];i<N;a[i++]=i);
return a;
}
function S(N) {
let foo,x;
for(foo=[x=N]; x; foo[x-1]=x--);
return foo;
}
function T(N) {
return new Uint8Array(N).map((item, i) => i + 1);
}
function U(N) {
return '_'.repeat(5).split('').map((_, i) => i + 1);
}
function V(N) {
return _.range(1, N+1);
}
function W(N) {
return [...(function*(){let i=0;while(i<N)yield ++i})()]
}
function X(N) {
function sequence(max, step = 1) {
return {
[Symbol.iterator]: function* () {
for (let i = 1; i <= max; i += step) yield i
}
}
}
return [...sequence(N)];
}
[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X].forEach(f=> {
console.log(`${f.name} ${f(5)}`);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"> </script>
This snippet only presents functions used in performance tests - it does not perform tests itself!
And here are example results for chrome
This question has a lot of complicated answers, but a simple one-liner:
[...Array(255).keys()].map(x => x + 1)
Also, although the above is short (and neat) to write, I think the following is a bit faster
(for a max length of:
127, Int8,
255, Uint8,
32,767, Int16,
65,535, Uint16,
2,147,483,647, Int32,
4,294,967,295, Uint32.
(based on the max integer values), also here's more on Typed Arrays):
(new Uint8Array(255)).map(($,i) => i + 1);
Although this solution is also not so ideal, because it creates two arrays, and uses the extra variable declaration "$" (not sure any way to get around that using this method). I think the following solution is the absolute fastest possible way to do this:
for(var i = 0, arr = new Uint8Array(255); i < arr.length; i++) arr[i] = i + 1;
Anytime after this statement is made, you can simple use the variable "arr" in the current scope;
If you want to make a simple function out of it (with some basic verification):
function range(min, max) {
min = min && min.constructor == Number ? min : 0;
!(max && max.constructor == Number && max > min) && // boolean statements can also be used with void return types, like a one-line if statement.
((max = min) & (min = 0)); //if there is a "max" argument specified, then first check if its a number and if its graeter than min: if so, stay the same; if not, then consider it as if there is no "max" in the first place, and "max" becomes "min" (and min becomes 0 by default)
for(var i = 0, arr = new (
max < 128 ? Int8Array :
max < 256 ? Uint8Array :
max < 32768 ? Int16Array :
max < 65536 ? Uint16Array :
max < 2147483648 ? Int32Array :
max < 4294967296 ? Uint32Array :
Array
)(max - min); i < arr.length; i++) arr[i] = i + min;
return arr;
}
//and you can loop through it easily using array methods if you want
range(1,11).forEach(x => console.log(x));
//or if you're used to pythons `for...in` you can do a similar thing with `for...of` if you want the individual values:
for(i of range(2020,2025)) console.log(i);
//or if you really want to use `for..in`, you can, but then you will only be accessing the keys:
for(k in range(25,30)) console.log(k);
console.log(
range(1,128).constructor.name,
range(200).constructor.name,
range(400,900).constructor.name,
range(33333).constructor.name,
range(823, 100000).constructor.name,
range(10,4) // when the "min" argument is greater than the "max", then it just considers it as if there is no "max", and the new max becomes "min", and "min" becomes 0, as if "max" was never even written
);
so, with the above function, the above super-slow "simple one-liner" becomes the super-fast, even-shorter:
range(1,14000);
Using ES2015/ES6 spread operator
[...Array(10)].map((_, i) => i + 1)
console.log([...Array(10)].map((_, i) => i + 1))
You can use this:
new Array(/*any number which you want*/)
.join().split(',')
.map(function(item, index){ return ++index;})
for example
new Array(10)
.join().split(',')
.map(function(item, index){ return ++index;})
will create following array:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
If you happen to be using d3.js in your app as I am, D3 provides a helper function that does this for you.
So to get an array from 0 to 4, it's as easy as:
d3.range(5)
[0, 1, 2, 3, 4]
and to get an array from 1 to 5, as you were requesting:
d3.range(1, 5+1)
[1, 2, 3, 4, 5]
Check out this tutorial for more info.
This is probably the fastest way to generate an array of numbers
Shortest
var a=[],b=N;while(b--)a[b]=b+1;
Inline
var arr=(function(a,b){while(a--)b[a]=a;return b})(10,[]);
//arr=[0,1,2,3,4,5,6,7,8,9]
If you want to start from 1
var arr=(function(a,b){while(a--)b[a]=a+1;return b})(10,[]);
//arr=[1,2,3,4,5,6,7,8,9,10]
Want a function?
function range(a,b,c){c=[];while(a--)c[a]=a+b;return c}; //length,start,placeholder
var arr=range(10,5);
//arr=[5,6,7,8,9,10,11,12,13,14]
WHY?
while is the fastest loop
Direct setting is faster than push
[] is faster than new Array(10)
it's short... look the first code. then look at all other functions in here.
If you like can't live without for
for(var a=[],b=7;b>0;a[--b]=b+1); //a=[1,2,3,4,5,6,7]
or
for(var a=[],b=7;b--;a[b]=b+1); //a=[1,2,3,4,5,6,7]
If you are using lodash, you can use _.range:
_.range([start=0], end, [step=1])
Creates an array of numbers
(positive and/or negative) progressing from start up to, but not
including, end. A step of -1 is used if a negative start is specified
without an end or step. If end is not specified, it's set to start
with start then set to 0.
Examples:
_.range(4);
// ➜ [0, 1, 2, 3]
_.range(-4);
// ➜ [0, -1, -2, -3]
_.range(1, 5);
// ➜ [1, 2, 3, 4]
_.range(0, 20, 5);
// ➜ [0, 5, 10, 15]
_.range(0, -4, -1);
// ➜ [0, -1, -2, -3]
_.range(1, 4, 0);
// ➜ [1, 1, 1]
_.range(0);
// ➜ []
the new way to filling Array is:
const array = [...Array(5).keys()]
console.log(array)
result will be: [0, 1, 2, 3, 4]
with ES6 you can do:
// `n` is the size you want to initialize your array
// `null` is what the array will be filled with (can be any other value)
Array(n).fill(null)
Very simple and easy to generate exactly 1 - N
const [, ...result] = Array(11).keys();
console.log('Result:', result);
Final Summary report .. Drrruummm Rolll -
This is the shortest code to generate an Array of size N (here 10) without using ES6. Cocco's version above is close but not the shortest.
(function(n){for(a=[];n--;a[n]=n+1);return a})(10)
But the undisputed winner of this Code golf(competition to solve a particular problem in the fewest bytes of source code) is Niko Ruotsalainen . Using Array Constructor and ES6 spread operator . (Most of the ES6 syntax is valid typeScript, but following is not. So be judicious while using it)
[...Array(10).keys()]
https://stackoverflow.com/a/49577331/8784402
With Delta
For javascript
smallest and one-liner
[...Array(N)].map((v, i) => from + i * step);
Examples and other alternatives
Array.from(Array(10).keys()).map(i => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
[...Array(10).keys()].map(i => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]
Array(10).fill(0).map((v, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
Array(10).fill().map((v, i) => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]
[...Array(10)].map((v, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
Range Function
const range = (from, to, step) =>
[...Array(Math.floor((to - from) / step) + 1)].map((_, i) => from + i * step);
range(0, 9, 2);
//=> [0, 2, 4, 6, 8]
// can also assign range function as static method in Array class (but not recommended )
Array.range = (from, to, step) =>
[...Array(Math.floor((to - from) / step) + 1)].map((_, i) => from + i * step);
Array.range(2, 10, 2);
//=> [2, 4, 6, 8, 10]
Array.range(0, 10, 1);
//=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array.range(2, 10, -1);
//=> []
Array.range(3, 0, -1);
//=> [3, 2, 1, 0]
As Iterators
class Range {
constructor(total = 0, step = 1, from = 0) {
this[Symbol.iterator] = function* () {
for (let i = 0; i < total; yield from + i++ * step) {}
};
}
}
[...new Range(5)]; // Five Elements
//=> [0, 1, 2, 3, 4]
[...new Range(5, 2)]; // Five Elements With Step 2
//=> [0, 2, 4, 6, 8]
[...new Range(5, -2, 10)]; // Five Elements With Step -2 From 10
//=>[10, 8, 6, 4, 2]
[...new Range(5, -2, -10)]; // Five Elements With Step -2 From -10
//=> [-10, -12, -14, -16, -18]
// Also works with for..of loop
for (i of new Range(5, -2, 10)) console.log(i);
// 10 8 6 4 2
As Generators Only
const Range = function* (total = 0, step = 1, from = 0) {
for (let i = 0; i < total; yield from + i++ * step) {}
};
Array.from(Range(5, -2, -10));
//=> [-10, -12, -14, -16, -18]
[...Range(5, -2, -10)]; // Five Elements With Step -2 From -10
//=> [-10, -12, -14, -16, -18]
// Also works with for..of loop
for (i of Range(5, -2, 10)) console.log(i);
// 10 8 6 4 2
// Lazy loaded way
const number0toInf = Range(Infinity);
number0toInf.next().value;
//=> 0
number0toInf.next().value;
//=> 1
// ...
From-To with steps/delta
using iterators
class Range2 {
constructor(to = 0, step = 1, from = 0) {
this[Symbol.iterator] = function* () {
let i = 0,
length = Math.floor((to - from) / step) + 1;
while (i < length) yield from + i++ * step;
};
}
}
[...new Range2(5)]; // First 5 Whole Numbers
//=> [0, 1, 2, 3, 4, 5]
[...new Range2(5, 2)]; // From 0 to 5 with step 2
//=> [0, 2, 4]
[...new Range2(5, -2, 10)]; // From 10 to 5 with step -2
//=> [10, 8, 6]
using Generators
const Range2 = function* (to = 0, step = 1, from = 0) {
let i = 0,
length = Math.floor((to - from) / step) + 1;
while (i < length) yield from + i++ * step;
};
[...Range2(5, -2, 10)]; // From 10 to 5 with step -2
//=> [10, 8, 6]
let even4to10 = Range2(10, 2, 4);
even4to10.next().value;
//=> 4
even4to10.next().value;
//=> 6
even4to10.next().value;
//=> 8
even4to10.next().value;
//=> 10
even4to10.next().value;
//=> undefined
For Typescript
class _Array<T> extends Array<T> {
static range(from: number, to: number, step: number): number[] {
return Array.from(Array(Math.floor((to - from) / step) + 1)).map(
(v, k) => from + k * step
);
}
}
_Array.range(0, 9, 1);
Solution for empty array and with just number in array
const arrayOne = new Array(10);
console.log(arrayOne);
const arrayTwo = [...Array(10).keys()];
console.log(arrayTwo);
var arrayThree = Array.from(Array(10).keys());
console.log(arrayThree);
const arrayStartWithOne = Array.from(Array(10).keys(), item => item + 1);
console.log(arrayStartWithOne)
✅ Simply, this worked for me:
[...Array(5)].map(...)
There is another way in ES6, using Array.from which takes 2 arguments, the first is an arrayLike (in this case an object with length property), and the second is a mapping function (in this case we map the item to its index)
Array.from({length:10}, (v,i) => i)
this is shorter and can be used for other sequences like generating even numbers
Array.from({length:10}, (v,i) => i*2)
Also this has better performance than most other ways because it only loops once through the array.
Check the snippit for some comparisons
// open the dev console to see results
count = 100000
console.time("from object")
for (let i = 0; i<count; i++) {
range = Array.from({length:10}, (v,i) => i )
}
console.timeEnd("from object")
console.time("from keys")
for (let i =0; i<count; i++) {
range = Array.from(Array(10).keys())
}
console.timeEnd("from keys")
console.time("apply")
for (let i = 0; i<count; i++) {
range = Array.apply(null, { length: 10 }).map(function(element, index) { return index; })
}
console.timeEnd("apply")
Fast
This solution is probably fastest it is inspired by lodash _.range function (but my is simpler and faster)
let N=10, i=0, a=Array(N);
while(i<N) a[i++]=i;
console.log(a);
Performance advantages over current (2020.12.11) existing answers based on while/for
memory is allocated once at the beginning by a=Array(N)
increasing index i++ is used - which looks is about 30% faster than decreasing index i-- (probably because CPU cache memory faster in forward direction)
Speed tests with more than 20 other solutions was conducted in this answer
Using new Array methods and => function syntax from ES6 standard (only Firefox at the time of writing).
By filling holes with undefined:
Array(N).fill().map((_, i) => i + 1);
Array.from turns "holes" into undefined so Array.map works as expected:
Array.from(Array(5)).map((_, i) => i + 1)
In ES6:
Array.from({length: 1000}, (_, i) => i).slice(1);
or better yet (without the extra variable _ and without the extra slice call):
Array.from({length:1000}, Number.call, i => i + 1)
Or for slightly faster results, you can use Uint8Array, if your list is shorter than 256 results (or you can use the other Uint lists depending on how short the list is, like Uint16 for a max number of 65535, or Uint32 for a max of 4294967295 etc. Officially, these typed arrays were only added in ES6 though). For example:
Uint8Array.from({length:10}, Number.call, i => i + 1)
ES5:
Array.apply(0, {length: 1000}).map(function(){return arguments[1]+1});
Alternatively, in ES5, for the map function (like second parameter to the Array.from function in ES6 above), you can use Number.call
Array.apply(0,{length:1000}).map(Number.call,Number).slice(1)
Or, if you're against the .slice here also, you can do the ES5 equivalent of the above (from ES6), like:
Array.apply(0,{length:1000}).map(Number.call, Function("i","return i+1"))
Array(...Array(9)).map((_, i) => i);
console.log(Array(...Array(9)).map((_, i) => i))
for(var i,a=[i=0];i<10;a[i++]=i);
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
It seems the only flavor not currently in this rather complete list of answers is one featuring a generator; so to remedy that:
const gen = N => [...(function*(){let i=0;while(i<N)yield i++})()]
which can be used thus:
gen(4) // [0,1,2,3]
The nice thing about this is you don't just have to increment... To take inspiration from the answer #igor-shubin gave, you could create an array of randoms very easily:
const gen = N => [...(function*(){let i=0;
while(i++<N) yield Math.random()
})()]
And rather than something lengthy operationally expensive like:
const slow = N => new Array(N).join().split(',').map((e,i)=>i*5)
// [0,5,10,15,...]
you could instead do:
const fast = N => [...(function*(){let i=0;while(i++<N)yield i*5})()]

How to un-nest this recursive algorithm in javascript?

In the game idle heroes, demon bells can be level 1,2,3,4. A level 4 is made of 4 level 1, a level 3 is made of 3 level 1 and so on.
I want to find all arrangements of db given a fixed number. I made this recursive algorithm in javascript:
Closer with a more simplified approach:
function findDB(numDB, arr) {
console.log("findDB"+numDB);
if (numDB == 1) {
console.log("HERE2");
return 1;
} else {
for (let i = 1; i < numDB; i++) {
console.log("FOR"+i);
console.log("COND" +(numDB + (numDB-i)));
if((numDB + (numDB-i)) > numDB+1)
continue;
arr= arr.concat([numDB,[i,findDB(numDB - i, arr)]]);
}
return arr;
}
}
var final = []
var y = findDB(3, final);
console.log(JSON.stringify(y));
Output:
findDB(2) CORRECT!
findDB2
FOR1
COND3
findDB1
HERE2
[2,[1,1]]
FindDB(3) is missing 1,1,1,
findDB3
FOR1
COND5
FOR2
COND4
findDB1
HERE2
[3,[2,1]]
here is intended output for input 1 through 6 (algo needs to scale for any number input)
/1/ (1)
/2/ (2),
(1,1)
/3/ (3),
(2,1),
(1,1,1)
/4/ (4),
(3,1),
(2,2),(2,1,1),
(1,1,1,1)
/5/ (4,1),
(3,2),(3,1,1),
(2,2,1),(2,1,1,1),
(1,1,1,1,1)
/6/ (4,2),(4,1,1),
(3,3),(3,2,1),(3,1,1,1),
(2,2,2),(2,2,1,1),(2,1,1,1,1)
(1,1,1,1,1,1)
This is called the partitions of a number, and is a well-known problem. I'm sure computer scientists have more efficient algorithms than this, but a naive recursive version might look like this:
const partitions = (n, m = n) =>
m > n
? partitions (n, n)
: m == 1
? [Array (n) .fill (1)]
: m < 1
? [[]]
: [
... partitions (n - m, m) .map (p => [m, ...p]),
... partitions (n, m - 1)
];
[1, 2, 3, 4, 5, 6] .forEach ((n) => console .log (`${n}: ${JSON .stringify (partitions (n))}`))
And if you're worried about the default parameter (there sometimes are good reasons to worry), then you can just make this a helper function and wrap it in a public function like this:
const _partitions = (n, m) =>
m > n
? _partitions (n, n)
: m == 1
? [Array (n) .fill (1)]
: m < 1
? [[]]
: [
... _partitions (n - m, m) .map (p => [m, ...p]),
... _partitions (n, m - 1)
];
const partitions = (n) => _partitions (n, n);
[1, 2, 3, 4, 5, 6] .forEach ((n) => console .log (`${n}: ${JSON .stringify (partitions (n))}`))
in either case, n is the integer we're summing to, and m is the maximum integer we can use. If m is too large, we simply call again with an appropriate m. If it equals 1, then we can only have an array of n 1's. If m reaches zero, then we have only the empty partition. Finally, we have two recursive cases to combine: When we choose to use that maximum number, we recur with the remainder and that maximum, prepending the maximum to each result. And when we don't use the maximum, we recur with the same target value and a decremented maximum.
I feel as though this has too many cases, but I don't see immediately how to combine them.
The time is exponential, and will be in any case, because the result is exponential in the size of n. If we added memoization, we could really speed this up, but I leave that as an exercise.
Update
I was bothered by those extra cases, and found an Erlang answer that showed a simpler version. Converted to JS, it might look like this:
const countdown = (n) => n > 0 ? [n , ...countdown (n - 1)] : []
const _partitions = (n, m) =>
n < 0
? []
: n == 0
? [[]]
: countdown (m) .flatMap (x => _partitions (n - x, x) .map (p => [x, ...p]))
We have a quick helper, countdown to turn, say 5 into [5, 4, 3, 2, 1]. The main function has two base cases, an empty result if n is negative and a result containing only the empty partition if n is zero. Otherwise, we countdown the possibilities for the maximum value in a single partition, and recur on the partitions for the target less than this new maximum, adding the maximum value to the front of each.
This should have similar performance characteristics as the above, but it somewhat simpler.
Here is a recursive function that produces the results you want. It attempts to break down the input (numDB) into parts up to the maximum number (maxDB, which defaults to 4). It does this by taking the numbers from numDB down to 1 and adding all the possible results from a recursive call to that number, noting that the value of maxDB has to change to be no more than the first number.
const findDB = function(numDB, maxDB = 4) {
if (numDB == 0) return [ [] ];
let result = [];
let thisDB = Math.min(numDB, maxDB);
for (let i = thisDB; i > 0; i--) {
findDB(numDB - i, Math.min(i, thisDB)).forEach(n => {
result.push([i].concat(n));
});
}
return result;
}
;
[6, 5, 4, 3, 2, 1].forEach((i) => console.log(JSON.stringify(findDB(i))))
.as-console-wrapper {
min-height: 100% !important;
}
I've written the above function in the style in your question, with the use of various ES6 Array methods it can be simplified:
const DBlist = (n) => [...Array(n).keys()].map(k => n - k)
const findDB = (numDB, maxDB = 4) => {
if (numDB == 0) return [ [] ];
const thisDB = Math.min(numDB, maxDB);
return DBlist(thisDB).flatMap((i) => findDB(numDB - i, Math.min(i, thisDB)).map(a => [i, ...a]))
}
DBlist(6).forEach((n) => console.log(JSON.stringify(findDB(n))))
.as-console-wrapper {
min-height: 100% !important;
}

Recursive forEach() in javascript

I have a bunch of arrays of numbers for example:
let a = [1,2,3];
let b = [4,5,7];
let c = [11,13,17]
I want to create a function that tells me what combination(s) of numbers in the arrays multiplied by numbers in a different array result to a certain number. Below is the code I have so far:
// to get which combination of numbers result to 165
a.forEach(x=>{
b.forEach(y=>{
c.forEach(z=>{
if(x*y*z==165){
console.log(x,y,z)
}
})
})
})
I want to create a function that can be fed an arrays of arrays of numbers such as: [[1,2],[2,5,],[7,11],[13,17]] and give back the combination(s) of numbers that, when multiplied, can result to a certain number.
Here's a recursive function that takes an input number and array of arrays of numbers. It processes through the first array of numbers, finding any that are potential divisors, and if it does, recursively calling itself with the divided input and the balance of the array to see if there are any divisors from that pair of values. If so, they are appended to the current divisor to produce the result:
const findDivisors = (num, arrs) => {
let result = [];
if (arrs.length == 1) {
return arrs[0].filter(n => n == num);
}
arrs[0].forEach(n => {
if (num % n === 0) {
findDivisors(num / n, arrs.slice(1)).forEach(r => result.push([n].concat(r)));
}
});
return result;
}
let a = [1, 2, 3];
let b = [4, 5, 7];
let c = [11, 13, 17];
console.log(findDivisors(165, [a, b, c]));
console.log(findDivisors(136, [a, b, c]));
This expresses the same algorithm from Nick's answer, but written in a very different style:
const findDivisors = (t, [ns, ...nss]) =>
nss .length == 0
? ns .filter (n => n == t) .map (n => [n])
: ns .flatMap (n => t % n == 0 ? findDivisors (t / n, nss) .map (ns => [n, ...ns]) : [])
const ns = [
[1, 2, 3, 5, 6, 9],
[2, 3, 4, 8, 12],
[3, 5, 9, 12, 16, 18]
]
console .log (findDivisors (100, ns))
console .log (findDivisors (144, ns))
console .log (findDivisors (240, ns))
.as-console-wrapper {max-height: 100% !important; top: 0}
I prefer to work with expressions rather than statements. But as far as I see, it offers no advantages or disadvantages over Nick's answer except for questions of style.
It might be better to handle the empty array possibility too, which might look like:
const findDivisors = (t, [ns, ...nss]) =>
ns == undefined
? []
: nss .length == 0
? ns .filter (n => n == t) .map (n => [n])
: ns .flatMap (n => t % n == 0 ? findDivisors (t / n, nss) .map (ns => [n, ...ns]) : [])

Calling and Passing Nested Functions in JavaScript

I have a function that returns the LCM of a range of numbers. It works great, but this has a function inside of a function inside of a function. My question is why can I not simplify the nested smallestCommon() by removing scm() from inside of it? Why does this particular solution need this if else functionality so deeply nested?
function smallestCommons(arr) {
var max = Math.max(...arr);
var min = Math.min(...arr);
var candidate = max;
var smallestCommon = function(low, high) {
// inner function to use 'high' variable
function scm(l, h) {
if (h % l === 0) {
return h;
} else {
return scm(l, h + high);
}
}
return scm(low, high);
};
for (var i = min; i <= max; i += 1) {
candidate = smallestCommon(i, candidate);
}
return candidate;
}
smallestCommons([5, 1]); // should return 60
smallestCommons([1, 13]); // should return 360360
smallestCommons([23, 18]); //should return 6056820
Having inner functions isn't necessarily bad. Sometimes you want to reduce some local duplication, but don't want to also create a new top-level function. Used carefully, they can clean up code.
In your particular case though, it isn't necessary to have it nested. You can just pass in the high variable as a third parameter:
function scm(l, h, high) {
if (h % l === 0) {
return h;
} else {
return scm(l, h + high, high);
}
}
function smallestCommon (low, high) {
return scm(low, high, high);
}
This is actually a fairly common pattern when dealing with recursion: have a recursive function, and a helper function that simplifies calling the recursive function. In functional languages where recursion is common though, it's actually commonplace to have a local recursive function like you had originally (often called something like go).
And it's a shame that JS doesn't have a range function. smallestCommons is basically just a reduction over the range [min,max]. Between the lack of range function and smallestCommon having it's arguments in the wrong order though, converting your code to use reduce unfortunately got a little bulky:
function smallestCommons(arr) {
var max = Math.max(...arr);
var min = Math.min(...arr);
return Array.from(new Array(max - min), (x,i) => i + min)
.reduce((acc, i) => smallestCommon(i, acc), max);
}
I'll suggest breaking this down into smaller parts. Instead of one function that's complicated and difficult to debug, you'll have lots of functions that are easy to write and debug. Smaller functions are easier to test and reuse in other parts of your program too -
const gcd = (m, n) =>
n === 0
? m
: gcd (n, m % n)
const lcm = (m, n) =>
Math.abs (m * n) / gcd (m, n)
console.log
( lcm (1, 5) // 5
, lcm (3, 4) // 12
, lcm (23, 18) // 414
)
Now we have minmax. Unique to this implementation is that it finds the min and the max using only a single traversal of the input array -
const None =
Symbol ()
const list = (...values) =>
values
const minmax = ([ x = None, ...rest ], then = list) =>
x === None
? then (Infinity, -Infinity)
: minmax
( rest
, (min, max) =>
then
( Math.min (min, x)
, Math.max (max, x)
)
)
console.log
( minmax ([ 3, 4, 2, 5, 1 ]) // [ 1, 5 ]
, minmax ([ 1, 5 ]) // [ 1, 5 ]
, minmax ([ 5, 1 ]) // [ 1, 5 ]
, minmax ([ 9 ]) // [ 9, 9 ]
, minmax ([]) // [ Infinity, -Infinity ]
)
By default minmax returns a list of the min and max values. We can plug the min and max directly into a range function, which might be more useful to us, as we'll see later -
const range = (m, n) =>
m > n
? []
: [ m, ... range (m + 1, n ) ]
console.log
( minmax ([ 3, 4, 2, 5, 1 ], range) // [ 1, 2, 3, 4, 5 ]
, minmax ([ 1, 5 ], range) // [ 1, 2, 3, 4, 5 ]
, minmax ([ 5, 1 ], range) // [ 1, 2, 3, 4, 5 ]
, minmax ([ 9 ], range) // [ 9 ]
, minmax ([], range) // []
)
Now that we can find the min and max of the input, create a range between the two, all that's left is calculating the lcm of the values in the range. Taking many values and reducing them to a single value is done with .reduce -
console.log
( minmax ([1, 5], range) .reduce (lcm, 1) // 60
, minmax ([5, 1], range) .reduce (lcm, 1) // 60
)
Wrap that up in a function and we're done -
const smallestCommons = xs =>
minmax (xs, range) .reduce (lcm, 1)
console.log
( smallestCommons ([ 5, 1 ]) // 60
, smallestCommons ([ 1, 13 ]) // 360360
, smallestCommons ([ 23, 18 ]) // 6056820
)
Verify the result in your own browser below -
const gcd = (m, n) =>
n === 0
? m
: gcd (n, m % n)
const lcm = (m, n) =>
Math.abs (m * n) / gcd (m, n)
const None =
Symbol ()
const list = (...values) =>
values
const minmax = ([ x = None, ...xs ], then = list) =>
x === None
? then (Infinity, -Infinity)
: minmax
( xs
, (min, max) =>
then
( Math.min (min, x)
, Math.max (max, x)
)
)
const range = (m, n) =>
m > n
? []
: [ m, ... range (m + 1, n ) ]
const smallestCommons = xs =>
minmax (xs, range) .reduce (lcm, 1)
console.log
( smallestCommons ([ 5, 1 ]) // 60
, smallestCommons ([ 1, 13 ]) // 360360
, smallestCommons ([ 23, 18 ]) // 6056820
)
extra
Above, minmax is defined using continuation passing style. We save extra computation by passing range as the specified continuation (then). However, we can call minmax without specifying a continuation and spread (...) the intermediate value to range. Either program might make more sense to you. The result is the same -
const smallestCommons = xs =>
range (...minmax (xs)) .reduce (lcm, 1)
console.log
( smallestCommons ([ 5, 1 ]) // 60
, smallestCommons ([ 1, 13 ]) // 360360
, smallestCommons ([ 23, 18 ]) // 6056820
)
same pig, different farm
smallestCommons is basically just a reduction over the range [min,max] - #Carcigenicate
Hopefully it helps to see the same result from multiple approaches :D
sourface
Some people will despise the above implementation of minmax regardless of its elegance and flexibility. Now that we maybe understand reducing a little better, we can show how minmax might be better implemented using direct style -
const minmax = xs =>
xs .reduce
( ([ min, max ], x) =>
[ Math.min (min, x)
, Math.max (max, x)
]
, [ Infinity, -Infinity ]
)
const smallestCommons = xs =>
range (...minmax (xs)) .reduce (lcm, 1) // direct style now required here
You can unnest it, if you rewrite the inner functions in such a way, that they don't reference variables in their outer scope.
function scm(l, h, step) {
if (h % l === 0) {
return h;
} else {
return scm(l, h + h, step);
}
}
function smallestCommons(arr) {
var max = Math.max(...arr);
var min = Math.min(...arr);
return scm(min, max, max);
}
It might blow your stack though, but that's a different problem. If you get a RangeError, you have to rewrite scm to be loop based instead of recursive.

What is the proper/idiomatic way to filter or map based on surrounding context in functional programming?

In functional programming, filtering based on the characteristics of a single item is relatively straightforward -- e.g., filtering to find only odd numbers:
const arrayOfInfo = [1,2,3,4,5,6,8,10,11,13,15,16,17,19]
const onlyOddNumbers = arrayOfInfo.filter(function(item) {
return (item % 2 == 1) ? true : false
})
However, I'm not sure what the idiomatic way of doing things is if I need context -- in other words, knowing something about the surrounding items. For example, if I wanted to filter for only the items that were surrounded by odd numbers on either side, I could do this (I'm taking advantage of some JavaScript characteristics and not even bothering to check whether the indexes exist first):
const surroundedByOneOddNumber = arrayOfInfo.filter(function(item,index) {
const itemBefore = arrayOfInfo[index - 1]
const itemAfter = arrayOfInfo[index + 1]
return ((itemBefore % 2 == 1) && (itemAfter % 2 == 1)) ? true : false
})
This becomes more obvious as a problematic or inefficient way to write code if I wanted to find numbers surrounded by two odd numbers on each side:
const surroundedByTwoOddNumbers = arrayOfInfo.filter(function(item,index) {
const itemBefore = arrayOfInfo[index - 1]
const itemTwoBefore = arrayOfInfo[index - 2]
const itemAfter = arrayOfInfo[index + 1]
const itemTwoAfter = arrayOfInfo[index + 2]
return ((itemBefore % 2 == 1) && (itemTwoBefore % 2 == 1) && (itemAfter % 2 == 1) && (itemTwoAfter % 2 == 1)) ? true : false
})
Obviously, if I wanted to do something like find only numbers surrounded by 50 odd numbers on each side, this would be completely pointless to write code like this.
Is there a good way to address this with functional programming, or is this a case where it is better to drop down to for/while loop-style?
CodePen to play with the samples: https://codepen.io/jnpdx/pen/MvradM
You could use a closure over the count of the left and right needed odd values, then take the left and right values and check every element and return the result of the check for filtering.
A special case is the count of zero, there you need to check just the actual element.
var array = [1, 2, 3, 4, 5, 6, 8, 10, 11, 13, 15, 16, 17, 19],
odd = item => item % 2,
getOdds = count => (a, i, aa) => {
var temp = aa.slice(i - count, i).concat(aa.slice(i + 1, i + 1 + count));
return count
? temp.length === 2 * count && temp.every(odd)
: odd(a);
};
console.log(array.filter(getOdds(0)));
console.log(array.filter(getOdds(1)));
console.log(array.filter(getOdds(2)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
A smarter approach is to count contiguous odd parts and use the array for filtering.
Check if 16 is surrounded by two odd numbers
name values comment
----- --------------------------------------------------------- --------------------
array [ 1, 2, 3, 4, 5, 6, 8, 10, 11, 13, 15, 16, 17, 19]
left [ 1, 0, 1, 0, 1, 0, 0, 0, 1, 2, 3, 0, 1, 2]
right [ 1, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 2, 1]
16 item to check
3 left count >= 2
2 right count >= 2
true result for filtering
var array = [1, 2, 3, 4, 5, 6, 8, 10, 11, 13, 15, 16, 17, 19],
odd = item => item % 2,
left = array.reduce((r, a, i) => (r[i] = odd(a) ? (r[i - 1] || 0) + 1 : 0, r), []),
right = array.reduceRight((r, a, i) => (r[i] = odd(a) ? (r[i + 1] || 0) + 1 : 0, r), []),
getOdds = count => (a, i) => count
? left[i - 1] >= count && right[i + 1] >= count
: odd(a);
console.log(array.filter(getOdds(0)));
console.log(array.filter(getOdds(1)));
console.log(array.filter(getOdds(2)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
The whole idea of functional programming is to write pure functions without side-effects
Array.filter is functional because it returns a new array, without mutating the original one. You can run that method on the same array millions of times without changing it.
If your logic gets complex, the code gets complex too, there is no functional magic which solves your domain problems.
However, you could make a createFilter function, which would create your filter function based on your domain requirements like:
const createFilter = ({
before = e => true,
after = e => true
}) => (entry, idx, entries) =>
before(entries[idx - 1]) && after(entries[idx + 1])
};
}
// This will return [ 4, 4 ] I guess ;)
[1, 3, 3, 3, 2, 4, 5, 2, 4, 7].filter(createFilter({
before: (e) => e % 2 === 0,
after: (e) => e % 2 === 1,
}))
The same way you could get only values where the item before is 50 and after 100:
[50, 1, 100, 4, 50, 3, 100].filter(createFilter({
before: (e) => e === 50,
after: (e) => e === 100
})) // pretty sure the output is [1, 3]
So this way you have a reusable filterCreator, extend it to your needs ;)
Update
#Aadit M Shah yes, after reading the OP again, I came to the conclusion, that my method would still work, you just have to write your own filterCreator function. And there is nothing wrong with the Array.filter actually.
const filterBySurrounding = (n, meetCondition) => {
return (item, idx, array) => {
return n <= idx && idx + n <= array.length - 1
? array.slice(idx - n, idx).every(meetCondition) &&
array.slice(idx + 1, idx + 1 + n).every(meetCondition)
: false
}
}
const isOdd = n => n % 2 === 1
array.filter(filterBySurrounding(50, isOdd))
Here's what I'd do:
const zipperFilter = (p, xs) => {
const before = []; // elements before x
const after = [...xs]; // x followed by elements after x, shallow copy
const result = [];
while (after.length > 0) {
const x = after.shift(); // remove x; thus, after = elements after x
if (p(before, x, after)) result.push(x);
before.unshift(x); // before = x followed by elements before x
}
return result;
};
const isOdd = n => n % 2 === 1;
const surroundedByPossiblyNOddNumbers = n => (before, x, after) =>
before.slice(0, n).every(isOdd) &&
after.slice(0, n).every(isOdd);
const surroundedByStrictlyNOddNumbers = n => (before, x, after) =>
before.length >= n &&
after.length >= n &&
before.slice(0, n).every(isOdd) &&
after.slice(0, n).every(isOdd);
const xs = [1,2,3,4,5,6,8,10,11,13,15,16,17,19];
const ys = zipperFilter(surroundedByPossiblyNOddNumbers(1), xs);
const zs = zipperFilter(surroundedByPossiblyNOddNumbers(2), xs);
const as = zipperFilter(surroundedByStrictlyNOddNumbers(1), xs);
const bs = zipperFilter(surroundedByStrictlyNOddNumbers(2), xs);
console.log(JSON.stringify(ys));
console.log(JSON.stringify(zs));
console.log(JSON.stringify(as));
console.log(JSON.stringify(bs));
What is a zipperFilter? It's a context sensitive list filter function based on the zipper data structure for lists. Any time you want to do context sensitive data processing (e.g. image processing) think of zippers.
The advantages of creating a custom zipperFilter function are:
It's more efficient than using the native filter method. This is because we don't have to keep using slice to generate the before and after arrays. We keep a running copy of both and update them on every iteration.
The before array is maintained in the reverse order. Hence, lower indices always correspond to closer neighbors. This allows us to simply slice the number of closest neighbors we want.
It's readable, generic and informs the reader that the filtering is context sensitive.
Hope that helps.

Categories

Resources