Welcome to End Point’s blog

Ongoing observations by End Point people

A Product Variant Code Challenge

A while ago, I came across a cute little Ruby challenge that looked interesting:

Given an array of arrays of possible values, enumerate all combinations that can occur, preserving order. For instance:

Given: [[1,2,3], [4,5,6], [7,8,9]], calculate the same result as the code below, but do so with an arbitrary size array:

combos = []
[1,2,3].each do |v1|
  [4,5,6].each do |v2|
    [7,8,9].each do |v3|
      combos << [v1, v2, v3]

Entries can be written using one or more functions, and may optionally be written as a class extension (i.e. Array)....

And now some context to why I thought this was applicable to ecommerce: Let's imagine you have a product. Then, let's imagine that the product has variations, or variants. We'll say we have an arbitrary size array of "option types", each with an arbitrary number of items in the array. Here, we have option types of size, color, and printed logo, which yields multiple variations, variants or combinations of a single product:

Size Color Logo

And let's give a real-life example data model, this one from a previous article on Spree's product data model:

Given this context, we can say that the outer array dimension represents product option types with individual values and that this challenge is asking for a simple method to create a list of all possible variants or variations of the product. In our t-shirt example, the solution to the example is:

[["large", "blue", "twitter"],
["large", "blue", "facebook"],
["large", "blue", "tumblr"],
["large", "blue", "blogger"],
["large", "red", "twitter"],
["large", "red", "facebook"],
["large", "red", "tumblr"],
["large", "red", "blogger"],
["medium", "blue", "twitter"],
["medium", "blue", "facebook"],
["medium", "blue", "tumblr"],
["medium", "blue", "blogger"],
["medium", "red", "twitter"],
["medium", "red", "facebook"],
["medium", "red", "tumblr"],
["medium", "red", "blogger"],
["small", "blue", "twitter"],
["small", "blue", "facebook"],
["small", "blue", "tumblr"],
["small", "blue", "blogger"],
["small", "red", "twitter"],
["small", "red", "facebook"],
["small", "red", "tumblr"],
["small", "red", "blogger"]]

Unfortunately, the original contest only received 2 submissions, so I wanted to open the door here to allow people to submit more submissions in Ruby and any other language. Please include a link to the gist or code solution and in a few weeks, I'll update the post with several submissions and links to the submissions, including my own solution. It's a nice little puzzle to take on in your desired language.


Chris Statzer said...

import itertools

source = [ ['small', 'medium', 'large'],
['red', 'blue', 'green'],
['django', 'psf', 'neckbeard', 'endpoint', 'WWJD'] ]

foo = itertools.product(*source)
for f in foo:
print f

David Christensen said...

Ok, I'll be a functional languages troll^Wgroupie and display the Haskell solution:

sequence [["large","medium","small"],["red","blue"],["twitter","facebook","tumblr","blogger"]]

This built-in function would work for any number of options for any number of axes and just return a list of lists for all of the combinations of the above.

Jon Jensen said...

Steph throws down!

I had to do exactly this in Perl for Interchange's matrix options, which Mike Heins committed back in 2003:

I used simple recursion in the recurse_dim() function, but it's pretty ugly looking because it's building up the option display names and got some weird formatting.

Nowadays I would use CPAN which makes it a lot more elegant:

use Math::Cartesian::Product;
cartesian { print "@_\n" } ["large","medium","small"],["red","blue"],["twitter","facebook","tumblr","blogger"];

Shane said...

SQL excels at this kind of thing. Assuming you had tables sizes,colors,logos which held the values you could basically just say:

SELECT sizes.size,colors.color,logos.logo FROM sizes,colors,logos

Spencer said...

How about this one?

It's part of my pull request to make this a feature in spree.