In Progress
Unit 1, Lesson 21
In Progress

Array Product

Sometimes when we’re generating a data set, we need to get every combination of a few different sets. Like, say, one card each for 4 suits and 13 ranks. There’s an a hard way and an easy way to do this in Ruby. Guess which way we’re going to learn today?

Video transcript & code

Let's say we're assembling an array to represent a deck of playing cards. We have a simple Card struct for the individual cards.

We'll represent suit as a string, and rank as a simple number. For now we're not going to worry about displaying rank names like Ace, Jack, or Queen.

For a standard 52-card deck we need a card for each combination of suit and rank. It would be tedious to type all of these combinations out manually. What's the easiest way to automatically generate them instead?

One way might involve taking our list of suits, and performing a flat_map on it.

For each suit, we take the range of ranks from 1 to 13 and do a second map.

Inside the inner block we now have both suit and rank available. We use these to construct a Card.

flat_map concatenates the arrays resulting from the inner map, so the upshot is a single array of 52 Card objects.

Card = Struct.new(:suit, :rank)

ace_of_spades = Card["Spades", 1]

%w[Hearts Spades Clubs Diamonds].flat_map { |suit|
  (1..13).map { |rank|
    Card[suit, rank]
  }
}
# => [#<struct Card suit="Hearts", rank=1>,
#     #<struct Card suit="Hearts", rank=2>,
#     #<struct Card suit="Hearts", rank=3>,
#     #<struct Card suit="Hearts", rank=4>,
#     #<struct Card suit="Hearts", rank=5>,
#     #<struct Card suit="Hearts", rank=6>,
#     #<struct Card suit="Hearts", rank=7>,
#     #<struct Card suit="Hearts", rank=8>,
#     #<struct Card suit="Hearts", rank=9>,
#     #<struct Card suit="Hearts", rank=10>,
#     #<struct Card suit="Hearts", rank=11>,
#     #<struct Card suit="Hearts", rank=12>,
#     #<struct Card suit="Hearts", rank=13>,
#     #<struct Card suit="Spades", rank=1>,
#     #<struct Card suit="Spades", rank=2>,
#     #<struct Card suit="Spades", rank=3>,
#     #<struct Card suit="Spades", rank=4>,
#     #<struct Card suit="Spades", rank=5>,
#     #<struct Card suit="Spades", rank=6>,
#     #<struct Card suit="Spades", rank=7>,
#     #<struct Card suit="Spades", rank=8>,
#     #<struct Card suit="Spades", rank=9>,
#     #<struct Card suit="Spades", rank=10>,
#     #<struct Card suit="Spades", rank=11>,
#     #<struct Card suit="Spades", rank=12>,
#     #<struct Card suit="Spades", rank=13>,
#     #<struct Card suit="Clubs", rank=1>,
#     #<struct Card suit="Clubs", rank=2>,
#     #<struct Card suit="Clubs", rank=3>,
#     #<struct Card suit="Clubs", rank=4>,
#     #<struct Card suit="Clubs", rank=5>,
#     #<struct Card suit="Clubs", rank=6>,
#     #<struct Card suit="Clubs", rank=7>,
#     #<struct Card suit="Clubs", rank=8>,
#     #<struct Card suit="Clubs", rank=9>,
#     #<struct Card suit="Clubs", rank=10>,
#     #<struct Card suit="Clubs", rank=11>,
#     #<struct Card suit="Clubs", rank=12>,
#     #<struct Card suit="Clubs", rank=13>,
#     #<struct Card suit="Diamonds", rank=1>,
#     #<struct Card suit="Diamonds", rank=2>,
#     #<struct Card suit="Diamonds", rank=3>,
#     #<struct Card suit="Diamonds", rank=4>,
#     #<struct Card suit="Diamonds", rank=5>,
#     #<struct Card suit="Diamonds", rank=6>,
#     #<struct Card suit="Diamonds", rank=7>,
#     #<struct Card suit="Diamonds", rank=8>,
#     #<struct Card suit="Diamonds", rank=9>,
#     #<struct Card suit="Diamonds", rank=10>,
#     #<struct Card suit="Diamonds", rank=11>,
#     #<struct Card suit="Diamonds", rank=12>,
#     #<struct Card suit="Diamonds", rank=13>]

This isn't a bad approach. But there's an array method that's a little more closely suited to this particular problem.

If, instead of flat-mapping, we send the product message to the list of suits, and supply an array of ranks as the argument, we get an array of arrays back.

Each inner array contains a suit and a rank. The whole assemblage is the cross product of the two arrays. One result for every possible combination of suit and rank - 52 in all.

Card = Struct.new(:suit, :rank)

%w[Hearts Spades Clubs Diamonds]
  .product((1..13).to_a)
# => [["Hearts", 1],
#     ["Hearts", 2],
#     ["Hearts", 3],
#     ["Hearts", 4],
#     ["Hearts", 5],
#     ["Hearts", 6],
#     ["Hearts", 7],
#     ["Hearts", 8],
#     ["Hearts", 9],
#     ["Hearts", 10],
#     ["Hearts", 11],
#     ["Hearts", 12],
#     ["Hearts", 13],
#     ["Spades", 1],
#     ["Spades", 2],
#     ["Spades", 3],
#     ["Spades", 4],
#     ["Spades", 5],
#     ["Spades", 6],
#     ["Spades", 7],
#     ["Spades", 8],
#     ["Spades", 9],
#     ["Spades", 10],
#     ["Spades", 11],
#     ["Spades", 12],
#     ["Spades", 13],
#     ["Clubs", 1],
#     ["Clubs", 2],
#     ["Clubs", 3],
#     ["Clubs", 4],
#     ["Clubs", 5],
#     ["Clubs", 6],
#     ["Clubs", 7],
#     ["Clubs", 8],
#     ["Clubs", 9],
#     ["Clubs", 10],
#     ["Clubs", 11],
#     ["Clubs", 12],
#     ["Clubs", 13],
#     ["Diamonds", 1],
#     ["Diamonds", 2],
#     ["Diamonds", 3],
#     ["Diamonds", 4],
#     ["Diamonds", 5],
#     ["Diamonds", 6],
#     ["Diamonds", 7],
#     ["Diamonds", 8],
#     ["Diamonds", 9],
#     ["Diamonds", 10],
#     ["Diamonds", 11],
#     ["Diamonds", 12],
#     ["Diamonds", 13]]

From here, it's just one more step to mapping the resulting pairs into Card objects.

Card = Struct.new(:suit, :rank)

%w[Hearts Spades Clubs Diamonds]
  .product((1..13).to_a)
  .map{|suit, rank| Card[suit, rank]}
# => [#<struct Card suit="Hearts", rank=1>,
#     #<struct Card suit="Hearts", rank=2>,
#     #<struct Card suit="Hearts", rank=3>,
#     #<struct Card suit="Hearts", rank=4>,
#     #<struct Card suit="Hearts", rank=5>,
#     #<struct Card suit="Hearts", rank=6>,
#     #<struct Card suit="Hearts", rank=7>,
#     #<struct Card suit="Hearts", rank=8>,
#     #<struct Card suit="Hearts", rank=9>,
#     #<struct Card suit="Hearts", rank=10>,
#     #<struct Card suit="Hearts", rank=11>,
#     #<struct Card suit="Hearts", rank=12>,
#     #<struct Card suit="Hearts", rank=13>,
#     #<struct Card suit="Spades", rank=1>,
#     #<struct Card suit="Spades", rank=2>,
#     #<struct Card suit="Spades", rank=3>,
#     #<struct Card suit="Spades", rank=4>,
#     #<struct Card suit="Spades", rank=5>,
#     #<struct Card suit="Spades", rank=6>,
#     #<struct Card suit="Spades", rank=7>,
#     #<struct Card suit="Spades", rank=8>,
#     #<struct Card suit="Spades", rank=9>,
#     #<struct Card suit="Spades", rank=10>,
#     #<struct Card suit="Spades", rank=11>,
#     #<struct Card suit="Spades", rank=12>,
#     #<struct Card suit="Spades", rank=13>,
#     #<struct Card suit="Clubs", rank=1>,
#     #<struct Card suit="Clubs", rank=2>,
#     #<struct Card suit="Clubs", rank=3>,
#     #<struct Card suit="Clubs", rank=4>,
#     #<struct Card suit="Clubs", rank=5>,
#     #<struct Card suit="Clubs", rank=6>,
#     #<struct Card suit="Clubs", rank=7>,
#     #<struct Card suit="Clubs", rank=8>,
#     #<struct Card suit="Clubs", rank=9>,
#     #<struct Card suit="Clubs", rank=10>,
#     #<struct Card suit="Clubs", rank=11>,
#     #<struct Card suit="Clubs", rank=12>,
#     #<struct Card suit="Clubs", rank=13>,
#     #<struct Card suit="Diamonds", rank=1>,
#     #<struct Card suit="Diamonds", rank=2>,
#     #<struct Card suit="Diamonds", rank=3>,
#     #<struct Card suit="Diamonds", rank=4>,
#     #<struct Card suit="Diamonds", rank=5>,
#     #<struct Card suit="Diamonds", rank=6>,
#     #<struct Card suit="Diamonds", rank=7>,
#     #<struct Card suit="Diamonds", rank=8>,
#     #<struct Card suit="Diamonds", rank=9>,
#     #<struct Card suit="Diamonds", rank=10>,
#     #<struct Card suit="Diamonds", rank=11>,
#     #<struct Card suit="Diamonds", rank=12>,
#     #<struct Card suit="Diamonds", rank=13>]

This solution feels more declarative and intention-revealing to me. After all, a cross-product of two arrays is exactly what we were after in the first place. Our original approach implicitly produced that outcome. Whereas using the product method explicitly names what we are trying to achieve.

And that's all I wanted to show you today. Happy hacking!

Responses