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