4

I new to Javascript and I am looking for a cycle function. Here's Clojure's implementation I am trying to find a cycle function that infinitely loops/recurses through values of an array. I was hoping to find something like this in the underscore library, but I could not find anything suitable. Ideally I would like to use something like this:

 _.head(_.cycle([1,2,3]), 100)

This function would return an array of 100 elements:

[1,2,3,1,2,3,1,2,3,1,2,3,...]

Is there a function like this I can use in Javascript? Here's my feable attempt, but I can't seem to get it to work:

arr = [1,2,3,4,5,6,7,8,9];

var cycle = function(arr) {
  arr.forEach(function(d, i) {
    if (d === arr.length)
      return d
      d == 0
    else {return d}
  });
};

cycle(arr);

turtle
  • 7,533
  • 18
  • 68
  • 97

8 Answers8

4

You could do something like:

var cycle = function(array, count) {
    var output = [];

    for (var i = 0; i < count; i++) {
        output.push(array[i % array.length]);
    }

    return output;
}
chinabuffet
  • 5,278
  • 9
  • 40
  • 64
3

An implementation of Clojure's cycle :

function cycle(input) {
    return function (times) {
        var i = 0, output = [];
        while (i < times) {
            output.push(input[i++ % input.length]);
        }
        return output;
    };
}

Usage examples :

var chars = cycle(['a', 'b']);
chars(0) // []
chars(1) // ["a"]
chars(3) // ["a", "b", "a"]
cycle([1, 2])(3) // [1, 2, 1]

An implementation of Clojure's take :

function take(length, input) {
    return typeof input === 'function'
        ? input(length)
        : input.slice(0, length);
}

Usage examples :

take(3, [1, 2, 3, 4])  // [1, 2, 3]
take(3, cycle([1, 2])) // [1, 2, 1]

Both implementations probably do not fit exactly Clojure's versions.

1

The problem with trying to emulate purely functional in JavaScript is eagerness: JavaScript doesn't have lazy evaluation and hence you can't produce infinite arrays in JavaScript. You need to define a lazy list in JavaScript. This is how I usually do it:

function cons(head, tail) {
    return cont({
        head: head,
        tail: tail
    });
}

function cont(a) {
    return function (k) {
        return k(a);
    };
}

The cons function is similar to the cons function in LISP or the : constructor in Haskell. It takes an element and a list and returns a new list with the element inserted at the beginning of the list. The cont function creates a continuation (really useful for reifying thunks to emulate lazy evaluation).

Creating a list using cons is very simple:

var list = cons(1, cons(2, cons(3, cons(4, cons(5, null)))));

var array = [1, 2, 3, 4, 5];

The above list and array are equivalent. We can create two function to convert arrays to lists and vice-versa:

function toList(array) {
    var list = null, length = array.length;
    while (length) list = cons(array[--length], list);
    return list;
}

function toArray(list) {
    var array = [];

    while (list) {
        list = list(id);
        array = array.concat(list.head);
        list = list.tail;
    }

    return array;
}

function id(x) {
    return x;
}

Now that we have a method of implementing lazy lists in JavaScript let's create the cycle function:

function cycle(list) {
    list = list(id);
    var head = list.head;
    var tail = join(list.tail, cons(head, null));

    return function (k) {
        return k({
            head: head,
            tail: cycle(tail)
        });
    };
}

function join(xs, ys) {
    if (xs) {
        xs = xs(id);
        return cons(xs.head, join(xs.tail, ys));
    } else return ys;
}

Now you can create an infinite list as follows:

var list = cycle(toList([1,2,3]));

Let's create a take function to get the first 100 elements of the list:

function take(n, xs) {
    if (n > 0) {
        xs = xs(id);
        return cons(xs.head, take(n - 1, xs.tail));
    } else return null;
}

We can now easily get an array of 100 elements with [1,2,3] repeating:

var array = toArray(take(100, list));

Let's see if it works as expected: http://jsfiddle.net/TR9Ma/

To summarize, lazy functional programming in JavaScript is not as much fun as it is in purely functional languages like Haskell. However with a little bit of effort you can make it work.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Sounds interesting but what does "reifying thunks" means? What is a thunk? –  Jan 29 '14 at 22:40
  • What the "id" function is for? Why doing `return k(a)` instead of `return a`, since `k` simply returns `a` without any additional operation? Maybe I've missed something. –  Jan 29 '14 at 23:14
  • A thunk is a delayed computation, a feature of functional programming languages which enables lazy evaluation of expressions (i.e. expressions are only evaluated when needed). Thunks are isomorphic to parameterless closures and hence can be reified in languages which don't have lazy evaluation (e.g. JavaScript) as closures. Read the following answer for more detail: http://stackoverflow.com/a/19862784/783743. – Aadit M Shah Jan 30 '14 at 02:20
  • I'm returning `k(a)` because `k` is a continuation. Yes I could've simply returned `a` itself but then `cont` would actually be the [`const`](http://hackage.haskell.org/package/base-4.6.0.1/docs/Prelude.html#v:const) function which is not as useful as `cont`. The `cont` function degrades to the `const` function when you pass `id` as the value of `k`. Hence `cont` is far more superior to `const`. For example you could do something like `list(toArray)` instead of `toArray(list())`. Not to mention that `cont` is the [mother of all monads](http://blog.sigfpe.com/2008/12/mother-of-all-monads.html). – Aadit M Shah Jan 30 '14 at 02:30
  • Thanks for these explanations. So, a thunk is similar to a closure, it's a bit more clear for me now. Your answer appears to be very interesting to me, your vocabulary and the concepts exposed are rather originals comparing to usual contents, I personnally really appreciate (please don't get a big head!). I'd like to know how you gathered this knowledge :) –  Jan 30 '14 at 10:11
  • Wow, I've visited your links... Are you really 21 `°-°'` ? I must be too easily impressed but, I feel really small. Also, it seems that your level is too high to get the reputation it deserves :) I mean, for my part, I'm not able to vote for you currently since I can't fully figure out what you are talking about :D Well, I can no longer cheat, I'm far from being a genius :D Again, don't get a big head and keep sharing :) –  Jan 30 '14 at 10:33
  • To be honest I've always been fascinated by rigor in formal systems. It's my passion. I surmise that it's because I am a Virgo: I like things to be in order, whether it's my grammar or my deductions. I abhor chaos. How did I aggregate all my knowledge? Practice. Programming is my distraction and I adhere to it whenever I am bored. So over the years I developed a flair for writing decent programs and evolved my own style of composition. I love writing code and answering questions on StackOverflow. It's one of the few things I am good at and it makes me feel good about myself. I am here to help. – Aadit M Shah Jan 30 '14 at 12:45
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46430/discussion-between-aadit-m-shah-and-wared) – Aadit M Shah Jan 30 '14 at 12:46
  • 1
    I'm remembering our discussion because I'm currently learning a language called OCaml (I work hard to catch up, lol), and the way to build and browse a list seems to be quite close to your answer. I believe I'm not far from understanding why I upvoted! Also, I have a tab queued in my browser since a few days, an article you wrote almost a year ago, "Why Prototypal Inheritance Matters". Each time I open it I realize the amount of reading and go back quickly to SO! Don't worry, this is not your fault, I'm a professional procrastinator... That's why I'm writing this comment by the way. Haha :) –  Feb 16 '14 at 20:00
0

This function should work. You can put the mod operation to good use here.

var cycle = function(input, n) {
    var output = [];
    for (var i = 0; i < n; i++) {
        var j = i % input.length;
        output.push(input[j]);
    }
    return output;
}

Here's a working fiddle: http://jsfiddle.net/K6UhS/1/

Also, I wouldn't introduce a whole library just for this function!

ktm5124
  • 11,861
  • 21
  • 74
  • 119
0

Here is a slightly more compact version:

function cycle(arr, count) {
    for (var i = 0, out = []; i < count; i++) {
        out.push(arr[i % arr.length]);
    }

    return out;
}

And a JSFiddle (outputs the results to the console): http://jsfiddle.net/2F9hY/1/

Basically just loops through count number of times, getting the i % arr.length item and adding it to the array.

samanime
  • 25,408
  • 15
  • 90
  • 139
0

The wu library includes a cycle function which does this:

wu.cycle([ 1, 2, 3 ]).take(10).toArray() // [ 1, 2, 3, 1, 2, 3, 1, 2, 3, 1 ]

If you don't need support for iterators/streams/infinite lists, and just want a function that cycles through an array's values, lei-cycle provides a much more lightweight solution:

const Cycle = require('lei-cycle')

let c = Cycle([ 1, 2, 3 ])

console.log(c()) // 1
console.log(c()) // 2
console.log(c()) // 3
console.log(c()) // 1
// ...
chocolateboy
  • 1,693
  • 1
  • 19
  • 21
0
function cycle(array) {
    let result = [...array]
    result[Symbol.iterator] = function* () {
        while (true)
            yield* this.values()
    }
    return result
}
0
class Cycle {
  constructor(array) {
    this.array = array;
  }
  
  next () {
    var x = this.array.shift();
    this.array.push(x);
    return x;
  }
}

const cycle = new Cycle(['a','b','c']);

console.log(cycle.next()); // returns "a"
console.log(cycle.next()); // returns "b"
console.log(cycle.next()); // returns "c"
console.log(cycle.next()); // returns "a"
console.log(cycle.next()); // returns "b"
...
Adam Fuller
  • 183
  • 2
  • 9