truthtable 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .idea
2
+ *.gem
3
+ *.iml
data/CONTRIBUTORS ADDED
@@ -0,0 +1,7 @@
1
+ == Author
2
+
3
+ Tanaka Akira <akr@fsij.org>
4
+
5
+ == Contributors
6
+
7
+ Max De Marzi
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in truthtable.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ truthtable (0.0.1)
5
+ msgpack
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.3)
11
+ msgpack (0.4.7)
12
+ rspec (2.11.0)
13
+ rspec-core (~> 2.11.0)
14
+ rspec-expectations (~> 2.11.0)
15
+ rspec-mocks (~> 2.11.0)
16
+ rspec-core (2.11.1)
17
+ rspec-expectations (2.11.2)
18
+ diff-lcs (~> 1.1.3)
19
+ rspec-mocks (2.11.1)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ rspec
26
+ truthtable!
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ # truthtable.rb - truth table and formula generator
2
+ #
3
+ # Copyright (C) 2007 Tanaka Akira <akr@fsij.org>
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ (1) Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+ (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+ (3) The name of the author may not be used to endorse or promote products
14
+ derived from this software without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ OF SUCH DAMAGE.
26
+
27
+ (The modified BSD licence)
data/README ADDED
@@ -0,0 +1,102 @@
1
+ = truthtable - truth table to logical formula
2
+
3
+ The truthtable library generates a truth table from a logical formula written in Ruby.
4
+ The truth table can be converted to a logical formula.
5
+
6
+ == Install
7
+
8
+ gem install 'truthtable'
9
+
10
+ == Features
11
+
12
+ * generate a truth table from a given block written in Ruby.
13
+ * generate a formula from the table:
14
+ * minimal one (obtained by Quine-McCluskey algorithm)
15
+ * disjunctive normal form
16
+ * conjunctive normal form
17
+
18
+ == Usage
19
+
20
+ require 'truthtable'
21
+
22
+ * puts, p and pp shows truth table
23
+
24
+ puts TruthTable.new {|v| v[0] & v[1] }
25
+ #=>
26
+ v[0] v[1] |
27
+ ----------+--
28
+ f f | f
29
+ f t | f
30
+ t f | f
31
+ t t | t
32
+
33
+ p TruthTable.new {|v| v[0] & v[1] }
34
+ #=> #<TruthTable: !v[0]&!v[1]=>false !v[0]&v[1]=>false v[0]&!v[1]=>false v[0]&v[1]=>true>
35
+
36
+ require 'pp'
37
+ pp TruthTable.new {|v| v[0] & v[1] }
38
+ #=>
39
+ #<TruthTable:
40
+ !v[0]&!v[1]=>false
41
+ !v[0]& v[1]=>false
42
+ v[0]&!v[1]=>false
43
+ v[0]& v[1]=>true>
44
+
45
+ * '?' is shown for non-evaluated variable
46
+
47
+ puts TruthTable.new {|v| v[0] && v[1] }'
48
+ #=>
49
+ v[0] v[1] |
50
+ ----------+--
51
+ f ? | f
52
+ t f | f
53
+ t t | t
54
+
55
+ puts TruthTable.new {|v| v[0] || v[1] }'
56
+ #=>
57
+ v[0] v[1] |
58
+ ----------+--
59
+ f f | f
60
+ f t | t
61
+ t ? | t
62
+
63
+ puts TruthTable.new {|v| !v[0] ? !v[1] : !v[2] }'
64
+ #=>
65
+ v[0] v[1] v[2] |
66
+ ---------------+--
67
+ f f ? | t
68
+ f t ? | f
69
+ t ? f | t
70
+ t ? t | f
71
+
72
+ * formula generation
73
+
74
+ p TruthTable.new {|v| !v[0] }.formula #=> "!v[0]"
75
+ p TruthTable.new {|v| v[0] & v[1] }.formula #=> "v[0]&v[1]"
76
+ p TruthTable.new {|v| v[0] | v[1] }.formula #=> "v[0] | v[1]"
77
+ p TruthTable.new {|v| v[0] ^ v[1] }.formula #=> "!v[0]&v[1] | v[0]&!v[1]"
78
+ p TruthTable.new {|v| v[0] == v[1] }.formula #=> "!v[0]&!v[1] | v[0]&v[1]"
79
+
80
+ * shortcuts, && and ||, are not preserved
81
+
82
+ p TruthTable.new {|v| v[0] && v[1] }.formula #=> "v[0]&v[1]"
83
+ p TruthTable.new {|v| v[0] || v[1] }.formula #=> "v[0] | v[1]"
84
+
85
+ * actually any expression (without side effect)
86
+
87
+ p TruthTable.new {|v| v[0] ? !v[1] : v[1] }.formula #=> "!v[0]&v[1] | v[0]&!v[1]"
88
+
89
+ * any number of inputs
90
+
91
+ p TruthTable.new {|v| [v[0], v[1], v[2], v[3]].grep(true).length <= 3 }.formula
92
+ #=> "!v[0] | !v[1] | !v[2] | !v[3]"
93
+
94
+
95
+ == Reference Manual
96
+
97
+ See rdoc/classes/TruthTable.html or
98
+ http://www.a-k-r.org/truthtable/rdoc/TruthTable.html
99
+
100
+ == Home Page
101
+
102
+ http://www.a-k-r.org/truthtable/
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/lib/truthtable.rb ADDED
@@ -0,0 +1,197 @@
1
+ require "truthtable/version"
2
+ require "truthtable/truth_table_object"
3
+ require "truthtable/qm"
4
+
5
+ class TruthTable
6
+
7
+ def self.test(&b)
8
+ r = []
9
+ o = TruthTableObject.new
10
+ begin
11
+ result = !!b.call(o)
12
+ inputs = o.plan
13
+ order = o.order
14
+ r << [inputs, result, order]
15
+ end while o.next_plan
16
+ r
17
+ end
18
+
19
+ def initialize(&b)
20
+ table = TruthTable.test(&b)
21
+ @table = table
22
+ end
23
+
24
+ def to_s
25
+ r = ''
26
+ names = sort_names(all_names.keys)
27
+ format = ''
28
+ sep = ''
29
+ names.each {|name|
30
+ format << "%-#{name.length}s "
31
+ sep << '-' * (name.length+1)
32
+ }
33
+ format << "| %s\n"
34
+ sep << "+--\n"
35
+ r << sprintf(format, *(names + ['']))
36
+ r << sep
37
+ @table.each {|inputs, output, order|
38
+ h = {}
39
+ each_input(inputs) {|name, input|
40
+ h[name] = input
41
+ }
42
+ args = []
43
+ names.each {|name|
44
+ if h.has_key? name
45
+ args << (h[name] ? 't' : 'f').center(name.length)
46
+ else
47
+ args << '?'.center(name.length)
48
+ end
49
+ }
50
+ args << (output ? 't' : 'f')
51
+ r << sprintf(format, *args)
52
+ }
53
+ r
54
+ end
55
+
56
+ # :stopdoc:
57
+ def inspect
58
+ result = "#<#{self.class}:"
59
+ @table.each {|inputs, output, order|
60
+ term = []
61
+ each_input(inputs) {|name, input|
62
+ if input
63
+ term << name
64
+ else
65
+ term << "!#{name}"
66
+ end
67
+ }
68
+ result << " #{term.join('&')}=>#{output}"
69
+ }
70
+ result << ">"
71
+ result
72
+ end
73
+
74
+ def pretty_print(q)
75
+ q.object_group(self) {
76
+ q.text ':'
77
+ q.breakable
78
+ q.seplist(@table, lambda { q.breakable('; ') }) {|inputs, output, order|
79
+ term = []
80
+ each_input(inputs) {|name, input|
81
+ if input
82
+ term << " #{name}"
83
+ else
84
+ term << "!#{name}"
85
+ end
86
+ }
87
+ q.text "#{term.join('&')}=>#{output}"
88
+ }
89
+ }
90
+ end
91
+
92
+ def all_names
93
+ return @all_names if defined? @all_names
94
+ @all_names = {}
95
+ @table.each {|inputs, output, order|
96
+ order.each {|name|
97
+ if !@all_names.has_key?(name)
98
+ @all_names[name] = @all_names.size
99
+ end
100
+ }
101
+ }
102
+ @all_names
103
+ end
104
+
105
+ def sort_names(names)
106
+ total_order = all_names
107
+ names.sort_by {|n| total_order[n] }
108
+ end
109
+
110
+ def each_input(inputs)
111
+ sort_names(inputs.keys).each {|name|
112
+ yield name, inputs[name]
113
+ }
114
+ end
115
+ # :startdoc:
116
+
117
+ # obtains a formula in disjunctive normal form.
118
+ def dnf
119
+ r = []
120
+ @table.each {|inputs, output|
121
+ return output.to_s if inputs.empty?
122
+ next if !output
123
+ term = []
124
+ each_input(inputs) {|name, input|
125
+ if input
126
+ term << name
127
+ else
128
+ term << "!#{name}"
129
+ end
130
+ }
131
+ r << term.join('&')
132
+ }
133
+ return "false" if r.empty?
134
+ r.join(' | ')
135
+ end
136
+
137
+ # obtains a formula in conjunctive normal form.
138
+ def cnf
139
+ r = []
140
+ @table.each {|inputs, output|
141
+ return output.to_s if inputs.empty?
142
+ next if output
143
+ term = []
144
+ each_input(inputs) {|name, input|
145
+ if input
146
+ term << "!#{name}"
147
+ else
148
+ term << name
149
+ end
150
+ }
151
+ if term.length == 1
152
+ r << term.join('|')
153
+ else
154
+ r << "(#{term.join('|')})"
155
+ end
156
+ }
157
+ return "true" if r.empty?
158
+ r.join(' & ')
159
+ end
160
+
161
+ # obtains a minimal formula using Quine-McCluskey algorithm.
162
+ def formula
163
+ input_names = all_names
164
+ input_names_ary = sort_names(input_names.keys)
165
+ tbl = {}
166
+ @table.each {|inputs, output|
167
+ return output.to_s if inputs.empty?
168
+ inputs2 = [:x] * input_names.length
169
+ inputs.each {|name, input|
170
+ inputs2[input_names[name]] = input ? 1 : 0
171
+ }
172
+ tbl[inputs2] = output ? 1 : 0
173
+ }
174
+ qm = QM.qm(tbl)
175
+ r = []
176
+ qm.each {|term|
177
+ t = []
178
+ num_dontcare = 0
179
+ term.each_with_index {|v, i|
180
+ if v == false
181
+ t << ("!" + input_names_ary[i])
182
+ elsif v == true
183
+ t << input_names_ary[i]
184
+ else # :x
185
+ num_dontcare += 1
186
+ end
187
+ }
188
+ if num_dontcare == term.length
189
+ r << 'true'
190
+ else
191
+ r << t.join('&')
192
+ end
193
+ }
194
+ return "false" if r.empty?
195
+ r.join(' | ')
196
+ end
197
+ end
@@ -0,0 +1,366 @@
1
+ require 'msgpack'
2
+
3
+ class TruthTable
4
+ # Quine-McCluskey algorithm
5
+ module QM
6
+ module_function
7
+
8
+ # implements Quine-McCluskey algorithm.
9
+ # It minimize the boolean function given by <i>tbl</i>.
10
+ #
11
+ # For example, the 3-inputs majority function is given as follows.
12
+ #
13
+ # tbl = {
14
+ # # P Q R
15
+ # [false, false, false] => false,
16
+ # [false, false, true ] => false,
17
+ # [false, true, false] => false,
18
+ # [false, true, true ] => true,
19
+ # [true, false, false] => false,
20
+ # [true, false, true ] => true,
21
+ # [true, true, false] => true,
22
+ # [true, true, true ] => true,
23
+ # }
24
+ # TruthTable::QM.qm(tbl)
25
+ # #=>
26
+ # [[true, true, :x], [true, :x, true], [:x, true, true]] # P&Q | P&R | Q&R
27
+ # # P Q P R Q R
28
+ #
29
+ # For another example, the implication function is given as follows.
30
+ #
31
+ # tbl = {
32
+ # # P Q
33
+ # [false, false] => true,
34
+ # [false, true ] => true,
35
+ # [true, false] => false,
36
+ # [true, true ] => true,
37
+ # }
38
+ # TruthTable::QM.qm(tbl)
39
+ # #=>
40
+ # [[false, :x], [:x, true]] # ~P | Q
41
+ # # ~P Q
42
+ #
43
+ # <i>tbl</i> is a hash to represent a boolean function.
44
+ # If the function has N variables,
45
+ # all key of <i>tbl</i> must be an array of N elements.
46
+ #
47
+ # A element of the key array and a value of the hash should be one of follows:
48
+ # - false, 0
49
+ # - true, 1
50
+ # - :x
51
+ #
52
+ # 0 is same as false.
53
+ #
54
+ # 1 is same as false.
55
+ #
56
+ # :x means "don't care".
57
+ #
58
+ # For example, 3-inputs AND function can be given as follows.
59
+ #
60
+ # tbl = {
61
+ # [false, :x, :x ] => false,
62
+ # [:x, false, :x ] => false,
63
+ # [:x, :x, false] => false,
64
+ # [true, true, true ] => true,
65
+ # }
66
+ #
67
+ # :x can be used for a value of <i>tbl</i> too.
68
+ # It means that the evaluated result of minimized boolean function is not specified:
69
+ # it may be evaluated to true or false.
70
+ #
71
+ # tbl = {
72
+ # [false, false] => false,
73
+ # [false, true ] => true,
74
+ # [true, false] => false,
75
+ # [true, true ] => :x
76
+ # }
77
+ #
78
+ # If <i>tbl</i> doesn't specify some combination of
79
+ # input variables, it assumes :x for such combination.
80
+ # The above function can be specified as follows.
81
+ #
82
+ # tbl = {
83
+ # [false, false] => false,
84
+ # [false, true ] => true,
85
+ # [true, false] => false,
86
+ # }
87
+ #
88
+ # QM.qm returns an array of arrays which represents
89
+ # the minimized boolean function.
90
+ #
91
+ # The minimized boolean function is a
92
+ # disjunction of terms such as "term1 | term2 | term3 | ...".
93
+ #
94
+ # The inner array represents a term.
95
+ # A term is a conjunction of input variables and negated input variables: "P & ~Q & ~R & S & ...".
96
+ #
97
+ def qm(tbl)
98
+ return [] if tbl.empty?
99
+ tbl = intern_tbl(tbl)
100
+ prime_implicants = find_prime_implicants(tbl)
101
+ essential_prime_implicants, chart = make_chart(prime_implicants, tbl)
102
+ additional_prime_implicants = search_minimal_combination(chart)
103
+ (essential_prime_implicants.keys + additional_prime_implicants).sort.reverse.map {|t|
104
+ extern_term(t)
105
+ }
106
+ end
107
+
108
+ # :stopdoc:
109
+
110
+ def has_intersection?(t1, t2)
111
+ [t1,t2].transpose.all? {|v1, v2|
112
+ v1 == -1 || v2 == -1 || v1 == v2
113
+ }
114
+ end
115
+
116
+ def implication?(t1, t2)
117
+ [t1,t2].transpose.all? {|v1, v2|
118
+ v2 == -1 || v1 == v2
119
+ }
120
+ end
121
+
122
+ INTERN = {
123
+ false => 0,
124
+ true => 1,
125
+ 0 => 0,
126
+ 1 => 1,
127
+ :x => -1,
128
+ }
129
+ def intern_tbl(tbl)
130
+ result = {}
131
+ num_inputs = nil
132
+ tbl.each {|inputs, output|
133
+ if !num_inputs
134
+ num_inputs = inputs.length
135
+ else
136
+ if inputs.length != num_inputs
137
+ raise ArgumentError, "different number of inputs"
138
+ end
139
+ end
140
+ inputs2 = inputs.map {|v|
141
+ if !INTERN.has_key?(v)
142
+ raise ArgumentError, "unexpected input: #{v.inspect}"
143
+ end
144
+ INTERN[v]
145
+ }
146
+ if !INTERN.has_key?(output)
147
+ raise ArgumentError, "unexpected output: #{output.inspect}"
148
+ end
149
+ result[inputs2] = INTERN[output]
150
+ }
151
+ result_keys = result.keys
152
+ 0.upto(result_keys.length-2) {|i|
153
+ ki = result_keys[i]
154
+ next if !result[ki]
155
+ (i+1).upto(result_keys.length-1) {|j|
156
+ kj = result_keys[j]
157
+ next if !result[kj]
158
+ if has_intersection?(ki, kj)
159
+ if result[ki] != result[kj]
160
+ raise ArgumentError, "inconsistent table"
161
+ end
162
+ if implication?(ki, kj)
163
+ result.delete ki
164
+ elsif implication?(kj, ki)
165
+ result.delete kj
166
+ end
167
+ end
168
+ }
169
+ }
170
+ not_specified = []
171
+ 0.upto((1 << num_inputs)-1) {|n|
172
+ inputs = (0...num_inputs).map {|i| n[i] }
173
+ if result.all? {|inputs_pat, output| !has_intersection?(inputs, inputs_pat) }
174
+ not_specified << inputs
175
+ end
176
+ }
177
+ not_specified.each {|inputs|
178
+ result[inputs] = -1
179
+ }
180
+ result
181
+ end
182
+
183
+ EXTERN = {
184
+ 0 => false,
185
+ 1 => true,
186
+ -1 => :x
187
+ }
188
+ def extern_term(t)
189
+ t.map {|v| EXTERN[v] }
190
+ end
191
+
192
+ def combine(t1, t2)
193
+ num_diffs = 0
194
+ r = [t1,t2].transpose.map {|v1, v2|
195
+ if v1 == v2
196
+ v1
197
+ elsif v1 == 0 && v2 == 1
198
+ num_diffs += 1
199
+ return nil if 1 < num_diffs
200
+ -1
201
+ elsif v1 == 1 && v2 == 0
202
+ num_diffs += 1
203
+ return nil if 1 < num_diffs
204
+ -1
205
+ else
206
+ return nil
207
+ end
208
+ }
209
+ if num_diffs == 1
210
+ r
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ def combine2(t1, t2)
217
+ num_diffs = 0
218
+ r = [t1,t2].transpose.map {|v1, v2|
219
+ if v1 == v2
220
+ v1
221
+ elsif v1 == 0 && v2 == 1
222
+ num_diffs += 1
223
+ return nil if 1 < num_diffs
224
+ -1
225
+ elsif v1 == 1 && v2 == 0
226
+ num_diffs += 1
227
+ return nil if 1 < num_diffs
228
+ -1
229
+ elsif v2 == -1
230
+ v1
231
+ else
232
+ return nil
233
+ end
234
+ }
235
+ if num_diffs == 1
236
+ r
237
+ else
238
+ nil
239
+ end
240
+ end
241
+
242
+ def find_prime_implicants(tbl)
243
+ num_inputs = nil
244
+ implicants_sets = []
245
+ tbl.each {|inputs, output|
246
+ num_inputs = inputs.length
247
+ next if output == 0
248
+ num_dontcares = inputs.grep(-1).length
249
+ num_ones = inputs.grep(1).length
250
+ implicants_sets[num_dontcares] ||= []
251
+ implicants_sets[num_dontcares][num_ones] ||= {}
252
+ implicants_sets[num_dontcares][num_ones][inputs] = true
253
+ }
254
+ combined = {}
255
+ 0.upto(num_inputs-1) {|num_dontcares|
256
+ #isets = implicants_sets[num_dontcares]
257
+ isets = implicants_sets[num_dontcares].clone unless implicants_sets[num_dontcares].nil?
258
+ next if !isets
259
+ 0.upto(isets.length-2) {|num_ones|
260
+ next if !isets[num_ones] || !isets[num_ones+1]
261
+ isets[num_ones].each_key {|t1|
262
+ isets[num_ones+1].each_key {|t2|
263
+ if t = combine(t1, t2)
264
+ combined[t1] = combined[t2] = true
265
+ implicants_sets[num_dontcares+1] ||= []
266
+ implicants_sets[num_dontcares+1][num_ones] ||= {}
267
+ implicants_sets[num_dontcares+1][num_ones][t] = true
268
+ end
269
+ }
270
+ }
271
+ }
272
+ isets.each {|ts1|
273
+ next if !ts1
274
+ ts1.each_key {|t1|
275
+ (num_dontcares+1).upto(num_inputs-1) {|num_dontcares2|
276
+ #isets2 = implicants_sets[num_dontcares2]
277
+ isets2 = MessagePack.unpack(implicants_sets[num_dontcares2].to_msgpack)
278
+ next if !isets2
279
+ isets2.each {|ts2|
280
+ next if !ts2
281
+ ts2.each_key {|t2|
282
+ if t = combine2(t1, t2)
283
+ combined[t1] = true
284
+ num_ones = t1.grep(1).length
285
+ implicants_sets[num_dontcares+1] ||= []
286
+ implicants_sets[num_dontcares+1][num_ones] ||= {}
287
+ implicants_sets[num_dontcares+1][num_ones][t] = true
288
+ end
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ prime_implicants = {}
296
+ implicants_sets.each {|isets|
297
+ next if !isets
298
+ isets.each {|ts|
299
+ next if !ts
300
+ ts.each_key {|t|
301
+ next if combined[t]
302
+ prime_implicants[t] = true
303
+ }
304
+ }
305
+ }
306
+ prime_implicants
307
+ end
308
+
309
+ def make_chart(prime_implicants, tbl)
310
+ essential_prime_implicants = {}
311
+ chart = []
312
+ tbl.each {|inputs, output|
313
+ next if output != 1
314
+ pi_list = []
315
+ prime_implicants.each_key {|pi|
316
+ if implication?(inputs, pi)
317
+ pi_list << pi
318
+ end
319
+ }
320
+ if pi_list.length == 1
321
+ essential_prime_implicants[pi_list[0]] = true
322
+ else
323
+ chart << pi_list
324
+ end
325
+ }
326
+ chart.reject! {|pi_list| pi_list.any? {|pi| essential_prime_implicants[pi] } }
327
+ return essential_prime_implicants, chart
328
+ end
329
+
330
+ def search_minimal_combination(chart)
331
+ return [] if chart.empty?
332
+ all_pi = {}
333
+ chart.each {|pi_list|
334
+ pi_list.each {|pi|
335
+ all_pi[pi] = true
336
+ }
337
+ }
338
+ q = {}
339
+ all_pi.each_key {|pi|
340
+ q[[pi]] = true
341
+ }
342
+ while true
343
+ next_q = {}
344
+ found = []
345
+ until q.empty?
346
+ pi_set0, _ = q.shift
347
+ pi_set = {}
348
+ pi_set0.each {|pi| pi_set[pi] = true }
349
+ if chart.all? {|pi_list| pi_list.any? {|pi| pi_set[pi] }}
350
+ found << pi_set0
351
+ end
352
+ all_pi.each_key {|pi|
353
+ next if pi_set[pi]
354
+ next_q[(pi_set0 + [pi]).sort] = true
355
+ }
356
+ end
357
+ if !found.empty?
358
+ return found.sort.first
359
+ end
360
+ q = next_q
361
+ end
362
+ end
363
+
364
+ # :startdoc:
365
+ end
366
+ end