31

I have an array of arrays, like so:

[['1','2'],['a','b'],['x','y']]

I need to combine those arrays into a string containing all possible combinations of all three sets, forward only. I have seen lots of examples of all possible combinations of the sets in any order, that is not what I want. For example, I do not want any of the elements in the first set to come after the second set, or any in the third set to come before the first, or second, and so on. So, for the above example, the output would be:

['1ax', '1ay', '1bx', '1by', '2ax', '2ay', '2bx', '2by']

The number of arrays, and length of each set is dynamic.

Does anybody know how to solve this in Ruby?

Travis
  • 735
  • 7
  • 12
  • possible duplicate of [Multiple iterations](http://stackoverflow.com/questions/5543896/multiple-iterations) – Andrew Grimm Apr 05 '11 at 04:49
  • See also [Creating permutations from a multi-dimensional array](http://stackoverflow.com/questions/5582481/creating-permutations-from-a-multi-dimensional-array-in-ruby) – aidan Mar 31 '16 at 06:41

3 Answers3

62

Know your Array#product:

a = [['1','2'],['a','b'],['x','y']]
a.first.product(*a[1..-1]).map(&:join)
Phrogz
  • 296,393
  • 112
  • 651
  • 745
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
  • @Travis: If this answers your question, you can tick the tick/check mark next to the question. – Andrew Grimm Mar 08 '11 at 22:44
  • It doesn't work for me : it gives me only ``['1ax', '2by']`` :/ – bfontaine Feb 24 '12 at 22:39
  • 1
    @AndrewGrimm The answer using `transpose` did not do the right thing; I've edited your answer to remove it. – Phrogz Apr 10 '12 at 19:37
  • 2
    Also note that you can do: `first, *rest = *a; combos = first.product(*rest).map(&:join)`; IMHO this extra line is a slight readability improvement over `*a[1..-1]`. – Phrogz Apr 10 '12 at 19:43
  • 1
    Good lord, thank you for this answer. I've been breaking my brain for over a day trying to build out this functionality, having no idea that Ruby already had a method to accomplish exactly what I needed. :-/ – jeffdill2 Nov 12 '19 at 19:10
10

Solved using a recursive, so-called "Dynamic Programming" approach:

  • For n-arrays, combine the entries of the first array with each result on the remaining (n-1) arrays
  • For a single array, the answer is just that array

In code:

def variations(a)
  first = a.first
  if a.length==1 then
    first
  else
    rest = variations(a[1..-1])
    first.map{ |x| rest.map{ |y| "#{x}#{y}" } }.flatten
  end
end

p variations([['1','2'],['a','b'],['x','y']])
#=> ["1ax", "1ay", "1bx", "1by", "2ax", "2ay", "2bx", "2by"]

puts variations([%w[a b],%w[M N],['-'],%w[x y z],%w[0 1 2]]).join(' ')
#=> aM-x0 aM-x1 aM-x2 aM-y0 aM-y1 aM-y2 aM-z0 aM-z1 aM-z2 aN-x0 aN-x1 aN-x2
#=> aN-y0 aN-y1 aN-y2 aN-z0 aN-z1 aN-z2 bM-x0 bM-x1 bM-x2 bM-y0 bM-y1 bM-y2
#=> bM-z0 bM-z1 bM-z2 bN-x0 bN-x1 bN-x2 bN-y0 bN-y1 bN-y2 bN-z0 bN-z1 bN-z2

You could also reverse the logic, and with care you should be able to implement this non-recursively. But the recursive answer is rather straightforward. :)

Phrogz
  • 296,393
  • 112
  • 651
  • 745
6

Pure, reduce with product:

a = [['1','2'],['a','b'],['x','y']]
a.reduce() { |acc, n| acc.product(n).map(&:flatten) }.map(&:join)
#  => ["1ax", "1ay", "1bx", "1by", "2ax", "2ay", "2bx", "2by"]
Sply Splyeff
  • 81
  • 1
  • 2