Iterating over consecutive items with Underscore.js

- jgilman - Rails

Ruby Enumerable’s each_cons

Ruby’s Enumerable module has a useful method called each_cons. It’s useful when you need to perform an operation over N consecutive items. Here’s an example:

<code>a = [1,2,3,4,5,6]
a.each_cons(3) { |l| puts l.inspect }

# output
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
</code>

It’s like a moving window, N items wide, that returns each consecutive N items. I don’t use it very often but occasionally it comes in very handy.

eachCons in CoffeeScript

I needed to do something similar in a web application recently. I was hoping Underscore.js would have an easy way to do it but it doesn’t have one built in. You can build a function in Javascript to do it without too much effort. Here it is written in Coffeescript

<code># Calls function fn with consSize items from array a.
eachCons = (a, consSize, fn) ->
  for i in [0..(a.length-consSize)]
    fn(a[i..i+consSize-1])

# Sample usage
a = [1,2,3,4,5,6]
printer = (s) -> console.log(s)
eachCons(a, 3, printer)
</code>

Try this in jsfiddle

Underscore.js with eachCons mixin

That works but if you’re using underscore.js throughout your app then you’ll have two different styles of iteration functions. You can extend Underscore.js using the mixin function. The following coffeescript shows how to extend Underscore.js to support eachCons.

<code># Define a function that takes:
# obj - an object to iterate over
# consSize - the number of consecutive items to get 
# iterator - a function we'll with each subset of items
# context - becomes 'this' when in the iterator function
conser = (obj, consSize, iterator, context) ->
  for i in [0..obj.length-consSize]
    stop = i + consSize
    stop = obj.length if stop > obj.length
    slice = obj[i...stop]
    iterator.call(context, slice, i, obj)

_.mixin({"eachCons": conser})
</code>

Then we can call the eachCons underscore function like this

<code>a = [1,2,3,4,5,6]
_.eachCons(a, 3, (l) -> console.log(l) )
</code>

Try this in jsfiddle