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"]

      

+3


source to share


4 answers


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
+3


source


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"]]

      

+2


source


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"]]

      

+2


source


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.

0


source







All Articles