0

I have the following:

@array = [['a', 'b'], ['c', 'd'], ['e', 'f', 'g']]

I want to create an array of arrays representing the product of the above members of @array.

I have tried this:

product_array = @array[0].product(@array[1]).product(@array[2])
product_array.map! {|i| i.flatten}

Which yields:

[["a", "c", "e"],
 ["a", "c", "f"],
 ["a", "c", "g"],
 ["a", "d", "e"],
 ["a", "d", "f"],
 ["a", "d", "g"],
 ["b", "c", "e"],
 ["b", "c", "f"],
 ["b", "c", "g"],
 ["b", "d", "e"],
 ["b", "d", "f"],
 ["b", "d", "g"]]

Which is the answer I want.

My question is: What is the best Ruby way to generalize this product scheme to an @array of any reasonable length?

I'm looking for a single function that would work on:

@array = [['a', 'b'], ['e', 'f', 'g']]
@array = [['a', 'b'], ['c', 'd'], ['e', 'f', 'g']]
@array = [['a', 'b'], ['c', 'd'], ['e', 'f', 'g'], ['h']]
... many more ...
Josh Petitt
  • 9,371
  • 12
  • 56
  • 104
  • 1
    possible duplicate of [Finding the product of a variable number of Ruby arrays](http://stackoverflow.com/questions/3419952/finding-the-product-of-a-variable-number-of-ruby-arrays) – Jörg W Mittag Jun 25 '13 at 00:28
  • @JörgWMittag, I agree. However, I believe theTinMan's explanation to be better than the answer on the duplicate, FWIW. – Josh Petitt Jun 25 '13 at 01:32
  • @JörgWMittag, just realized you were the other answer :-) I also saw your explanation in the comment. I gave +1 over there too, as it was helpful but not my specific problem. – Josh Petitt Jun 25 '13 at 01:35
  • 1
    @JörgWMittag is probably right that's a duplicate, because this gets asked periodically, but that isn't a bad thing because of how SO handles duplicates. We want to point duplicate questions to one that is the real definitive answer to the question.A duplicate isn't a bad thing, even if this gets closed; Your points remain and it'll probably remain as a permanent answer. :-) That said, JörgWMittag knows his stuff, so pay attention to what he says. – the Tin Man Jun 25 '13 at 04:09

1 Answers1

2

Working through the examples:

array = [['a', 'b'], ['e', 'f', 'g']]
array.first.product(*array[1..-1]).map(&:flatten)
=> [["a", "e"], ["a", "f"], ["a", "g"], ["b", "e"], ["b", "f"], ["b", "g"]]

array = [['a', 'b'], ['c', 'd'], ['e', 'f', 'g']]
array.first.product(*array[1..-1]).map(&:flatten)
=> [["a", "c", "e"], ["a", "c", "f"], ["a", "c", "g"], ["a", "d", "e"], ["a", "d", "f"], ["a", "d", "g"], ["b", "c", "e"], ["b", "c", "f"], ["b", "c", "g"], ["b", "d", "e"], ["b", "d", "f"], ["b", "d", "g"]]

array = [['a', 'b'], ['c', 'd'], ['e', 'f', 'g'], ['h']]
array.first.product(*array[1..-1]).map(&:flatten)
=> [["a", "c", "e", "h"], ["a", "c", "f", "h"], ["a", "c", "g", "h"], ["a", "d", "e", "h"], ["a", "d", "f", "h"], ["a", "d", "g", "h"], ["b", "c", "e", "h"], ["b", "c", "f", "h"], ["b", "c", "g", "h"], ["b", "d", "e", "h"], ["b", "d", "f", "h"], ["b", "d", "g", "h"]]

Now, to 'splode that, product is the method you want. Here are the pertinent excerpts from the documentation:

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

[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]]

The * operator before *array[1..-1] is also known as "splat", as in stomping on the array and squishing its contents out. It's the opposite of when we use * with a parameter in a parameter list to a method, which probably should be called "vacuum" or "hoover" or "suck", but that last one is reserved for descriptions of my code. Its effect is hard to see in IRB because you can't use *array, it has to be used where multiple arrays are allowed, as in product(other_ary, ...). array[1..-1] would normally return an array of arrays, so *array[1..-1] results in "arrays" instead of the original "arrays of arrays". I'm sure that's confusing but you'll get it.

So, if you need a method to do that:

def foo(array)
  array.first.product(*array[1..-1]).map(&:flatten)
end

And poking at it:

foo(array)
=> [["a", "c", "e", "h"], ["a", "c", "f", "h"], ["a", "c", "g", "h"], ["a", "d", "e", "h"], ["a", "d", "f", "h"], ["a", "d", "g", "h"], ["b", "c", "e", "h"], ["b", "c", "f", "h"], ["b", "c", "g", "h"], ["b", "d", "e", "h"], ["b", "d", "f", "h"], ["b", "d", "g", "h"]]
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • Thank you very much for the answer and the very detailed example. I would give more points if I could. I was unaware of the * splat operator. If you had to give a name (other than foo), to the operation that is happening, what would you propose? I had considered monkey patching Array with #discrete_permutation as a function name for this operation? – Josh Petitt Jun 25 '13 at 00:13
  • 1
    Well, you're pretty safe to monkey patch when you give an entirely new name to the method. Really it's up to you, because it has to mean something to you, in context with your code. If `discrete_permutation` is it that's fine. I'm glad the code works for you. It's one of those cases where I think Ruby shines. – the Tin Man Jun 25 '13 at 04:03