How to output all possible combinations using loops in Ruby?
I just started learning programming and am trying to write a function that outputs all possible combinations. So far I've managed to find all the possible combinations of size 2, but I'm not sure how to leave the code open to deal with combinations of large sizes. Would some kind of recursion be helpful?
I know I can use the built-in combination method, but I'm just trying to figure out how to write it from scratch. Any advice would be much appreciated. Thank!
def two_combos(input)
list = []
for index1 in (0...input.length)
for index2 in (0...input.length)
if input[index1] != input[index2]
if list.include?([input[index2],input[index1]])==false
list << [input[index1],input[index2]]
end
end
end
end
return list
end
two_combos(["A","B","C"])
#outputs
=> [["A", "B"], ["A", "C"], ["B", "C"]]
#Missing
["A","B","C"]
source to share
This implementation is similar to recursive counting in binary:
def combinations(items)
return [] unless items.any?
prefix = items[0]
suffixes = combinations(items[1..-1])
[[prefix]] + suffixes + suffixes.map {|item| [prefix] + item }
end
> combinations(%w(a b c))
=> [["a"], ["b"], ["c"], ["b", "c"], ["a", "b"], ["a", "c"], ["a", "b", "c"]]
At each step, the combinations are concatenations:
- only the first element
- combinations of the following elements (elements 1..n-1)
- the first element combined with combinations of the following elements
source to share
Here's a recursive algorithm
def combinations(array, size)
fail "size is too big" if size > array.size
combination([], [], array, size)
end
def combination(result, step, array, size)
steps = size - step.size
array[0..-steps].each_with_index do |a, i|
next_step = step + [a]
if next_step.size < size
combination(result, next_step, array[i+1..-1], size)
else
result << next_step
end
end
result
end
a = ("A".."E").to_a
p combinations(a, 1)
# [["A"], ["B"], ["C"], ["D"], ["E"]]
p combinations(a, 2)
# [["A", "B"], ["A", "C"], ["A", "D"], ["A", "E"], ["B", "C"], ["B", "D"], ["B", "E"], ["C", "D"], ["C", "E"], ["D", "E"]]
p combinations(a, 3)
# [["A", "B", "C"], ["A", "B", "D"], ["A", "B", "E"], ["A", "C", "D"], ["A", "C", "E"], ["A", "D", "E"], ["B", "C", "D"], ["B", "C", "E"], ["B", "D", "E"], ["C", "D", "E"]]
p combinations(a, 4)
# [["A", "B", "C", "D"], ["A", "B", "C", "E"], ["A", "B", "D", "E"], ["A", "C", "D", "E"], ["B", "C", "D", "E"]]
source to share
I can think of a way to calculate combinations of a given size without using recursion, but this is not particularly efficient. It is very effective, however, if you want to get all combinations of all sizes (sometimes called "power"). [Edit: obviously not. ] I understand that the question is about the latter, but I'll give methods for each.
If index
has elements n
, each combination can be represented by an array - an n
element whose elements are zero or one 1
means that the combination includes the element at that index, '0' (or leading space), which means it does not. Therefore, we can generate a set of all combinations of all sizes by simply creating all binary numbers of length n
, converting each of its string representation (with leading zeros) to an array of s "0"
and "1"
s, replacing "1"
with their index positions, deleting "0"
and extracting an element index
at given index positions.
code
def all_combos(sz)
[*(0..2**sz-1)].map { |i| ("%0#{sz}b" % i).chars }
.map { |a| a.each_with_index
.select { |n,ndx| n=="1" }.map(&:last) }
end
def combos(input, n, all_combos)
all_combos.select { |c| c.size == n }.map { |c| input.values_at(*c) }
end
def power(input, all_combos)
all_combos.map { |c| input.values_at(*c) }
end
Example
input = %w{b e a r s}
#=> ["b", "e", "a", "r", "s"]
ac = all_combos(input.size)
#=> [[], [4], [3], [3, 4], [2], [2, 4], [2, 3], [2, 3, 4],
# [1], [1, 4], [1, 3], [1, 3, 4], [1, 2], [1, 2, 4], [1, 2, 3],
# [1, 2, 3, 4], [0], [0, 4], [0, 3], [0, 3, 4], [0, 2], [0, 2, 4],
# [0, 2, 3], [0, 2, 3, 4], [0, 1], [0, 1, 4], [0, 1, 3], [0, 1, 3, 4],
# [0, 1, 2], [0, 1, 2, 4], [0, 1, 2, 3], [0, 1, 2, 3, 4]]
(0..input.size).each { |i| puts "size #{i}"; p combos(input, i, ac) }
# size 0
# [[]]
# size 1
# [["s"], ["r"], ["a"], ["e"], ["b"]]
# size 2
# [["r", "s"], ["a", "s"], ["a", "r"], ["e", "s"], ["e", "r"],
# ["e", "a"], ["b", "s"], ["b", "r"], ["b", "a"], ["b", "e"]]
# size 3
# [["a", "r", "s"], ["e", "r", "s"], ["e", "a", "s"], ["e", "a", "r"],
# ["b", "r", "s"], ["b", "a", "s"], ["b", "a", "r"], ["b", "e", "s"],
# ["b", "e", "r"], ["b", "e", "a"]]
# size 4
# [["e", "a", "r", "s"], ["b", "a", "r", "s"], ["b", "e", "r", "s"],
# ["b", "e", "a", "s"], ["b", "e", "a", "r"]]
# size 5
# [["b", "e", "a", "r", "s"]]
power(input, ac)
#=> [[], ["s"], ["r"], ["r", "s"], ["a"], ["a", "s"], ["a", "r"],
# ["a", "r", "s"], ["e"], ["e", "s"], ["e", "r"], ["e", "r", "s"],
# ["e", "a"], ["e", "a", "s"], ["e", "a", "r"], ["e", "a", "r", "s"],
# ["b"], ["b", "s"], ["b", "r"], ["b", "r", "s"], ["b", "a"],
# ["b", "a", "s"], ["b", "a", "r"], ["b", "a", "r", "s"], ["b", "e"],
# ["b", "e", "s"], ["b", "e", "r"], ["b", "e", "r", "s"], ["b", "e", "a"],
# ["b", "e", "a", "s"], ["b", "e", "a", "r"], ["b", "e", "a", "r", "s"]]
source to share
Gentlemen, start your engines!
Comparison of methods
module Methods
def ruby(array)
(0..array.size).each_with_object([]) { |i,a|
a.concat(array.combination(i).to_a) }
end
def todd(input)
permutations(input) << []
end
private
def permutations(items)
return [] unless items.any?
prefix = items[0]
suffixes = permutations(items[1..-1])
[[prefix]] + suffixes + suffixes.map {|item| [prefix, item].flatten }
end
public
def fl00r(array)
(1..array.size).each_with_object([]) { |i,a|
a.concat(combinations(array, i)) } << []
end
private
def combinations(array, size)
fail "size is too big" if size > array.size
combination([], [], array, size)
end
def combination(result, step, array, size)
steps = size - step.size
array[0..-steps].each_with_index do |a, i|
next_step = step + [a]
if next_step.size < size
combination(result, next_step, array[i+1..-1], size)
else
result << next_step
end
end
result
end
public
def cary(input)
ac = all_combos(input.size)
ac.map { |c| input.values_at(*c) }
end
private
def all_combos(sz)
[*0..2**sz-1].map { |i| ("%0#{sz}b" % i).chars }
.map { |a| a.each_with_index.select { |n,ndx| n=="1" }.map(&:last) }
end
end
Test data
def test_array(n)
[*1..n]
end
Helpers
def compute(arr, meth)
send(meth, arr)
end
def compute_sort(arr, meth)
compute(arr, meth).map(&:sort).sort
end
Enable module
include Methods
@methods = Methods.public_instance_methods(false)
#=> [:ruby, :todd, :fl00r, :cary]
Validate methods return the same values
arr = test_array(8)
a = compute_sort(arr, @methods.first)
puts @methods[1..-1].all? { |m| a == compute_sort(arr, m) }
#=> true
Control code
require 'benchmark'
@indent = methods.map { |m| m.to_s.size }.max
[10, 15, 20].each do |n|
puts "\nn = #{n}"
arr = test_array(n)
Benchmark.bm(@indent) do |bm|
@methods.each do |m|
bm.report m.to_s do
compute(arr, m).size
end
end
end
end
Tests (in seconds)
n = 10
user system total real
ruby 0.000000 0.000000 0.000000 ( 0.000312)
todd 0.000000 0.000000 0.000000 ( 0.001611)
fl00r 0.000000 0.000000 0.000000 ( 0.002675)
cary 0.010000 0.000000 0.010000 ( 0.010026)
n = 15
user system total real
ruby 0.010000 0.000000 0.010000 ( 0.010742)
todd 0.070000 0.010000 0.080000 ( 0.081821)
fl00r 0.080000 0.000000 0.080000 ( 0.076030)
cary 0.430000 0.020000 0.450000 ( 0.450382)
n = 20
user system total real
ruby 0.310000 0.040000 0.350000 ( 0.350484)
todd 2.360000 0.130000 2.490000 ( 2.487493)
fl00r 2.320000 0.090000 2.410000 ( 2.405377)
cary 21.420000 0.620000 22.040000 ( 22.053303)
I can only make one final conclusion.
source to share