Is there a mechanism to loop x times in ES6 (ECMAScript 6) without mutable variables?

JavascriptGeneratorEcmascript 6Ecmascript Harmony

Javascript Problem Overview


The typical way to loop x times in JavaScript is:

for (var i = 0; i < x; i++)
  doStuff(i);

But I don't want to use the ++ operator or have any mutable variables at all. So is there a way, in ES6, to loop x times another way? I love Ruby's mechanism:

x.times do |i|
  do_stuff(i)
end

Anything similar in JavaScript/ES6? I could kind of cheat and make my own generator:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}

Of course I'm still using i++. At least it's out of sight :), but I'm hoping there's a better mechanism in ES6.

Javascript Solutions


Solution 1 - Javascript

Using the ES2015 Spread operator:

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => {
  return i * 10;
});

// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);

Or if you don't need the result:

[...Array(10)].forEach((_, i) => {
  console.log(i);
});

// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));

Or using the ES2015 Array.from operator:

Array.from(...)

const res = Array.from(Array(10)).map((_, i) => {
  return i * 10;
});

// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);

Note that if you just need a string repeated you can use String.prototype.repeat.

console.log("0".repeat(10))
// 0000000000

Solution 2 - Javascript

OK!

The code below is written using ES6 syntaxes but could just as easily be written in ES5 or even less. ES6 is not a requirement to create a "mechanism to loop x times"


If you don't need the iterator in the callback, this is the most simple implementation

const times = x => f => {
  if (x > 0) {
    f()
    times (x - 1) (f)
  }
}

// use it
times (3) (() => console.log('hi'))

// or define intermediate functions for reuse
let twice = times (2)

// twice the power !
twice (() => console.log('double vision'))

If you do need the iterator, you can use a named inner function with a counter parameter to iterate for you

const times = n => f => {
  let iter = i => {
    if (i === n) return
    f (i)
    iter (i + 1)
  }
  return iter (0)
}

times (3) (i => console.log(i, 'hi'))


> Stop reading here if you don't like learning more things ...

But something should feel off about those...

  • single branch if statements are ugly — what happens on the other branch ?
  • multiple statements/expressions in the function bodies — are procedure concerns being mixed ?
  • implicitly returned undefined — indication of impure, side-effecting function

"Isn't there a better way ?"

There is. Let's first revisit our initial implementation

// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f()               // has to be side-effecting function
times (x - 1) (f)
}
}

Sure, it's simple, but notice how we just call f() and don't do anything with it. This really limits the type of function we can repeat multiple times. Even if we have the iterator available, f(i) isn't much more versatile.

What if we start with a better kind of function repetition procedure ? Maybe something that makes better use of input and output.

Generic function repetition

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// power :: Int -> Int -> Int
const power = base => exp => {
  // repeat <exp> times, <base> * <x>, starting with 1
  return repeat (exp) (x => base * x) (1)
}

console.log(power (2) (8))
// => 256

Above, we defined a generic repeat function which takes an additional input which is used to start the repeated application of a single function.

// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)

// is the same as ...
var result = f(f(f(x)))

Implementing times with repeat

Well this is easy now; almost all of the work is already done.

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// times :: Int -> (Int -> Int) -> Int 
const times = n=> f=>
  repeat (n) (i => (f(i), i + 1)) (0)

// use it
times (3) (i => console.log(i, 'hi'))

Since our function takes i as an input and returns i + 1, this effectively works as our iterator which we pass to f each time.

We've fixed our bullet list of issues too

  • No more ugly single branch if statements
  • Single-expression bodies indicate nicely separated concerns
  • No more useless, implicitly returned undefined

JavaScript comma operator, the

In case you're having trouble seeing how the last example is working, it depends on your awareness of one of JavaScript's oldest battle axes; the comma operator – in short, it evaluates expressions from left to right and returns the value of the last evaluated expression

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

In our above example, I'm using

(i => (f(i), i + 1))

which is just a succinct way of writing

(i => { f(i); return i + 1 })

Tail Call Optimisation

As sexy as the recursive implementations are, at this point it would be irresponsible for me to recommend them given that no JavaScript VM I can think of supports proper tail call elimination – babel used to transpile it, but it's been in "broken; will reimplement" status for well over a year.

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

As such, we should revisit our implementation of repeat to make it stack-safe.

The code below does use mutable variables n and x but note that all mutations are localized to the repeat function – no state changes (mutations) are visible from outside of the function

// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
  {
    let m = 0, acc = x
    while (m < n)
      (m = m + 1, acc = f (acc))
    return acc
  }

// inc :: Int -> Int
const inc = x =>
  x + 1

console.log (repeat (1e8) (inc) (0))
// 100000000

This is going to have a lot of you saying "but that's not functional !" – I know, just relax. We can implement a Clojure-style loop/recur interface for constant-space looping using pure expressions; none of that while stuff.

Here we abstract while away with our loop function – it looks for a special recur type to keep the loop running. When a non-recur type is encountered, the loop is finished and the result of the computation is returned

const recur = (...args) =>
  ({ type: recur, args })
  
const loop = f =>
  {
    let acc = f ()
    while (acc.type === recur)
      acc = f (...acc.args)
    return acc
  }

const repeat = $n => f => x =>
  loop ((n = $n, acc = x) =>
    n === 0
      ? acc
      : recur (n - 1, f (acc)))
      
const inc = x =>
  x + 1

const fibonacci = $n =>
  loop ((n = $n, a = 0, b = 1) =>
    n === 0
      ? a
      : recur (n - 1, b, a + b))
      
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100))        // 354224848179262000000

Solution 3 - Javascript

for (let i of Array(100).keys()) {
    console.log(i)
}

Solution 4 - Javascript

I think the best solution is to use let:

for (let i=0; i<100; i++) …

That will create a new (mutable) i variable for each body evaluation and assures that the i is only changed in the increment expression in that loop syntax, not from anywhere else.

> I could kind of cheat and make my own generator. At least i++ is out of sight :)

That should be enough, imo. Even in pure languages, all operations (or at least, their interpreters) are built from primitives that use mutation. As long as it is properly scoped, I cannot see what is wrong with that.

You should be fine with

function* times(n) {
  for (let i = 0; i < n; i++)
    yield i;
}
for (const i of times(5)) {
  console.log(i);
}

> But I don't want to use the ++ operator or have any mutable variables at all.

Then your only choice is to use recursion. You can define that generator function without a mutable i as well:

function* range(i, n) {
  if (i >= n) return;
  yield i;
  return yield* range(i+1, n);
}
times = (n) => range(0, n);

But that seems overkill to me and might have performance problems (as tail call elimination is not available for return yield*).

Solution 5 - Javascript

Here is another good alternative:

Array.from({ length: 3}).map(...);

Preferably, as @Dave Morse pointed out in the comments, you can also get rid of the map call, by using the second parameter of the Array.from function like so:

Array.from({ length: 3 }, () => (...))

Solution 6 - Javascript

I think it is pretty simple:

[...Array(3).keys()]

or

Array(3).fill()

Solution 7 - Javascript

const times = 4;
new Array(times).fill().map(() => console.log('test'));

This snippet will console.log test 4 times.

Solution 8 - Javascript

Answer: 09 December 2015

Personally, I found the accepted answer both concise (good) and terse (bad). Appreciate this statement might be subjective, so please read this answer and see if you agree or disagree

The example given in the question was something like Ruby's:

x.times do |i|
  do_stuff(i)
end

Expressing this in JS using below would permit:

times(x)(doStuff(i));

Here is the code:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};

That's it!

Simple example usage:

let cheer = () => console.log('Hip hip hooray!');

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!

Alternatively, following the examples of the accepted answer:

let doStuff = (i) => console.log(i, ' hi'),
  once = times(1),
  twice = times(2),
  thrice = times(3);

once(doStuff);
//0 ' hi'

twice(doStuff);
//0 ' hi'
//1 ' hi'

thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'

Side note - Defining a range function

A similar / related question, that uses fundamentally very similar code constructs, might be is there a convenient Range function in (core) JavaScript, something similar to underscore's range function.

Create an array with n numbers, starting from x

Underscore

_.range(x, x + n)

ES2015

Couple of alternatives:

Array(n).fill().map((_, i) => x + i)

Array.from(Array(n), (_, i) => x + i)

Demo using n = 10, x = 1:

> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

In a quick test I ran, with each of the above running a million times each using our solution and doStuff function, the former approach (Array(n).fill()) proved slightly faster.

Solution 9 - Javascript

I am late to the party, but since this question turns up often in search results, I would just like to add a solution that I consider to be the best in terms of readability while not being long (which is ideal for any codebase IMO). It mutates, but I'd make that tradeoff for KISS principles.

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0

Solution 10 - Javascript

Array(100).fill().map((_,i)=> console.log(i) );

This version satisifies the OP's requirement for immutability. Also consider using reduce instead of map depending on your use case.

This is also an option if you don't mind a little mutation in your prototype.

Number.prototype.times = function(f) {
   return Array(this.valueOf()).fill().map((_,i)=>f(i));
};

Now we can do this

((3).times(i=>console.log(i)));

+1 to arcseldon for the .fill suggestion.

Solution 11 - Javascript

Not something I would teach (or ever use in my code), but here's a codegolf-worthy solution without mutating a variable, no need for ES6:

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})

More of an interesting proof-of-concept thing than a useful answer, really.

Solution 12 - Javascript

If you're willing to use a library, there's also lodash _.times or underscore _.times:

_.times(x, i => {
   return doStuff(i)
})

Note this returns an array of the results, so it's really more like this ruby:

x.times.map { |i|
  doStuff(i)
}

Solution 13 - Javascript

Afaik, there is no mechanism in ES6 similar to Ruby's times method. But you can avoid mutation by using recursion:

let times = (i, cb, l = i) => {
  if (i === 0) return;

  cb(l - i);
  times(i - 1, cb, l);
}

times(5, i => doStuff(i));

Demo: http://jsbin.com/koyecovano/1/edit?js,console

Solution 14 - Javascript

In the functional paradigm repeat is usually an infinite recursive function. To use it we need either lazy evaluation or continuation passing style.

Lazy evaluated function repetition

const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

I use a thunk (a function without arguments) to achieve lazy evaluation in Javascript.

Function repetition with continuation passing style

const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

CPS is a little scary at first. However, it always follows the same pattern: The last argument is the continuation (a function), which invokes its own body: k => k(...). Please note that CPS turns the application inside out, i.e. take(8) (repeat...) becomes k(take(8)) (...) where k is the partially applied repeat.

Conclusion

By separating the repetition (repeat) from the termination condition (take) we gain flexibility - separation of concerns up to its bitter end :D

Solution 15 - Javascript

Advantages of this solution

  • Simplest to read / use (imo)
  • Return value can be used as a sum, or just ignored
  • Plain es6 version, also link to TypeScript version of the code

Disadvantages

  • Mutation. Being internal only I don't care, maybe some others will not either.

Examples and Code

times(5, 3)                       // 15    (3+3+3+3+3)

times(5, (i) => Math.pow(2,i) )   // 31    (1+2+4+8+16)

times(5, '<br/>')                 // <br/><br/><br/><br/><br/>

times(3, (i, count) => {          // name[0], name[1], name[2]
    let n = 'name[' + i + ']'
    if (i < count-1)
        n += ', '
    return n
})

function times(count, callbackOrScalar) {
    let type = typeof callbackOrScalar
    let sum
    if (type === 'number') sum = 0
    else if (type === 'string') sum = ''

    for (let j = 0; j < count; j++) {
        if (type === 'function') {
            const callback = callbackOrScalar
            const result = callback(j, count)
            if (typeof result === 'number' || typeof result === 'string')
                sum = sum === undefined ? result : sum + result
        }
        else if (type === 'number' || type === 'string') {
            const scalar = callbackOrScalar
            sum = sum === undefined ? scalar : sum + scalar
        }
    }
    return sum
}

TypeScipt version
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011

Solution 16 - Javascript

The simplest way I can think of for creating list/array within range

Array.from(Array(max-min+1), (_, index) => index+min)

Solution 17 - Javascript

addressing the functional aspect:

function times(n, f) {
    var _f = function (f) {
        var i;
        for (i = 0; i < n; i++) {
            f(i);
        }
    };
    return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
    console.log('in parts: ' + v);
});
times(6, function (v) {
    console.log('complete: ' + v);
});

Solution 18 - Javascript

Generators? Recursion? Why so much hatin' on mutatin'? ;-)

If it is acceptable as long as we "hide" it, then just accept the use of a unary operator and we can keep things simple:

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

Just like in ruby:

> (3).times(console.log)
0
1
2

Solution 19 - Javascript

I wrapped @Tieme s answer with a helper function.

In TypeScript:

export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())

Now you can run:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']

Solution 20 - Javascript

I made this:

function repeat(func, times) {
    for (var i=0; i<times; i++) {
        func(i);
    }
}

Usage:

repeat(function(i) {
	console.log("Hello, World! - "+i);
}, 5)

/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/

The i variable returns the amount of times it has looped - useful if you need to preload an x amount of images.

Solution 21 - Javascript

I am just going to put this here. If you are looking for a compact function without using Arrays and you have no issue with mutability/immutability :

var g =x=>{/*your code goes here*/x-1>0?g(x-1):null};
 

Solution 22 - Javascript

For me, this is the easiest answer to understand for many levels of developers

const times = (n, callback) => {
    while (n) {
        callback();
        n--;
    }
}

times(10, ()=> console.log('hello'))

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionat.View Question on Stackoverflow
Solution 1 - JavascriptTiemeView Answer on Stackoverflow
Solution 2 - JavascriptMulanView Answer on Stackoverflow
Solution 3 - JavascriptzerkmsView Answer on Stackoverflow
Solution 4 - JavascriptBergiView Answer on Stackoverflow
Solution 5 - JavascriptoemeraView Answer on Stackoverflow
Solution 6 - JavascriptGergely FehérváriView Answer on Stackoverflow
Solution 7 - JavascriptHossam MouradView Answer on Stackoverflow
Solution 8 - JavascriptarcseldonView Answer on Stackoverflow
Solution 9 - JavascriptJ GarciaView Answer on Stackoverflow
Solution 10 - JavascriptTomView Answer on Stackoverflow
Solution 11 - JavascriptdoldtView Answer on Stackoverflow
Solution 12 - JavascriptronenView Answer on Stackoverflow
Solution 13 - JavascriptPavloView Answer on Stackoverflow
Solution 14 - Javascriptuser6445533View Answer on Stackoverflow
Solution 15 - JavascriptwhitneylandView Answer on Stackoverflow
Solution 16 - JavascriptmesteaView Answer on Stackoverflow
Solution 17 - JavascriptNina ScholzView Answer on Stackoverflow
Solution 18 - JavascriptconnyView Answer on Stackoverflow
Solution 19 - JavascriptAndriesView Answer on Stackoverflow
Solution 20 - JavascriptNanooView Answer on Stackoverflow
Solution 21 - JavascriptVibhuView Answer on Stackoverflow
Solution 22 - Javascriptfound8View Answer on Stackoverflow