Class: ComboFinder
- Inherits:
-
Object
- Object
- ComboFinder
- Defined in:
- lib/minitest/find_minimal_combination.rb
Overview
Finds the minimal combination of a collection of items that satisfy test
.
Instance Method Summary collapse
-
#cache_result(result, data, cache) ⇒ Object
:nodoc:.
-
#d(s = "") ⇒ Object
:nodoc:.
-
#find_minimal_combination(ary) ⇒ Object
Find the minimal combination of a collection of items that satisfy
test
.
Instance Method Details
#cache_result(result, data, cache) ⇒ Object
:nodoc:
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/minitest/find_minimal_combination.rb', line 89 def cache_result result, data, cache # :nodoc: cache[data] = true return result if result unless result or data.size > 128 then max = data.size subdiv = 2 until subdiv >= max do data.each_slice(max / subdiv) do |sub_data| cache[sub_data] = true end subdiv *= 2 end end result end |
#d(s = "") ⇒ Object
:nodoc:
85 86 87 |
# File 'lib/minitest/find_minimal_combination.rb', line 85 def d s = "" # :nodoc: warn s if ENV["MTB_DEBUG"] end |
#find_minimal_combination(ary) ⇒ Object
Find the minimal combination of a collection of items that satisfy test
.
If you think of the collection as a binary tree, this algorithm does a breadth first search of the combinations that satisfy test
. –
level collection
0 A
1 B C
2 D E F G
3 1 2 3 4 5 6 7 8
This assumes that A has already been tested and you’re now trying to reduce the match. Starting at level 1, test B & C separately. If either test positive, reduce the search space accordingly. If not, step down to level 2 and search w/ finer granularity (ie, DF, DG, EF–DE and FG were already tested as B & C). Repeat until a minimal combination is found.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/minitest/find_minimal_combination.rb', line 30 def find_minimal_combination ary level, n_combos = 1, 1 seen = {} d "Total number of culprits: #{ary.size}" loop do size = 2 ** (Math.log(ary.size) / Math.log(2)).round divs = 2 ** level done = divs >= size divs = size if done subsections = ary.each_slice(size/divs).to_a.combination(n_combos) d d "# new round!" d "# of subsections in this round: #{subsections.to_a.size}" d found = subsections.find { |a| b = a.flatten next if seen[b] d "# trying #{b.size} at level #{level} / combo #{n_combos}" cache_result yield(b), b, seen } if found then ary = found.flatten break if done seen.delete ary d "# FOUND!" d "# search space size = #{ary.size}" d "# resetting level and n_combos to 1" level = n_combos = 1 else if done then n_combos += 1 d "# increasing n_combos to #{n_combos}" break if n_combos > size else level += 1 n_combos = level d "# setting level to #{level} and n_combos to #{n_combos}" end end end ary end |