Created
March 9, 2010 03:52
-
-
Save maxim/326139 to your computer and use it in GitHub Desktop.
VariantGenerator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See Array#zip...