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 ...
4
  • 1
    possible duplicate of Finding the product of a variable number of Ruby arrays Commented Jun 25, 2013 at 0:28
  • @JörgWMittag, I agree. However, I believe theTinMan's explanation to be better than the answer on the duplicate, FWIW. Commented Jun 25, 2013 at 1: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. Commented Jun 25, 2013 at 1: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. Commented Jun 25, 2013 at 4:09

1 Answer 1

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"]]
Sign up to request clarification or add additional context in comments.

2 Comments

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?
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.