Skip to content

Instantly share code, notes, and snippets.

@maxim
Created March 9, 2010 03:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save maxim/326139 to your computer and use it in GitHub Desktop.
Save maxim/326139 to your computer and use it in GitHub Desktop.
VariantGenerator
require 'test/unit'
##
# Variant generator returns all possible combinations of elements of all given arrays.
# Running this gist will run the tests. For normal usage, simply copy VariantsGenerator module.
module VariantsGenerator
module_function
##
# Operates on arrays.
#
# Example:
# generate_variants(["red", "blue"], ["long", "short"])
# # => [["red", "long"], ["red", "short"], ["blue", "long"], ["blue", "short"]]
def generate_variants(*arrays)
options = arrays.last.is_a?(Hash) ? arrays.pop : {}
splat_result = options[:splat_result]
unless arrays.all?{|arg| arg.is_a?(Array)}
raise ArgumentError.new("Only array arguments allowed.")
end
if arrays.empty?
raise ArgumentError.new("Expecting at least one array passed in.")
end
if arrays.size < 2
first_array = arrays[0]
first_array.map{|el| [el]}
elsif arrays.size > 2
generate_variants(arrays.shift, generate_variants(*arrays), :splat_result => true)
else
first_array, second_array = *arrays
result = []
first_array.each do |el1|
second_array.each do |el2|
if splat_result
result << [el1, *el2]
else
result << [el1, el2]
end
end
end
result
end
end
##
# Operates on hashes where key is name and value is array.
#
# Example:
# generate_named_variants(:color => ["red", "blue"], :type => ["long", "short"])
# # => [{:color => "red", :type => "long"}, {:color => "red", :type => "short"},
# # {:color => "blue", :type => "long"}, {:color => "red", :type => "short"}]
def generate_named_variants(hash)
names, arrays = hash.to_a.transpose
generate_variants(*arrays).map { |values| Hash[names.zip(values)] }
end
end
# Tests
class Test::Unit::TestCase
include VariantsGenerator
def assert_same_elements(a1, a2, msg = nil)
a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
assert_equal(a1h, a2h, msg)
end
end
class GenerateVariantsTest < Test::Unit::TestCase
def test_valid_permutations_for_2_arrays_of_strings
assert_same_elements [["white", "m"],
["white", "f"],
["black", "m"],
["black", "f"]], generate_variants(["white", "black"], ["m", "f"])
end
def test_errors_out_on_non_array_elements
assert_raise(ArgumentError) do
generate_variants("foo", "bar")
end
end
def test_array_elements_are_preserved_unflattened_for_2_arrays
assert_same_elements [[["white"], ["m"]],
[["white"], ["f"]],
[["black"], ["m"]],
[["black"], ["f"]]], generate_variants([["white"], ["black"]], [["m"], ["f"]])
end
def test_array_elements_are_preserved_unflattened_for_3_arrays
assert_same_elements [[["white"], ["m"], ["s"]],
[["white"], ["m"], ["m"]],
[["white"], ["m"], ["l"]],
[["white"], ["f"], ["s"]],
[["white"], ["f"], ["m"]],
[["white"], ["f"], ["l"]],
[["black"], ["m"], ["s"]],
[["black"], ["m"], ["m"]],
[["black"], ["m"], ["l"]],
[["black"], ["f"], ["s"]],
[["black"], ["f"], ["m"]],
[["black"], ["f"], ["l"]]], generate_variants([["white"], ["black"]],
[["m"], ["f"]],
[["s"], ["m"], ["l"]])
end
def test_valid_permutations_for_3_arrays
assert_same_elements [["s", "white", "m"],
["s", "white", "f"],
["s", "black", "m"],
["s", "black", "f"],
["m", "white", "m"],
["m", "white", "f"],
["m", "black", "m"],
["m", "black", "f"],
["l", "white", "m"],
["l", "white", "f"],
["l", "black", "m"],
["l", "black", "f"]], generate_variants(["s", "m", "l"], ["white", "black"], ["m", "f"])
end
def test_valid_permutations_for_4_arrays
assert_same_elements [["s", "white", "m", 1],
["s", "white", "m", 2],
["s", "white", "f", 1],
["s", "white", "f", 2],
["s", "black", "m", 1],
["s", "black", "m", 2],
["s", "black", "f", 1],
["s", "black", "f", 2],
["m", "white", "m", 1],
["m", "white", "m", 2],
["m", "white", "f", 1],
["m", "white", "f", 2],
["m", "black", "m", 1],
["m", "black", "m", 2],
["m", "black", "f", 1],
["m", "black", "f", 2]], generate_variants(["s", "m"],
["white", "black"],
["m", "f"],
[1, 2])
end
def test_works_with_single_element_array
assert_same_elements [["blue", "s"],
["blue", "m"]], generate_variants(["blue"], ["s", "m"])
end
def test_works_with_symbols
assert_same_elements [[:s, :white, :m],
[:s, :white, :f],
[:s, :black, :m],
[:s, :black, :f],
[:m, :white, :m],
[:m, :white, :f],
[:m, :black, :m],
[:m, :black, :f],
[:l, :white, :m],
[:l, :white, :f],
[:l, :black, :m],
[:l, :black, :f]], generate_variants([:s, :m, :l], [:white, :black], [:m, :f])
end
def test_works_with_fixnums
assert_same_elements [[1, 11, 21],
[1, 11, 22],
[1, 12, 21],
[1, 12, 22],
[2, 11, 21],
[2, 11, 22],
[2, 12, 21],
[2, 12, 22],
[3, 11, 21],
[3, 11, 22],
[3, 12, 21],
[3, 12, 22]], generate_variants([1, 2, 3], [11, 12], [21, 22])
end
def test_1_permutation_for_1_array_element
assert_same_elements [["s"], ["m"], ["l"]], generate_variants(["s", "m", "l"])
end
def test_errors_out_when_called_with_no_arguments
assert_raise(ArgumentError) do
generate_variants
end
end
end
class GenerateNamedVariantsTest < Test::Unit::TestCase
def test_permutes_3_named_arrays
assert_same_elements [{:color=>"white", :gender=>"m", :size=>"s"},
{:color=>"white", :gender=>"m", :size=>"m"},
{:color=>"white", :gender=>"m", :size=>"l"},
{:color=>"white", :gender=>"f", :size=>"s"},
{:color=>"white", :gender=>"f", :size=>"m"},
{:color=>"white", :gender=>"f", :size=>"l"},
{:color=>"black", :gender=>"m", :size=>"s"},
{:color=>"black", :gender=>"m", :size=>"m"},
{:color=>"black", :gender=>"m", :size=>"l"},
{:color=>"black", :gender=>"f", :size=>"s"},
{:color=>"black", :gender=>"f", :size=>"m"},
{:color=>"black", :gender=>"f", :size=>"l"}],
generate_named_variants( :size => ["s", "m", "l"],
:color => ["white", "black"],
:gender => ["m", "f"])
end
end
@webgago
Copy link

webgago commented Jul 30, 2010

See Array#zip...

@maxim
Copy link
Author

maxim commented Jul 30, 2010

What about Array#zip?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment