truthtable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ class TruthTableObject
2
+ def initialize
3
+ @checked = {}
4
+ @plan = {}
5
+ @order = []
6
+ @queue = []
7
+ end
8
+ attr_reader :plan, :order
9
+
10
+ def next_plan
11
+ @log = {}
12
+ @plan, @order = @queue.shift
13
+ @plan
14
+ end
15
+
16
+ def [](index)
17
+ s = "v[#{index}]"
18
+ if @plan.has_key?(s)
19
+ v = @plan[s]
20
+ else
21
+ fplan = @plan.dup
22
+ fplan[s] = false
23
+ fkey = fplan.keys.sort.map {|k| "#{k}=#{fplan[k]}" }.join(' ')
24
+ @order += [s]
25
+ @plan = fplan
26
+ v = false
27
+ if !@checked[fkey]
28
+ tplan = @plan.dup
29
+ tplan[s] = true
30
+ tkey = tplan.keys.sort.map {|k| "#{k}=#{tplan[k]}" }.join(' ')
31
+ torder = @order.dup
32
+ torder[-1] = s
33
+ @queue.unshift [tplan, torder]
34
+ @checked[tkey] = true
35
+ @checked[fkey] = true
36
+ end
37
+ end
38
+ v
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Truthtable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'truthtable'
2
+
3
+ class String
4
+ def to_bool
5
+ return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
6
+ return false if self == false || self.nil? || self.empty? || self =~ (/(false|f|no|n|0)$/i)
7
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
8
+ end
9
+ end
@@ -0,0 +1,133 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ QM = TruthTable::QM
4
+
5
+ describe QM do
6
+
7
+ describe "test intern_tbl" do
8
+ it "can raise ArgumentError" do
9
+ lambda{ QM.intern_tbl({[0]=>0, []=>1}) }.should raise_error(ArgumentError)
10
+ lambda{ QM.intern_tbl({[:y]=>0}) }.should raise_error(ArgumentError)
11
+ lambda{ QM.intern_tbl({[0]=>:y}) }.should raise_error(ArgumentError)
12
+ lambda{ QM.intern_tbl({[0]=>0, [:x]=>1}) }.should raise_error(ArgumentError)
13
+ end
14
+
15
+ it "can return right values" do
16
+ QM.intern_tbl({[0]=>0, [:x]=>0}).should == {[-1]=>0}
17
+ QM.intern_tbl({[0]=>0}).should == {[0]=>0, [1]=>-1}
18
+ end
19
+
20
+ end
21
+
22
+ describe "test qm" do
23
+ it "can be empty" do
24
+ QM.qm({}).should == []
25
+ end
26
+
27
+ it "can pass sample 1" do
28
+ tbl = {
29
+ [0,0,0,0]=>0,
30
+ [0,0,0,1]=>0,
31
+ [0,0,1,0]=>0,
32
+ [0,0,1,1]=>0,
33
+ [0,1,0,0]=>1,
34
+ [0,1,0,1]=>0,
35
+ [0,1,1,0]=>0,
36
+ [0,1,1,1]=>0,
37
+ [1,0,0,0]=>1,
38
+ [1,0,0,1]=>:x,
39
+ [1,0,1,0]=>1,
40
+ [1,0,1,1]=>1,
41
+ [1,1,0,0]=>1,
42
+ [1,1,0,1]=>0,
43
+ [1,1,1,0]=>:x,
44
+ [1,1,1,1]=>1,
45
+ }
46
+ QM.qm(tbl).should == [[true, :x, true, :x ],
47
+ [true, :x, :x, false],
48
+ [:x, true, false, false]]
49
+
50
+ end
51
+
52
+ it "can pass implication" do
53
+ tbl = {
54
+ [false,false]=>true,
55
+ [false,true ]=>true,
56
+ [true, false]=>false,
57
+ [true, true ]=>true,
58
+ }
59
+ QM.qm(tbl).should == [[false, :x], [:x, true]]
60
+ end
61
+
62
+ it "can shortcut or" do
63
+ tbl = {
64
+ [0, 0]=>0,
65
+ [1, :x]=>1,
66
+ [0, 1]=>1
67
+ }
68
+ QM.qm(tbl).should == [[true, :x], [:x, true]]
69
+ end
70
+
71
+ it "can do 3 ands" do
72
+ tbl = {
73
+ [false,:x, :x ]=>false,
74
+ [:x, false,:x ]=>false,
75
+ [:x, :x, false]=>false,
76
+ [true, true, true ]=>true,
77
+ }
78
+ QM.qm(tbl).should == [[true, true, true]]
79
+ end
80
+
81
+ it "can do 3 ors" do
82
+ tbl = {
83
+ [false,false,false]=>false,
84
+ [true, :x, :x ]=>true,
85
+ [:x, true, :x ]=>true,
86
+ [:x, :x, true ]=>true,
87
+ }
88
+ QM.qm(tbl).should == [[true, :x, :x], [:x, true, :x], [:x, :x, true]]
89
+ end
90
+
91
+ it "can do majority" do
92
+ tbl = {
93
+ [0,0,0]=>0,
94
+ [0,0,1]=>0,
95
+ [0,1,0]=>0,
96
+ [0,1,1]=>1,
97
+ [1,0,0]=>0,
98
+ [1,0,1]=>1,
99
+ [1,1,0]=>1,
100
+ [1,1,1]=>1,
101
+ }
102
+ QM.qm(tbl).should == [[true, true, :x], [true, :x, true], [:x, true, true]]
103
+ end
104
+
105
+ it "can do 4 bit fib predicate" do
106
+ tbl = {
107
+ [0,0,0,0]=>0,
108
+ [0,0,0,1]=>1, # 1
109
+ [0,0,1,0]=>1, # 2
110
+ [0,0,1,1]=>1, # 3
111
+ [0,1,0,0]=>0,
112
+ [0,1,0,1]=>1, # 5
113
+ [0,1,1,0]=>0,
114
+ [0,1,1,1]=>0,
115
+ [1,0,0,0]=>1, # 8
116
+ [1,0,0,1]=>0,
117
+ [1,0,1,0]=>0,
118
+ [1,0,1,1]=>0,
119
+ [1,1,0,0]=>0,
120
+ [1,1,0,1]=>1, # 13
121
+ [1,1,1,0]=>0,
122
+ [1,1,1,1]=>0,
123
+ }
124
+ QM.qm(tbl).should == [[true, false, false, false],
125
+ [false, false, true, :x],
126
+ [false, :x, false, true],
127
+ [:x, true, false, true]]
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
@@ -0,0 +1,61 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Truthtable do
4
+ describe "test tautology" do
5
+
6
+ it "can dnf true" do
7
+ TruthTable.new {|v| true }.dnf.to_bool.should be_true
8
+ end
9
+
10
+ it "can cnf true" do
11
+ TruthTable.new {|v| true }.cnf.to_bool.should be_true
12
+ end
13
+
14
+ it "can formula true" do
15
+ TruthTable.new {|v| true }.formula.to_bool.should be_true
16
+ end
17
+
18
+ it "can dnf values" do
19
+ TruthTable.new {|v| v[0] | !v[0] }.dnf.should == "!v[0] | v[0]"
20
+ end
21
+
22
+ it "can cnf values" do
23
+ TruthTable.new {|v| v[0] | !v[0] }.cnf.to_bool.should be_true
24
+ end
25
+
26
+ it "can formula values" do
27
+ TruthTable.new {|v| v[0] | !v[0] }.formula.to_bool.should be_true
28
+ end
29
+
30
+ end
31
+
32
+ describe "test contradiction" do
33
+
34
+ it "can dnf false" do
35
+ TruthTable.new {|v| false }.dnf.to_bool.should be_false
36
+ end
37
+
38
+ it "can cnf false" do
39
+ TruthTable.new {|v| false }.cnf.to_bool.should be_false
40
+ end
41
+
42
+ it "can formula false" do
43
+ TruthTable.new {|v| false }.formula.to_bool.should be_false
44
+ end
45
+
46
+ it "can dnf values" do
47
+ TruthTable.new {|v| v[0] & !v[0] }.dnf.to_bool.should be_false
48
+ end
49
+
50
+ it "can cnf values" do
51
+ TruthTable.new {|v| v[0] & !v[0] }.cnf.should == "v[0] & !v[0]"
52
+ end
53
+
54
+ it "can formula values" do
55
+ TruthTable.new {|v| v[0] & !v[0] }.formula.to_bool.should be_false
56
+ end
57
+
58
+ end
59
+
60
+
61
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/truthtable/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Tanaka Akira"]
6
+ gem.email = ["akr@fsij.org"]
7
+ gem.description = "The truthtable library generates a truth table from a logical formula written in Ruby.
8
+ The truth table can be converted to a logical formula.
9
+ DNF, CNF and Quine-McCluskey supported."
10
+ gem.summary = "Generate truthtable from logical formula"
11
+ gem.homepage = "http://rubygems.org/gems/truthtable"
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "truthtable"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Truthtable::VERSION
19
+
20
+ gem.add_development_dependency "rspec"
21
+ gem.add_development_dependency "gem-release"
22
+ gem.add_dependency "msgpack"
23
+ end
data/truthtable.rb ADDED
@@ -0,0 +1,345 @@
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
+ require 'truthtable/qm'
28
+
29
+ # = truth table and formula generator from Ruby block
30
+ #
31
+ # The truthtable library generates a truth table from
32
+ # a logical formula written in Ruby.
33
+ # The truth table can be converted to a logical formula.
34
+ #
35
+ # == Author
36
+ #
37
+ # Tanaka Akira <akr@fsij.org>
38
+ #
39
+ # == Feature
40
+ #
41
+ # * generate a truth table from a given block which contains logical formula written in Ruby.
42
+ # * generate a formula from the table:
43
+ # * minimal one (obtained by Quine-McCluskey algorithm)
44
+ # * disjunctive normal form
45
+ # * conjunctive normal form
46
+ #
47
+ # == Usage
48
+ #
49
+ # * require
50
+ #
51
+ # require 'truthtable'
52
+ #
53
+ # * puts, p and pp shows truth table
54
+ #
55
+ # puts TruthTable.new {|v| v[0] & v[1] }
56
+ # #=>
57
+ # v[0] v[1] |
58
+ # ----------+--
59
+ # f f | f
60
+ # f t | f
61
+ # t f | f
62
+ # t t | t
63
+ #
64
+ # p TruthTable.new {|v| v[0] & v[1] }
65
+ # #=> #<TruthTable: !v[0]&!v[1]=>false !v[0]&v[1]=>false v[0]&!v[1]=>false v[0]&v[1]=>true>
66
+ #
67
+ # require 'pp'
68
+ # pp TruthTable.new {|v| v[0] & v[1] }
69
+ # #=>
70
+ # #<TruthTable:
71
+ # !v[0]&!v[1]=>false
72
+ # !v[0]& v[1]=>false
73
+ # v[0]&!v[1]=>false
74
+ # v[0]& v[1]=>true>
75
+ #
76
+ # * formula generation
77
+ #
78
+ # p TruthTable.new {|v| v[0] }.formula #=>"v[0]"
79
+ # p TruthTable.new {|v| !v[0] }.formula #=> "!v[0]"
80
+ # p TruthTable.new {|v| true ^ v[0] }.formula #=> "!v[0]"
81
+ # p TruthTable.new {|v| v[0] & v[1] }.formula #=> "v[0]&v[1]"
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] | v[0]&!v[1]"
84
+ # p TruthTable.new {|v| v[0] == v[1] }.formula #=> "!v[0]&!v[1] | v[0]&v[1]"
85
+ #
86
+ # * shortcuts, && and ||, are also usable but converted to & and |
87
+ #
88
+ # p TruthTable.new {|v| v[0] && v[1] }.formula #=> "v[0]&v[1]"
89
+ # p TruthTable.new {|v| v[0] || v[1] }.formula #=> "v[0] | v[1]"
90
+ #
91
+ # * actually any expression (without side effect)
92
+ #
93
+ # p TruthTable.new {|v| v[0] ? !v[1] : v[1] }.formula #=> "!v[0]&v[1] | v[0]&!v[1]"
94
+ #
95
+ # * any number of inputs
96
+ #
97
+ # p TruthTable.new {|v| [v[0], v[1], v[2], v[3]].grep(true).length <= 3 }.formula
98
+ # #=> "!v[0] | !v[1] | !v[2] | !v[3]"
99
+ #
100
+ class TruthTable
101
+ # :stopdoc:
102
+ class TruthTableObject
103
+ def initialize
104
+ @checked = {}
105
+ @plan = {}
106
+ @order = []
107
+ @queue = []
108
+ end
109
+ attr_reader :plan, :order
110
+
111
+ def next_plan
112
+ @log = {}
113
+ @plan, @order = @queue.shift
114
+ @plan
115
+ end
116
+
117
+ def [](index)
118
+ s = "v[#{index}]"
119
+ if @plan.has_key?(s)
120
+ v = @plan[s]
121
+ else
122
+ fplan = @plan.dup
123
+ fplan[s] = false
124
+ fkey = fplan.keys.sort.map {|k| "#{k}=#{fplan[k]}" }.join(' ')
125
+ @order += [s]
126
+ @plan = fplan
127
+ v = false
128
+ if !@checked[fkey]
129
+ tplan = @plan.dup
130
+ tplan[s] = true
131
+ tkey = tplan.keys.sort.map {|k| "#{k}=#{tplan[k]}" }.join(' ')
132
+ torder = @order.dup
133
+ torder[-1] = s
134
+ @queue.unshift [tplan, torder]
135
+ @checked[tkey] = true
136
+ @checked[fkey] = true
137
+ end
138
+ end
139
+ v
140
+ end
141
+ end
142
+ # :startdoc:
143
+
144
+ def self.test(&b)
145
+ r = []
146
+ o = TruthTableObject.new
147
+ begin
148
+ result = !!b.call(o)
149
+ inputs = o.plan
150
+ order = o.order
151
+ r << [inputs, result, order]
152
+ end while o.next_plan
153
+ r
154
+ end
155
+
156
+ def initialize(&b)
157
+ table = TruthTable.test(&b)
158
+ @table = table
159
+ end
160
+
161
+ def to_s
162
+ r = ''
163
+ names = sort_names(all_names.keys)
164
+ format = ''
165
+ sep = ''
166
+ names.each {|name|
167
+ format << "%-#{name.length}s "
168
+ sep << '-' * (name.length+1)
169
+ }
170
+ format << "| %s\n"
171
+ sep << "+--\n"
172
+ r << sprintf(format, *(names + ['']))
173
+ r << sep
174
+ @table.each {|inputs, output, order|
175
+ h = {}
176
+ each_input(inputs) {|name, input|
177
+ h[name] = input
178
+ }
179
+ args = []
180
+ names.each {|name|
181
+ if h.has_key? name
182
+ args << (h[name] ? 't' : 'f').center(name.length)
183
+ else
184
+ args << '?'.center(name.length)
185
+ end
186
+ }
187
+ args << (output ? 't' : 'f')
188
+ r << sprintf(format, *args)
189
+ }
190
+ r
191
+ end
192
+
193
+ # :stopdoc:
194
+ def inspect
195
+ result = "#<#{self.class}:"
196
+ @table.each {|inputs, output, order|
197
+ term = []
198
+ each_input(inputs) {|name, input|
199
+ if input
200
+ term << name
201
+ else
202
+ term << "!#{name}"
203
+ end
204
+ }
205
+ result << " #{term.join('&')}=>#{output}"
206
+ }
207
+ result << ">"
208
+ result
209
+ end
210
+
211
+ def pretty_print(q)
212
+ q.object_group(self) {
213
+ q.text ':'
214
+ q.breakable
215
+ q.seplist(@table, lambda { q.breakable('; ') }) {|inputs, output, order|
216
+ term = []
217
+ each_input(inputs) {|name, input|
218
+ if input
219
+ term << " #{name}"
220
+ else
221
+ term << "!#{name}"
222
+ end
223
+ }
224
+ q.text "#{term.join('&')}=>#{output}"
225
+ }
226
+ }
227
+ end
228
+
229
+ def all_names
230
+ return @all_names if defined? @all_names
231
+ @all_names = {}
232
+ @table.each {|inputs, output, order|
233
+ order.each {|name|
234
+ if !@all_names.has_key?(name)
235
+ @all_names[name] = @all_names.size
236
+ end
237
+ }
238
+ }
239
+ @all_names
240
+ end
241
+
242
+ def sort_names(names)
243
+ total_order = all_names
244
+ names.sort_by {|n| total_order[n] }
245
+ end
246
+
247
+ def each_input(inputs)
248
+ sort_names(inputs.keys).each {|name|
249
+ yield name, inputs[name]
250
+ }
251
+ end
252
+ # :startdoc:
253
+
254
+ # obtains a formula in disjunctive normal form.
255
+ def dnf
256
+ r = []
257
+ @table.each {|inputs, output|
258
+ return output.to_s if inputs.empty?
259
+ next if !output
260
+ term = []
261
+ each_input(inputs) {|name, input|
262
+ if input
263
+ term << name
264
+ else
265
+ term << "!#{name}"
266
+ end
267
+ }
268
+ r << term.join('&')
269
+ }
270
+ return "false" if r.empty?
271
+ r.join(' | ')
272
+ end
273
+
274
+ # obtains a formula in conjunctive normal form.
275
+ def cnf
276
+ r = []
277
+ @table.each {|inputs, output|
278
+ return output.to_s if inputs.empty?
279
+ next if output
280
+ term = []
281
+ each_input(inputs) {|name, input|
282
+ if input
283
+ term << "!#{name}"
284
+ else
285
+ term << name
286
+ end
287
+ }
288
+ if term.length == 1
289
+ r << term.join('|')
290
+ else
291
+ r << "(#{term.join('|')})"
292
+ end
293
+ }
294
+ return "true" if r.empty?
295
+ r.join(' & ')
296
+ end
297
+
298
+ # obtains a minimal formula using Quine-McCluskey algorithm.
299
+ def formula
300
+ input_names = all_names
301
+ input_names_ary = sort_names(input_names.keys)
302
+ tbl = {}
303
+ @table.each {|inputs, output|
304
+ return output.to_s if inputs.empty?
305
+ inputs2 = [:x] * input_names.length
306
+ inputs.each {|name, input|
307
+ inputs2[input_names[name]] = input ? 1 : 0
308
+ }
309
+ tbl[inputs2] = output ? 1 : 0
310
+ }
311
+ qm = QM.qm(tbl)
312
+ r = []
313
+ qm.each {|term|
314
+ t = []
315
+ num_dontcare = 0
316
+ term.each_with_index {|v, i|
317
+ if v == false
318
+ t << ("!" + input_names_ary[i])
319
+ elsif v == true
320
+ t << input_names_ary[i]
321
+ else # :x
322
+ num_dontcare += 1
323
+ end
324
+ }
325
+ if num_dontcare == term.length
326
+ r << 'true'
327
+ else
328
+ r << t.join('&')
329
+ end
330
+ }
331
+ return "false" if r.empty?
332
+ r.join(' | ')
333
+ end
334
+ end
335
+
336
+ if __FILE__ == $0
337
+ p TruthTable.new {|v| v[0] & v[1] }.formula
338
+ p TruthTable.new {|v| v[0] && v[1] }.formula
339
+ p TruthTable.new {|v| v[0] | v[1] }.formula
340
+ p TruthTable.new {|v| v[0] || v[1] }.formula
341
+ p TruthTable.new {|v| v[0] ^ !v[1] }.formula
342
+ p TruthTable.new {|v| v[0] == v[1] }.formula
343
+ p TruthTable.new {|v| v[0] == v[1] && v[1] != v[2] || v[3] == v[1] }.formula
344
+ end
345
+