Stok Footage

Continually experimenting with new ideas and techniques — Reconstructing, Developing, Modernising.

Array#product in Ruby’s core

One of the things which has delighted me about Ruby ever since I discovered it is that its core and standard libraries cover so much useful ground, and I’m usually half way through implementing something before I stop, search, and discover the functionality was in the library all the time.

Last week at work I needed to generate all the possible values of a template like foo[1-3]bar[1,4,5], giving foo1bar1, foo1bar4, … foo3bar5.

It was easy enough to divide that up into creating a printf style template and expanding the ranges, so I had format = 'foo%dbar%d' and values = [[1, 2, 3], [1, 4, 5]]. I was about to write a routine to generate the product of an arbitrary number of arrays when I stopped and searched the web, and pretty quickly was reading about Array#product:

[1] pry(main)> ? Array#product

From: array.c (C Method):
Owner: Array
Visibility: public
Signature: product(*arg1)
Number of lines: 14

Returns an array of all combinations of elements from all arrays.

The length of the returned array is the product of the length of self and
the argument arrays.

If given a block, #product will yield all combinations and return self

   [1,2,3].product([4,5])     #=> [[1,4],[1,5],[2,4],[2,5],[3,4],[3,5]]
   [1,2].product([1,2])       #=> [[1,1],[1,2],[2,1],[2,2]]
   [1,2].product([3,4],[5,6]) #=> [[1,3,5],[1,3,6],[1,4,5],[1,4,6],
                              #     [2,3,5],[2,3,6],[2,4,5],[2,4,6]]
   [1,2].product()            #=> [[1],[2]]
   [1,2].product([])          #=> []

As Array#product is a method on an Array I had to pull out the first array from values to have something to call product on, and the way I chose to do that was something like this to return the array of expansions:

  first, *rest = values
  first.product(*rest).map do |v|
    sprintf format, *v

As this was replacing a flawed implementation which generated all the combinations in memory and the typical use case has tens or hundreds of returned value this seemed to be an appropriate place to stop working.


Leave a Reply

Your email address will not be published. Required fields are marked *