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.
@@ -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
+