logic_tools 0.2.2 → 0.2.3
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.
- checksums.yaml +4 -4
- data/README.md +37 -30
- data/lib/logic_tools.rb +8 -1
- data/lib/logic_tools/logicinput.rb +43 -0
- data/lib/logic_tools/logicparse.rb +13 -14
- data/lib/logic_tools/logicsimplify.rb +93 -85
- data/lib/logic_tools/logictree.rb +327 -324
- data/lib/logic_tools/simplify_bug.txt +16 -5
- data/lib/logic_tools/simplify_qm.rb +31 -21
- data/lib/logic_tools/std_conj.rb +40 -21
- data/lib/logic_tools/std_dij.rb +40 -21
- data/lib/logic_tools/truth_tbl.rb +43 -22
- data/lib/logic_tools/version.rb +1 -1
- metadata +3 -2
@@ -6,19 +6,19 @@ require 'forwardable'
|
|
6
6
|
|
7
7
|
module LogicTools
|
8
8
|
|
9
|
-
##
|
9
|
+
##
|
10
|
+
# Represents a logical variable.
|
10
11
|
class Variable
|
11
12
|
|
12
13
|
include Comparable
|
13
14
|
|
14
|
-
|
15
|
+
# The pool of variables
|
15
16
|
@@variables = {}
|
16
17
|
|
18
|
+
## The current value of the variable (boolean).
|
17
19
|
attr_reader :value
|
18
20
|
|
19
|
-
##
|
20
|
-
# Params:
|
21
|
-
# +name+:: the name of the variable
|
21
|
+
## Creates a new variable with +name+ (the value is set to false).
|
22
22
|
def initialize(name)
|
23
23
|
if @@variables.key?(name.to_s)
|
24
24
|
raise "Variable already present."
|
@@ -30,9 +30,7 @@ module LogicTools
|
|
30
30
|
@@variables[name.to_s] = self
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
# Params:
|
35
|
-
# +value+:: the value to set
|
33
|
+
# Sets the variable to +value+ (boolean).
|
36
34
|
def value=(value)
|
37
35
|
if (value.respond_to?(:to_i))
|
38
36
|
@value = value.to_i == 0 ? false : true
|
@@ -41,16 +39,13 @@ module LogicTools
|
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
44
|
-
## Checks if
|
45
|
-
# Params:
|
46
|
-
# +name+:: the name of the variable to check
|
42
|
+
## Checks if variable +name+ exists.
|
47
43
|
def Variable.exists?(name)
|
48
44
|
return @@variables.has_key?(name.to_s)
|
49
45
|
end
|
50
46
|
|
51
|
-
##
|
52
|
-
#
|
53
|
-
# +name+:: the name of the variable to get
|
47
|
+
## Gets a variable by +name+.
|
48
|
+
# If there is no such variable yet, creates it.
|
54
49
|
def Variable.get(name)
|
55
50
|
var = @@variables[name.to_s]
|
56
51
|
# print "Got var=#{var.to_s} with value=#{var.value}\n" if var
|
@@ -58,49 +53,55 @@ module LogicTools
|
|
58
53
|
return var
|
59
54
|
end
|
60
55
|
|
61
|
-
##
|
56
|
+
## Converts to a string: actually returns a duplicate of the name
|
57
|
+
# of the variable.
|
62
58
|
def to_s
|
63
59
|
@name.dup
|
64
60
|
end
|
65
61
|
|
66
|
-
|
67
|
-
def inspect
|
62
|
+
def inspect # :nodoc:
|
68
63
|
to_s
|
69
64
|
end
|
70
65
|
|
71
|
-
##
|
66
|
+
## Compares with another object using the name of the variable.
|
72
67
|
def <=>(b)
|
73
68
|
self.to_s <=> b.to_s
|
74
69
|
end
|
75
70
|
end
|
76
71
|
|
77
|
-
##
|
72
|
+
##
|
73
|
+
# Represents a node of a tree representing a logical expression.
|
78
74
|
class Node
|
79
75
|
include Enumerable
|
80
76
|
|
81
|
-
##
|
82
|
-
# Return: the variables into an array, sorted by name
|
77
|
+
## Gets a array containing the variables of the tree sorted by name.
|
83
78
|
def getVariables()
|
84
79
|
result = self.getVariablesRecurse
|
85
80
|
return result.flatten.uniq.sort
|
86
81
|
end
|
87
82
|
|
88
|
-
##
|
83
|
+
## Gets the operator.
|
84
|
+
#
|
85
|
+
# Default: +nil+ (none).
|
89
86
|
def op
|
90
87
|
nil
|
91
88
|
end
|
92
89
|
|
93
|
-
##
|
94
|
-
#
|
90
|
+
## Gets number of children.
|
91
|
+
#
|
92
|
+
# Default: +0+ (none).
|
95
93
|
def size
|
96
94
|
0
|
97
95
|
end
|
98
96
|
|
99
|
-
##
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
97
|
+
## Iterates on each line of the truth table obtained from the tree
|
98
|
+
# rooted by current node.
|
99
|
+
#
|
100
|
+
# Iteration parameters (for the current line of the truth table):
|
101
|
+
# * +vars+: the variables of the expression
|
102
|
+
# * +val+: the value of the expression
|
103
|
+
# --
|
104
|
+
# TODO generate an Enumerator.
|
104
105
|
def each_line
|
105
106
|
# Get the variables
|
106
107
|
vars = self.getVariables
|
@@ -118,18 +119,22 @@ module LogicTools
|
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
121
|
-
##
|
122
|
-
#
|
122
|
+
## Iterates over each minterm of the tree rooted from current node.
|
123
|
+
#
|
123
124
|
# Iteration parameters:
|
124
|
-
# vars
|
125
|
+
# * +vars+: the variables of the expression
|
126
|
+
# --
|
127
|
+
# TODO generate an Enumerator.
|
125
128
|
def each_minterm
|
126
129
|
each_line { |vars,val| yield(vars) if val }
|
127
130
|
end
|
128
131
|
|
129
|
-
##
|
130
|
-
#
|
132
|
+
## Iterates over each maxterm of the tree rooted from current node.
|
133
|
+
#
|
131
134
|
# Iteration parameters:
|
132
|
-
# vars
|
135
|
+
# * +vars+: the variables of the expression
|
136
|
+
# --
|
137
|
+
# TODO generate an Enumerator.
|
133
138
|
def each_maxterm
|
134
139
|
each_line do |vars,val|
|
135
140
|
unless val then
|
@@ -139,12 +144,14 @@ module LogicTools
|
|
139
144
|
end
|
140
145
|
end
|
141
146
|
|
142
|
-
## Iterate
|
143
|
-
#
|
147
|
+
## Iterate over the children.
|
148
|
+
#
|
149
|
+
# --
|
150
|
+
# TODO generate an Enumerator.
|
144
151
|
def each
|
145
152
|
end
|
146
153
|
|
147
|
-
## Generates the equivalent standard conjunctive form
|
154
|
+
## Generates the equivalent standard conjunctive form.
|
148
155
|
def to_std_conjunctive
|
149
156
|
# Generate each minterm tree
|
150
157
|
minterms = []
|
@@ -163,8 +170,8 @@ module LogicTools
|
|
163
170
|
return minterms.size == 1 ? minterms[0] : NodeOr.new(*minterms)
|
164
171
|
end
|
165
172
|
|
166
|
-
## Generates the equivalent standard
|
167
|
-
def
|
173
|
+
## Generates the equivalent standard disjunctive form.
|
174
|
+
def to_std_disjunctive
|
168
175
|
# Generate each maxterm tree
|
169
176
|
maxterms = []
|
170
177
|
each_maxterm do |vars|
|
@@ -182,24 +189,24 @@ module LogicTools
|
|
182
189
|
return maxterms.size == 1 ? maxterms[0] : NodeAnd.new(*maxterms)
|
183
190
|
end
|
184
191
|
|
185
|
-
##
|
186
|
-
#
|
192
|
+
## Creates a new tree where the *and*, *or* or *not* operator of
|
193
|
+
# the current node is flattened.
|
194
|
+
#
|
195
|
+
# Default: simply duplicates the node.
|
187
196
|
def flatten
|
188
197
|
return self.dup
|
189
198
|
end
|
190
199
|
|
191
|
-
##
|
200
|
+
## Creates a new tree where all the *and*, *or* and *not* operators
|
201
|
+
# from the current node are flattened.
|
202
|
+
#
|
192
203
|
# Default: simply duplicate.
|
193
|
-
# Return: the new tree
|
194
204
|
def flatten_deep
|
195
205
|
return self.dup
|
196
206
|
end
|
197
207
|
|
198
|
-
##
|
199
|
-
#
|
200
|
-
# Params:
|
201
|
-
# +dop+:: the operator to distribute over
|
202
|
-
# +node+:: the node to distribute with
|
208
|
+
## Creates a new tree where the current node is distributed over +node+
|
209
|
+
# according to the +dop+ operator.
|
203
210
|
def distribute(dop,node)
|
204
211
|
fop = dop == :and ? :or : :and
|
205
212
|
# print "dop=#{dop}, fop=#{fop}, node.op=#{node.op}\n"
|
@@ -208,146 +215,149 @@ module LogicTools
|
|
208
215
|
return NodeNary.make(dop, self, *node)
|
209
216
|
elsif (node.op == fop) then
|
210
217
|
# Opposite operator: can distribute
|
211
|
-
|
212
|
-
NodeNary.make(dop,
|
218
|
+
nchildren = node.map do |child|
|
219
|
+
NodeNary.make(dop,child,self).flatten
|
213
220
|
end
|
214
|
-
return NodeNary.make(fop,*
|
221
|
+
return NodeNary.make(fop,*nchildren).flatten
|
215
222
|
else
|
216
223
|
# Unary operator: simple product
|
217
224
|
return NodeNary.make(dop, self, node)
|
218
225
|
end
|
219
226
|
end
|
220
227
|
|
221
|
-
##
|
222
|
-
#
|
223
|
-
# +flattened
|
224
|
-
# Return: the conversion result
|
228
|
+
## Creates a sum fo product from the tree rooted by current node.
|
229
|
+
#
|
230
|
+
# Argument +flattened+ tells if the tree is already flattend
|
225
231
|
def to_sum_product(flattened = false)
|
226
232
|
return self.dup
|
227
233
|
end
|
228
234
|
|
229
|
-
|
230
|
-
def inspect
|
235
|
+
def inspect # :nodoc:
|
231
236
|
to_s
|
232
237
|
end
|
233
238
|
|
234
|
-
##
|
235
|
-
#
|
239
|
+
## Converts to a symbol.
|
240
|
+
#
|
241
|
+
# There is exactly one symbol per possible tree.
|
236
242
|
def to_sym
|
237
243
|
to_s.to_sym
|
238
244
|
end
|
239
245
|
|
240
|
-
|
241
|
-
def hash
|
246
|
+
def hash # :nodoc:
|
242
247
|
to_sym.hash
|
243
248
|
end
|
244
|
-
def eql?(val)
|
249
|
+
def eql?(val) # :nodoc:
|
245
250
|
self == val
|
246
251
|
end
|
247
252
|
end
|
248
253
|
|
249
|
-
|
254
|
+
|
255
|
+
##
|
256
|
+
# Represents a value node.
|
250
257
|
class NodeValue < Node
|
251
258
|
|
252
259
|
protected
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
def initialize(value)
|
260
|
+
|
261
|
+
## Creates a node by +value+.
|
262
|
+
def initialize(value) # :nodoc:
|
257
263
|
@value = value
|
258
264
|
@sym = @value.to_s.to_sym
|
259
265
|
end
|
266
|
+
|
260
267
|
public
|
261
268
|
|
262
|
-
##
|
263
|
-
#
|
264
|
-
|
269
|
+
## Gets the variables, recursively, without postprocessing.
|
270
|
+
#
|
271
|
+
# Returns the variables into sets of arrays with possible doublon
|
272
|
+
def getVariablesRecurse() # :nodoc:
|
265
273
|
return [ ]
|
266
274
|
end
|
267
275
|
|
268
|
-
##
|
269
|
-
#
|
270
|
-
|
271
|
-
|
272
|
-
return false unless n.is_a?(NodeValue)
|
273
|
-
return self.eval() == n.eval()
|
276
|
+
## Compares with +node+.
|
277
|
+
def ==(node) # :nodoc:
|
278
|
+
return false unless node.is_a?(NodeValue)
|
279
|
+
return self.eval() == node.eval()
|
274
280
|
end
|
275
281
|
|
276
|
-
##
|
282
|
+
## Computes the value of the node.
|
277
283
|
def eval
|
278
284
|
return @value
|
279
285
|
end
|
280
286
|
|
281
|
-
##
|
282
|
-
def to_sym
|
287
|
+
## Converts to a symbol.
|
288
|
+
def to_sym # :nodoc:
|
283
289
|
return @sym
|
284
290
|
end
|
285
291
|
|
286
292
|
## Converts to a string.
|
287
|
-
def to_s
|
293
|
+
def to_s # :nodoc:
|
288
294
|
return @value.to_s
|
289
295
|
end
|
290
296
|
end
|
291
297
|
|
292
|
-
##
|
298
|
+
##
|
299
|
+
# Represents a true node.
|
293
300
|
class NodeTrue < NodeValue
|
294
|
-
##
|
301
|
+
## Creates as a NodeValue whose value is true.
|
295
302
|
def initialize
|
296
303
|
super(true)
|
297
304
|
end
|
298
305
|
end
|
299
306
|
|
300
|
-
##
|
307
|
+
##
|
308
|
+
# Represents a false node
|
301
309
|
class NodeFalse < NodeValue
|
302
|
-
##
|
310
|
+
## Creates as a NodeValue whose value is false.
|
303
311
|
def initialize
|
304
312
|
super(false)
|
305
313
|
end
|
306
314
|
end
|
307
315
|
|
308
|
-
|
316
|
+
|
317
|
+
##
|
318
|
+
# Represents a variable node.
|
309
319
|
class NodeVar < Node
|
320
|
+
|
321
|
+
## The variable held by the node.
|
310
322
|
attr_reader :variable
|
311
323
|
|
312
|
-
##
|
313
|
-
# Params:
|
314
|
-
# +name+:: the name of the variable
|
324
|
+
## Creates a node with variable +name+.
|
315
325
|
def initialize(name)
|
316
326
|
@variable = Variable.get(name)
|
317
327
|
@sym = @variable.to_s.to_sym
|
318
328
|
end
|
319
329
|
|
320
|
-
##
|
330
|
+
## Computes the value of the node.
|
321
331
|
def eval()
|
322
332
|
return @variable.value
|
323
333
|
end
|
324
334
|
|
325
|
-
##
|
326
|
-
def to_sym
|
335
|
+
## Converts to a symbol.
|
336
|
+
def to_sym # :nodoc:
|
327
337
|
return @sym
|
328
338
|
end
|
329
339
|
|
330
|
-
##
|
331
|
-
#
|
332
|
-
|
340
|
+
## Gets the variables, recursively, without postprocessing.
|
341
|
+
#
|
342
|
+
# Returns the variables into sets of arrays with possible doublon
|
343
|
+
def getVariablesRecurse() # :nodoc:
|
333
344
|
return [ @variable ]
|
334
345
|
end
|
335
346
|
|
336
|
-
##
|
337
|
-
#
|
338
|
-
|
339
|
-
|
340
|
-
return false unless n.is_a?(NodeVar)
|
341
|
-
return self.variable == n.variable
|
347
|
+
## Compares with +node+.
|
348
|
+
def ==(node) # :nodoc:
|
349
|
+
return false unless node.is_a?(NodeVar)
|
350
|
+
return self.variable == node.variable
|
342
351
|
end
|
343
352
|
|
344
|
-
## Converts to a string
|
345
|
-
def to_s
|
353
|
+
## Converts to a string.
|
354
|
+
def to_s # :nodoc:
|
346
355
|
return variable.to_s
|
347
356
|
end
|
348
357
|
end
|
349
358
|
|
350
|
-
##
|
359
|
+
##
|
360
|
+
# Represents an operator node with multiple children.
|
351
361
|
class NodeNary < Node
|
352
362
|
extend Forwardable
|
353
363
|
include Enumerable
|
@@ -355,35 +365,29 @@ module LogicTools
|
|
355
365
|
attr_reader :op
|
356
366
|
|
357
367
|
protected
|
358
|
-
##
|
359
|
-
#
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
unless son.is_a?(Node) then
|
366
|
-
raise ArgumentError.new("Not a valid class for a son: "+
|
367
|
-
"#{son.class}")
|
368
|
+
## Creates a node with operator +op+ and +children+.
|
369
|
+
def initialize(op,*children) # :nodoc:
|
370
|
+
# Check the children
|
371
|
+
children.each do |child|
|
372
|
+
unless child.is_a?(Node) then
|
373
|
+
raise ArgumentError.new("Not a valid class for a child: "+
|
374
|
+
"#{child.class}")
|
368
375
|
end
|
369
376
|
end
|
370
|
-
#
|
377
|
+
# Children are ok
|
371
378
|
@op = op.to_sym
|
372
|
-
@
|
379
|
+
@children = children
|
373
380
|
@sym = self.to_s.to_sym
|
374
381
|
end
|
375
382
|
|
376
383
|
public
|
377
|
-
##
|
378
|
-
|
379
|
-
# +op+:: the operator name
|
380
|
-
# +sons+:: the sons
|
381
|
-
def NodeNary.make(op,*sons)
|
384
|
+
## Creates a node with operator +op+ and +children+ (factory method).
|
385
|
+
def NodeNary.make(op,*children)
|
382
386
|
case op
|
383
387
|
when :or
|
384
|
-
return NodeOr.new(*
|
388
|
+
return NodeOr.new(*children)
|
385
389
|
when :and
|
386
|
-
return NodeAnd.new(*
|
390
|
+
return NodeAnd.new(*children)
|
387
391
|
else
|
388
392
|
raise ArgumentError.new("Not a valid operator: #{op}")
|
389
393
|
end
|
@@ -391,60 +395,55 @@ module LogicTools
|
|
391
395
|
|
392
396
|
|
393
397
|
# Also acts as an array of nodes
|
394
|
-
def_delegators :@
|
395
|
-
|
396
|
-
|
398
|
+
def_delegators :@children, :[], :empty?, :size
|
399
|
+
|
400
|
+
## Iterates over the children.
|
401
|
+
def each(&blk) # :nodoc:
|
402
|
+
@children.each(&blk)
|
397
403
|
return self
|
398
404
|
end
|
399
|
-
|
400
|
-
|
401
|
-
# return self
|
402
|
-
# end
|
403
|
-
# def sort!
|
404
|
-
# @sons.sort_by! {|son| son.sym }
|
405
|
-
# return self
|
406
|
-
# end
|
405
|
+
|
406
|
+
## Creates a new node whose childrens are sorted.
|
407
407
|
def sort
|
408
|
-
return NodeNary.make(@op,*@
|
408
|
+
return NodeNary.make(@op,*@children.sort_by {|child| child.to_sym })
|
409
409
|
end
|
410
410
|
|
411
|
-
##
|
411
|
+
## Creates a new node without duplicate in the children.
|
412
412
|
def uniq(&blk)
|
413
413
|
if blk then
|
414
|
-
|
414
|
+
nchildren = @children.uniq(&blk)
|
415
415
|
else
|
416
|
-
|
416
|
+
nchildren = @children.uniq { |child| child.to_sym }
|
417
417
|
end
|
418
|
-
if
|
419
|
-
return
|
418
|
+
if nchildren.size == 1 then
|
419
|
+
return nchildren[0]
|
420
420
|
else
|
421
|
-
return NodeNary.make(@op,*
|
421
|
+
return NodeNary.make(@op,*nchildren)
|
422
422
|
end
|
423
423
|
end
|
424
424
|
|
425
|
-
##
|
426
|
-
def to_sym
|
425
|
+
## Converts to a symbol.
|
426
|
+
def to_sym # :nodoc:
|
427
427
|
return @sym
|
428
428
|
end
|
429
429
|
|
430
|
-
##
|
431
|
-
#
|
432
|
-
|
433
|
-
|
434
|
-
|
430
|
+
## Gets the variables, recursively, without postprocessing.
|
431
|
+
#
|
432
|
+
# Returns the variables into sets of arrays with possible doublon
|
433
|
+
def getVariablesRecurse() # :nodoc:
|
434
|
+
return @children.reduce([]) do |res,child|
|
435
|
+
res.concat(child.getVariablesRecurse)
|
435
436
|
end
|
436
437
|
end
|
437
438
|
|
438
|
-
##
|
439
|
-
#
|
440
|
-
|
441
|
-
|
442
|
-
return false unless n.is_a?(Node)
|
443
|
-
return false unless self.op == n.op
|
439
|
+
## Compares with +node+.
|
440
|
+
def ==(node) # :nodoc:
|
441
|
+
return false unless node.is_a?(Node)
|
442
|
+
return false unless self.op == node.op
|
444
443
|
# There is no find_with_index!
|
445
|
-
# return ! @
|
446
|
-
@
|
447
|
-
return false if
|
444
|
+
# return ! @children.find_with_index {|child,i| child != node[i] }
|
445
|
+
@children.each_with_index do |child,i|
|
446
|
+
return false if child != node[i]
|
448
447
|
end
|
449
448
|
return true
|
450
449
|
end
|
@@ -457,7 +456,7 @@ module LogicTools
|
|
457
456
|
# fop = @op == :and ? :or : :and
|
458
457
|
# # Gather the terms by factor
|
459
458
|
# terms = Hash.new {|h,k| h[k] = [] }
|
460
|
-
# @
|
459
|
+
# @children.each do |term|
|
461
460
|
# if (term.op == fop) then
|
462
461
|
# # There are factors
|
463
462
|
# term.each { |fact| terms[fact] << term }
|
@@ -468,32 +467,34 @@ module LogicTools
|
|
468
467
|
# end
|
469
468
|
# # Keep only the shortest term per factor
|
470
469
|
# terms.each_key {|k| terms[k] = terms[k].min_by {|term| term.size} }
|
471
|
-
#
|
470
|
+
# nchildren = terms.values
|
472
471
|
# # Avoid doublons
|
473
|
-
#
|
472
|
+
# nchildren.uniq!
|
474
473
|
# # Generate the result
|
475
|
-
# if (
|
476
|
-
# return
|
474
|
+
# if (nchildren.size == 1)
|
475
|
+
# return nchildren[0]
|
477
476
|
# else
|
478
|
-
# return NodeNary.make(@op,*
|
477
|
+
# return NodeNary.make(@op,*nchildren)
|
479
478
|
# end
|
480
479
|
# end
|
481
480
|
|
482
|
-
##
|
483
|
-
#
|
481
|
+
## Reduces a node: remove its redundancies using the absbortion rules.
|
482
|
+
#
|
483
|
+
# --
|
484
|
+
# TODO consider the X~X and X+~X cases.
|
484
485
|
def reduce
|
485
486
|
# The operator used for the factors
|
486
487
|
fop = @op == :and ? :or : :and
|
487
488
|
# Gather the terms converted to a sorted string for fast
|
488
489
|
# comparison
|
489
|
-
terms = @
|
490
|
-
if (
|
491
|
-
[
|
490
|
+
terms = @children.map do |child|
|
491
|
+
if (child.op == fop) then
|
492
|
+
[ child, child.sort.to_s ]
|
492
493
|
else
|
493
|
-
[
|
494
|
+
[ child, child.to_s ]
|
494
495
|
end
|
495
496
|
end
|
496
|
-
|
497
|
+
nchildren = []
|
497
498
|
# Keep only the terms that do not contain another one
|
498
499
|
terms.each_with_index do |term0,i|
|
499
500
|
skipped = false
|
@@ -505,48 +506,47 @@ module LogicTools
|
|
505
506
|
break
|
506
507
|
end
|
507
508
|
end
|
508
|
-
|
509
|
+
nchildren << term0[0] unless skipped # Term has not been skipped
|
509
510
|
end
|
510
|
-
# Avoid
|
511
|
-
|
511
|
+
# Avoid duplicates
|
512
|
+
nchildren.uniq!
|
512
513
|
# Generate the result
|
513
|
-
if (
|
514
|
-
return
|
514
|
+
if (nchildren.size == 1)
|
515
|
+
return nchildren[0]
|
515
516
|
else
|
516
|
-
return NodeNary.make(@op,*
|
517
|
+
return NodeNary.make(@op,*nchildren)
|
517
518
|
end
|
518
519
|
end
|
519
520
|
|
520
521
|
## Flatten ands, ors and nots.
|
521
|
-
#
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
nsons.push(*son)
|
522
|
+
def flatten # :nodoc:
|
523
|
+
return NodeNary.make(@op,*(@children.reduce([]) do |nchildren,child|
|
524
|
+
if (child.op == self.op) then
|
525
|
+
nchildren.push(*child)
|
526
526
|
else
|
527
|
-
|
527
|
+
nchildren << child
|
528
528
|
end
|
529
529
|
end)).reduce
|
530
530
|
end
|
531
531
|
|
532
|
-
##
|
533
|
-
#
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
532
|
+
## Creates a new tree where all the *and*, *or* and *not* operators
|
533
|
+
# from the current node are flattened.
|
534
|
+
#
|
535
|
+
# Default: simply duplicate.
|
536
|
+
def flatten_deep # :nodoc:
|
537
|
+
return NodeNary.make(@op,*(@children.reduce([]) do |nchildren,child|
|
538
|
+
child = child.flatten_deep
|
539
|
+
if (child.op == self.op) then
|
540
|
+
nchildren.push(*child)
|
539
541
|
else
|
540
|
-
|
542
|
+
nchildren << child
|
541
543
|
end
|
542
544
|
end)).reduce
|
543
545
|
end
|
544
546
|
|
545
|
-
##
|
546
|
-
#
|
547
|
-
|
548
|
-
# +node+:: the node to distribute with
|
549
|
-
def distribute(dop,node)
|
547
|
+
## Creates a new tree where the current node is distributed over +node+
|
548
|
+
# according to the +dop+ operator.
|
549
|
+
def distribute(dop,node) # :nodoc:
|
550
550
|
fop = dop == :and ? :or : :and
|
551
551
|
# print "dop=#{dop} fop=#{fop} self.op=#{@op}\n"
|
552
552
|
if (@op == dop) then
|
@@ -556,60 +556,61 @@ module LogicTools
|
|
556
556
|
# self operator if fop
|
557
557
|
if (node.op == fop) then
|
558
558
|
# node operator is also fop: (a+b)(c+d) or ab+cd case
|
559
|
-
|
560
|
-
self.each do |
|
561
|
-
node.each do |
|
562
|
-
# print "
|
563
|
-
|
564
|
-
|
559
|
+
nchildren = []
|
560
|
+
self.each do |child0|
|
561
|
+
node.each do |child1|
|
562
|
+
# print "child0=#{child0}, child1=#{child1}\n"
|
563
|
+
nchildren <<
|
564
|
+
NodeNary.make(dop, child0, child1).flatten
|
565
|
+
# print "nchildren=#{nchildren}\n"
|
565
566
|
end
|
566
567
|
end
|
567
|
-
return NodeNary.make(fop,*
|
568
|
+
return NodeNary.make(fop,*nchildren).flatten
|
568
569
|
else
|
569
570
|
# node operator is not fop: (a+b)c or ab+c case
|
570
|
-
|
571
|
-
NodeNary.make(dop,
|
571
|
+
nchildren = self.map do |child|
|
572
|
+
NodeNary.make(dop,child,node).flatten
|
572
573
|
end
|
573
|
-
return NodeNary.make(fop,*
|
574
|
+
return NodeNary.make(fop,*nchildren).flatten
|
574
575
|
end
|
575
576
|
end
|
576
577
|
end
|
577
578
|
end
|
578
579
|
|
579
|
-
|
580
|
+
|
581
|
+
##
|
582
|
+
# Represents an AND node
|
580
583
|
class NodeAnd < NodeNary
|
581
|
-
|
582
|
-
#
|
583
|
-
|
584
|
-
|
585
|
-
super(:and,*sons)
|
584
|
+
|
585
|
+
# Creates a new AND node with +children+.
|
586
|
+
def initialize(*children)
|
587
|
+
super(:and,*children)
|
586
588
|
end
|
587
589
|
|
588
590
|
## Duplicates the node.
|
589
|
-
def dup
|
590
|
-
return NodeAnd.new(@
|
591
|
+
def dup # :nodoc:
|
592
|
+
return NodeAnd.new(@children.map(&:dup))
|
591
593
|
end
|
592
594
|
|
593
|
-
##
|
595
|
+
## Computes the value of the node.
|
594
596
|
def eval()
|
595
|
-
return !@
|
597
|
+
return !@children.any? {|child| child.eval() == false }
|
596
598
|
end
|
597
599
|
|
598
|
-
##
|
599
|
-
#
|
600
|
-
# +flattened
|
601
|
-
|
602
|
-
def to_sum_product(flattened = false)
|
600
|
+
## Creates a sum fo product from the tree rooted by current node.
|
601
|
+
#
|
602
|
+
# Argument +flattened+ tells if the tree is already flattend
|
603
|
+
def to_sum_product(flattened = false) # :nodoc:
|
603
604
|
# Flatten if required
|
604
605
|
node = flattened ? self : self.flatten_deep
|
605
606
|
# print "node = #{node}\n"
|
606
|
-
# Convert each
|
607
|
-
|
608
|
-
# print "
|
607
|
+
# Convert each child to sum of product
|
608
|
+
nchildren = node.map {|child| child.to_sum_product(true) }
|
609
|
+
# print "nchildren = #{nchildren}\n"
|
609
610
|
# Distribute
|
610
|
-
while(
|
611
|
+
while(nchildren.size>1)
|
611
612
|
dist = []
|
612
|
-
|
613
|
+
nchildren.each_slice(2) do |left,right|
|
613
614
|
# print "left=#{left}, right=#{right}\n"
|
614
615
|
if right then
|
615
616
|
dist << (left.op == :or ? left.distribute(:and,right) :
|
@@ -619,28 +620,28 @@ module LogicTools
|
|
619
620
|
end
|
620
621
|
end
|
621
622
|
# print "dist=#{dist}\n"
|
622
|
-
|
623
|
+
nchildren = dist
|
623
624
|
end
|
624
|
-
# print "Distributed
|
625
|
+
# print "Distributed nchildren=#{nchildren}\n"
|
625
626
|
# Generate the or
|
626
|
-
if (
|
627
|
-
return NodeOr.new(*
|
627
|
+
if (nchildren.size > 1)
|
628
|
+
return NodeOr.new(*nchildren)
|
628
629
|
else
|
629
|
-
return
|
630
|
+
return nchildren[0]
|
630
631
|
end
|
631
632
|
end
|
632
633
|
|
633
634
|
## Convert to a string.
|
634
|
-
def to_s
|
635
|
+
def to_s # :nodoc:
|
635
636
|
return @str if @str
|
636
637
|
@str = ""
|
637
|
-
# Convert the
|
638
|
-
@
|
639
|
-
if (
|
638
|
+
# Convert the children to a string
|
639
|
+
@children.each do |child|
|
640
|
+
if (child.op == :or) then
|
640
641
|
# Yes, need parenthesis
|
641
|
-
@str << ( "(" +
|
642
|
+
@str << ( "(" + child.to_s + ")" )
|
642
643
|
else
|
643
|
-
@str <<
|
644
|
+
@str << child.to_s
|
644
645
|
end
|
645
646
|
end
|
646
647
|
return @str
|
@@ -648,159 +649,161 @@ module LogicTools
|
|
648
649
|
end
|
649
650
|
|
650
651
|
|
651
|
-
##
|
652
|
+
##
|
653
|
+
# Represents an OR node
|
652
654
|
class NodeOr < NodeNary
|
653
|
-
|
654
|
-
#
|
655
|
-
def initialize(*
|
656
|
-
super(:or,*
|
655
|
+
|
656
|
+
# Creates a new OR node with +children+.
|
657
|
+
def initialize(*children)
|
658
|
+
super(:or,*children)
|
657
659
|
end
|
658
660
|
|
659
|
-
## Duplicates the node
|
660
|
-
def dup
|
661
|
-
return NodeOr.new(@
|
661
|
+
## Duplicates the node.
|
662
|
+
def dup # :nodoc:
|
663
|
+
return NodeOr.new(@children.map(&:dup))
|
662
664
|
end
|
663
665
|
|
664
|
-
##
|
666
|
+
## Computes the value of the node.
|
665
667
|
def eval
|
666
|
-
return @
|
668
|
+
return @children.any? {|child| child.eval() == true }
|
667
669
|
end
|
668
670
|
|
669
|
-
##
|
670
|
-
#
|
671
|
-
#
|
672
|
-
def to_sum_product(flattened = false)
|
673
|
-
return NodeOr.new(
|
671
|
+
## Creates a sum fo product from the tree rooted by current node.
|
672
|
+
#
|
673
|
+
# Argument +flattened+ tells if the tree is already flattend
|
674
|
+
def to_sum_product(flattened = false) # :nodoc:
|
675
|
+
return NodeOr.new(
|
676
|
+
*@children.map {|child| child.to_sum_product(flatten) } )
|
674
677
|
end
|
675
678
|
|
676
|
-
## Converts to a string
|
677
|
-
def to_s
|
679
|
+
## Converts to a string.
|
680
|
+
def to_s # :nodoc:
|
678
681
|
return @str if @str
|
679
|
-
# Convert the
|
680
|
-
@str = @
|
682
|
+
# Convert the children to string a insert "+" between them
|
683
|
+
@str = @children.join("+")
|
681
684
|
return @str
|
682
685
|
end
|
683
686
|
end
|
684
687
|
|
685
688
|
|
686
689
|
|
687
|
-
|
690
|
+
##
|
691
|
+
# Represents an unary node.
|
688
692
|
class NodeUnary < Node
|
689
|
-
attr_reader :op, :
|
690
|
-
|
691
|
-
##
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
def initialize(op,son)
|
696
|
-
if !son.is_a?(Node) then
|
697
|
-
raise ArgumentError.new("Not a valid object for son.")
|
693
|
+
attr_reader :op, :child
|
694
|
+
|
695
|
+
## Creates a node with operator +op+ and a +child+.
|
696
|
+
def initialize(op,child)
|
697
|
+
if !child.is_a?(Node) then
|
698
|
+
raise ArgumentError.new("Not a valid object for child.")
|
698
699
|
end
|
699
700
|
@op = op.to_sym
|
700
|
-
@
|
701
|
+
@child = child
|
701
702
|
@sym = self.to_s.to_sym
|
702
703
|
end
|
703
704
|
|
704
|
-
##
|
705
|
-
def size
|
705
|
+
## Gets the number of children.
|
706
|
+
def size # :nodoc:
|
706
707
|
1
|
707
708
|
end
|
708
709
|
|
709
|
-
# ## Set the
|
710
|
-
# # @param
|
711
|
-
# def
|
710
|
+
# ## Set the child node
|
711
|
+
# # @param child the node to set
|
712
|
+
# def child=(child)
|
712
713
|
# # Checks it is a valid object
|
713
|
-
# if !
|
714
|
-
# raise ArgumentError.new("Not a valid object for
|
714
|
+
# if !child.is_a?(Node) then
|
715
|
+
# raise ArgumentError.new("Not a valid object for child.")
|
715
716
|
# else
|
716
|
-
# @
|
717
|
+
# @child = child
|
717
718
|
# end
|
718
719
|
# end
|
719
720
|
|
720
|
-
##
|
721
|
-
#
|
722
|
-
|
723
|
-
|
721
|
+
## Gets the variables, recursively, without postprocessing.
|
722
|
+
#
|
723
|
+
# Returns the variables into sets of arrays with possible doublon
|
724
|
+
def getVariablesRecurse() # :nodoc:
|
725
|
+
return @child.getVariablesRecurse
|
724
726
|
end
|
725
727
|
|
726
|
-
##
|
727
|
-
def each
|
728
|
-
yield(@
|
728
|
+
## Iterates over the children.
|
729
|
+
def each # :nodoc:
|
730
|
+
yield(@child)
|
729
731
|
end
|
730
732
|
|
731
|
-
##
|
732
|
-
#
|
733
|
-
|
734
|
-
def ==(n)
|
735
|
-
return false unless n.is_a?(Node)
|
733
|
+
## Compares with +node+.
|
734
|
+
def ==(node) # :nodoc:
|
735
|
+
return false unless node.is_a?(Node)
|
736
736
|
return false unless self.op == n.op
|
737
|
-
return self.
|
737
|
+
return self.child == node.child
|
738
738
|
end
|
739
739
|
|
740
|
-
##
|
741
|
-
def to_sym
|
740
|
+
## Converts to a symbol.
|
741
|
+
def to_sym # :nodoc:
|
742
742
|
return @sym
|
743
743
|
end
|
744
744
|
end
|
745
745
|
|
746
|
-
##
|
746
|
+
##
|
747
|
+
# Represents a NOT node.
|
747
748
|
class NodeNot < NodeUnary
|
748
|
-
##
|
749
|
-
|
750
|
-
|
751
|
-
def initialize(son)
|
752
|
-
super(:not,son)
|
749
|
+
## Creates a NOT node with a +child+.
|
750
|
+
def initialize(child)
|
751
|
+
super(:not,child)
|
753
752
|
end
|
754
753
|
|
755
754
|
## Duplicates the node.
|
756
|
-
def dup
|
757
|
-
return NodeNot.new(@
|
755
|
+
def dup # :nodoc:
|
756
|
+
return NodeNot.new(@child.dup)
|
758
757
|
end
|
759
758
|
|
760
|
-
##
|
761
|
-
def eval
|
762
|
-
return !
|
759
|
+
## Computes the value of the node.
|
760
|
+
def eval # :nodoc:
|
761
|
+
return !child.eval
|
763
762
|
end
|
764
763
|
|
765
|
-
##
|
766
|
-
#
|
767
|
-
|
768
|
-
|
769
|
-
|
764
|
+
## Creates a new tree where the *and*, *or* or *not* operator of
|
765
|
+
# the current node is flattened.
|
766
|
+
#
|
767
|
+
# Default: simply duplicates the node.
|
768
|
+
def flatten # :nodoc:
|
769
|
+
nchild = @child.flatten
|
770
|
+
if nchild.op == :not then
|
771
|
+
return nchild.child
|
770
772
|
else
|
771
|
-
return NodeNot.new(
|
773
|
+
return NodeNot.new(nchild)
|
772
774
|
end
|
773
775
|
end
|
774
776
|
|
775
|
-
##
|
776
|
-
#
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
777
|
+
## Creates a new tree where all the *and*, *or* and *not* operators
|
778
|
+
# from the current node are flattened.
|
779
|
+
#
|
780
|
+
# Default: simply duplicate.
|
781
|
+
def flatten_deep # :nodoc:
|
782
|
+
nchild = @child.flatten_deep
|
783
|
+
if nchild.op == :not then
|
784
|
+
return nchild.child
|
781
785
|
else
|
782
|
-
return NodeNot.new(
|
786
|
+
return NodeNot.new(child)
|
783
787
|
end
|
784
788
|
end
|
785
789
|
|
786
|
-
##
|
787
|
-
#
|
788
|
-
# +flattened
|
789
|
-
|
790
|
-
|
791
|
-
return NodeNot.new(@son.to_sum_product(flatten))
|
790
|
+
## Creates a sum fo product from the tree rooted by current node.
|
791
|
+
#
|
792
|
+
# Argument +flattened+ tells if the tree is already flattend
|
793
|
+
def to_sum_product(flattened = false) # :nodoc:
|
794
|
+
return NodeNot.new(@child.to_sum_product(flatten))
|
792
795
|
end
|
793
796
|
|
794
797
|
## Converts to a string.
|
795
|
-
def to_s
|
798
|
+
def to_s # :nodoc:
|
796
799
|
return @str if @str
|
797
|
-
# Is the
|
798
|
-
if
|
800
|
+
# Is the child a binary node?
|
801
|
+
if child.op == :or || child.op == :and then
|
799
802
|
# Yes must put parenthesis
|
800
|
-
@str = "~(" +
|
803
|
+
@str = "~(" + child.to_s + ")"
|
801
804
|
else
|
802
805
|
# No
|
803
|
-
@str = "~" +
|
806
|
+
@str = "~" + child.to_s
|
804
807
|
end
|
805
808
|
return @str
|
806
809
|
end
|