truthtable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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