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 +3 -0
- data/CONTRIBUTORS +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +27 -0
- data/README +102 -0
- data/Rakefile +2 -0
- data/lib/truthtable.rb +197 -0
- data/lib/truthtable/qm.rb +366 -0
- data/lib/truthtable/truth_table_object.rb +40 -0
- data/lib/truthtable/version.rb +3 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/truthtable/qm_spec.rb +133 -0
- data/spec/truthtable/truthtable_spec.rb +61 -0
- data/truthtable.gemspec +23 -0
- data/truthtable.rb +345 -0
- metadata +114 -0
data/.gitignore
ADDED
data/CONTRIBUTORS
ADDED
data/Gemfile
ADDED
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
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
|