boolean_simplifier 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ ## Boolean Simplifier
2
+
3
+ A hobbyist's attempt to simplify boolean expressions using a recursive, object-oriented strategy.
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ puts BooleanSimplifier.simplify("a && a || !!b")
9
+ # a || b
10
+
11
+ puts BooleanSimplifier.simplify("a && !a")
12
+ # false
13
+
14
+ puts BooleanSimplifier.simplify("!a || !b && (true || false)")
15
+ # !(a && b)
16
+ ```
17
+
18
+ ## How it works
19
+
20
+ The boolean expression is parsed and an object graph is constructed. This graph consists of instances of Negation, Conjunction, Disjunction. It also contains literal strings.
21
+
22
+ A set of boolean simplification rules is applied to the expression and recursively to its subexpressions. These rules take an arbitrary expression as input and return a new expression if the rule applies. If not, the original expression is returned.
23
+
24
+ The base case for this recursive algorithm is when no improvement is made to the given expression after iterating through all simplification rules.
25
+
26
+ ## Limitations
27
+
28
+ Ruby does not implement tail recursion. Therefore, you may run into a SystemStackError when simplifying large expressions.
29
+
30
+ I haven't figured out every simplification rule, or the most efficient way to apply them. Therefore, you may find that some expressions are only partially simplified.
31
+
32
+ ## Contribution
33
+
34
+ Open an issue, or send a pull request. I'd appreciate all feedback.
35
+
36
+ [Twitter](https://twitter.com/cpatuzzo)
@@ -0,0 +1,20 @@
1
+ require "treetop"
2
+ require "polyglot"
3
+
4
+ require "boolean_simplifier/base"
5
+ require "boolean_simplifier/expression"
6
+ require "boolean_simplifier/expression/conjunction"
7
+ require "boolean_simplifier/expression/disjunction"
8
+ require "boolean_simplifier/expression/negation"
9
+ require "boolean_simplifier/expression_parser"
10
+ require "boolean_simplifier/expression_simplifier"
11
+ require "boolean_simplifier/grammar/boolean_expression"
12
+ require "boolean_simplifier/rule"
13
+ require "boolean_simplifier/rule/absorbtion"
14
+ require "boolean_simplifier/rule/annihilator"
15
+ require "boolean_simplifier/rule/complementation"
16
+ require "boolean_simplifier/rule/de_morgans"
17
+ require "boolean_simplifier/rule/distributivity"
18
+ require "boolean_simplifier/rule/double_negation"
19
+ require "boolean_simplifier/rule/idempotence"
20
+ require "boolean_simplifier/rule/identity"
@@ -0,0 +1,12 @@
1
+ module BooleanSimplifier
2
+ def version
3
+ "1.0.0"
4
+ end
5
+
6
+ def simplify(string)
7
+ expression = ExpressionParser.parse(string)
8
+ ExpressionSimplifier.simplify(expression).to_s
9
+ end
10
+
11
+ extend self
12
+ end
@@ -0,0 +1,22 @@
1
+ class Expression
2
+
3
+ attr_accessor :parts
4
+
5
+ def ==(other)
6
+ to_s == other.to_s
7
+ end
8
+
9
+ def to_s
10
+ raise NotImplementedError
11
+ end
12
+
13
+ private
14
+ def bracket(expression, *classes)
15
+ if classes.include?(expression.class)
16
+ "(#{expression})"
17
+ else
18
+ expression
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,14 @@
1
+ class Conjunction < Expression
2
+
3
+ def initialize(a, b)
4
+ @parts = [a, b]
5
+ end
6
+
7
+ def to_s
8
+ a = bracket(parts[0], Disjunction)
9
+ b = bracket(parts[1], Disjunction)
10
+
11
+ "#{a} && #{b}"
12
+ end
13
+
14
+ end
@@ -0,0 +1,11 @@
1
+ class Disjunction < Expression
2
+
3
+ def initialize(a, b)
4
+ @parts = [a, b]
5
+ end
6
+
7
+ def to_s
8
+ parts.join(" || ")
9
+ end
10
+
11
+ end
@@ -0,0 +1,12 @@
1
+ class Negation < Expression
2
+
3
+ def initialize(expression)
4
+ @parts = [expression]
5
+ end
6
+
7
+ def to_s
8
+ expr = bracket(parts[0], Conjunction, Disjunction)
9
+ "!#{expr}"
10
+ end
11
+
12
+ end
@@ -0,0 +1,16 @@
1
+ module ExpressionParser
2
+ def parse(string)
3
+ parser = BooleanExpressionParser.new
4
+
5
+ ast = parser.parse(string)
6
+ if ast.nil?
7
+ raise ParseError, parser.failure_reason
8
+ end
9
+
10
+ ast.to_expression
11
+ end
12
+
13
+ class ::ParseError < StandardError; end
14
+
15
+ extend self
16
+ end
@@ -0,0 +1,39 @@
1
+ module ExpressionSimplifier
2
+ def simplify(expression)
3
+ until_no_improvement(expression) do
4
+ simplify_parts(expression)
5
+ simplify_root(expression)
6
+ end
7
+ end
8
+
9
+ private
10
+ def until_no_improvement(expression, &block)
11
+ begin
12
+ break if trivial?(expression)
13
+ previous = expression.dup
14
+ expression = yield
15
+ end until previous == expression
16
+
17
+ expression
18
+ end
19
+
20
+ def simplify_parts(expression)
21
+ expression.parts.each.with_index do |part, index|
22
+ expression.parts[index] = simplify(part)
23
+ end
24
+ end
25
+
26
+ def simplify_root(expression)
27
+ Rule.collection.each do |rule|
28
+ expression = rule.simplify(expression)
29
+ end
30
+
31
+ expression
32
+ end
33
+
34
+ def trivial?(expression)
35
+ !expression.respond_to?(:parts)
36
+ end
37
+
38
+ extend self
39
+ end
@@ -0,0 +1,71 @@
1
+ grammar BooleanExpression
2
+
3
+ rule expression
4
+ space disjunctive space {
5
+ def to_expression
6
+ disjunctive.to_expression
7
+ end
8
+ }
9
+ end
10
+
11
+ rule disjunctive
12
+ a:conjunctive space "||" space b:disjunctive {
13
+ def to_expression
14
+ Disjunction.new(a.to_expression, b.to_expression)
15
+ end
16
+ } /
17
+ conjunctive
18
+ end
19
+
20
+ rule conjunctive
21
+ a:negative space "&&" space b:conjunctive {
22
+ def to_expression
23
+ Conjunction.new(a.to_expression, b.to_expression)
24
+ end
25
+ } /
26
+ negative
27
+ end
28
+
29
+ rule negative
30
+ "!" negative {
31
+ def to_expression
32
+ Negation.new(negative.to_expression)
33
+ end
34
+ } /
35
+ primary
36
+ end
37
+
38
+ rule primary
39
+ boolean / variable / "(" expression ")" {
40
+ def to_expression
41
+ expression.to_expression
42
+ end
43
+ }
44
+ end
45
+
46
+ rule boolean
47
+ "true" {
48
+ def to_expression
49
+ true
50
+ end
51
+ } /
52
+ "false" {
53
+ def to_expression
54
+ false
55
+ end
56
+ }
57
+ end
58
+
59
+ rule variable
60
+ [a-zA-Z0-9_]+ {
61
+ def to_expression
62
+ text_value
63
+ end
64
+ }
65
+ end
66
+
67
+ rule space
68
+ " "*
69
+ end
70
+
71
+ end
@@ -0,0 +1,7 @@
1
+ module Rule
2
+ def collection
3
+ @collection ||= []
4
+ end
5
+
6
+ extend self
7
+ end
@@ -0,0 +1,28 @@
1
+ module Absorbtion
2
+ def simplify(expr)
3
+ if expr.class == Conjunction
4
+ absorb_for_class(expr, Disjunction)
5
+ elsif expr.class == Disjunction
6
+ absorb_for_class(expr, Conjunction)
7
+ else
8
+ expr
9
+ end
10
+ end
11
+
12
+ private
13
+ def absorb_for_class(expr, klass)
14
+ [[0, 1], [1, 0]].each do |a, b|
15
+ if expr.parts[a].class == klass && contains?(expr.parts[a], expr.parts[b])
16
+ return expr.parts[b]
17
+ end
18
+ end
19
+ expr
20
+ end
21
+
22
+ def contains?(a, b)
23
+ a.parts[0] == b || a.parts[1] == b
24
+ end
25
+
26
+ extend self
27
+ Rule.collection << self
28
+ end
@@ -0,0 +1,25 @@
1
+ module Annihilator
2
+ def simplify(expr)
3
+ if expr.class == Conjunction
4
+ return_x_if_contains_x(expr, false)
5
+ elsif expr.class == Disjunction
6
+ return_x_if_contains_x(expr, true)
7
+ else
8
+ expr
9
+ end
10
+ end
11
+
12
+ private
13
+ def return_x_if_contains_x(expr, x)
14
+ if expr.parts[0] == x
15
+ x
16
+ elsif expr.parts[1] == x
17
+ x
18
+ else
19
+ expr
20
+ end
21
+ end
22
+
23
+ extend self
24
+ Rule.collection << self
25
+ end
@@ -0,0 +1,25 @@
1
+ module Complementation
2
+ def simplify(expr)
3
+ if expr.class == Conjunction
4
+ return_x_if_complementary(expr, false)
5
+ elsif expr.class == Disjunction
6
+ return_x_if_complementary(expr, true)
7
+ else
8
+ expr
9
+ end
10
+ end
11
+
12
+ private
13
+ def return_x_if_complementary(expr, x)
14
+ complementary_expressions?(expr) ? x : expr
15
+ end
16
+
17
+ def complementary_expressions?(expr)
18
+ [[0, 1], [1, 0]].any? do |a, b|
19
+ expr.parts[a] == Negation.new(expr.parts[b])
20
+ end
21
+ end
22
+
23
+ extend self
24
+ Rule.collection << self
25
+ end
@@ -0,0 +1,31 @@
1
+ module DeMorgans
2
+ def simplify(expr)
3
+ if expr.class == Conjunction
4
+ apply_de_morgans(expr, Disjunction)
5
+ elsif expr.class == Disjunction
6
+ apply_de_morgans(expr, Conjunction)
7
+ else
8
+ expr
9
+ end
10
+ end
11
+
12
+ private
13
+ def apply_de_morgans(expr, inner_class)
14
+ if both_negation?(expr)
15
+ Negation.new(
16
+ inner_class.new(
17
+ expr.parts[0].parts[0],
18
+ expr.parts[1].parts[0])
19
+ )
20
+ else
21
+ expr
22
+ end
23
+ end
24
+
25
+ def both_negation?(expr)
26
+ [0, 1].all? { |i| expr.parts[i].class == Negation }
27
+ end
28
+
29
+ extend self
30
+ Rule.collection << self
31
+ end
@@ -0,0 +1,31 @@
1
+ module Distributivity
2
+ def simplify(expr)
3
+ if expr.class == Conjunction
4
+ distribute(expr, Conjunction, Disjunction)
5
+ elsif expr.class == Disjunction
6
+ distribute(expr, Disjunction, Conjunction)
7
+ else
8
+ expr
9
+ end
10
+ end
11
+
12
+ private
13
+ def distribute(expr, inner, outer)
14
+ [[0, 1], [1, 0]].each do |a, b|
15
+ if expr.parts[a].class == outer && trivial?(expr.parts[b])
16
+ return outer.new(
17
+ inner.new(expr.parts[b], expr.parts[a].parts[0]),
18
+ inner.new(expr.parts[b], expr.parts[a].parts[1])
19
+ )
20
+ end
21
+ end
22
+ expr
23
+ end
24
+
25
+ def trivial?(expr)
26
+ !expr.respond_to?(:parts)
27
+ end
28
+
29
+ extend self
30
+ Rule.collection << self
31
+ end
@@ -0,0 +1,17 @@
1
+ module DoubleNegation
2
+ def simplify(expr)
3
+ if negation?(expr) && negation?(expr.parts[0])
4
+ expr.parts[0].parts[0]
5
+ else
6
+ expr
7
+ end
8
+ end
9
+
10
+ private
11
+ def negation?(expr)
12
+ expr.class == Negation
13
+ end
14
+
15
+ extend self
16
+ Rule.collection << self
17
+ end
@@ -0,0 +1,12 @@
1
+ module Idempotence
2
+ def simplify(expr)
3
+ if expr.respond_to?(:parts) && expr.parts[0] == expr.parts[1]
4
+ expr.parts[0]
5
+ else
6
+ expr
7
+ end
8
+ end
9
+
10
+ extend self
11
+ Rule.collection << self
12
+ end
@@ -0,0 +1,25 @@
1
+ module Identity
2
+ def simplify(expr)
3
+ if expr.class == Conjunction
4
+ return_part_if_contains_x(expr, true)
5
+ elsif expr.class == Disjunction
6
+ return_part_if_contains_x(expr, false)
7
+ else
8
+ expr
9
+ end
10
+ end
11
+
12
+ private
13
+ def return_part_if_contains_x(expr, x)
14
+ if expr.parts[0] == x
15
+ expr.parts[1]
16
+ elsif expr.parts[1] == x
17
+ expr.parts[0]
18
+ else
19
+ expr
20
+ end
21
+ end
22
+
23
+ extend self
24
+ Rule.collection << self
25
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: boolean_simplifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Patuzzo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: treetop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: polyglot
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Simplify boolean expressions
63
+ email: chris@patuzzo.co.uk
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - README.md
69
+ - lib/boolean_simplifier/base.rb
70
+ - lib/boolean_simplifier/expression/conjunction.rb
71
+ - lib/boolean_simplifier/expression/disjunction.rb
72
+ - lib/boolean_simplifier/expression/negation.rb
73
+ - lib/boolean_simplifier/expression.rb
74
+ - lib/boolean_simplifier/expression_parser.rb
75
+ - lib/boolean_simplifier/expression_simplifier.rb
76
+ - lib/boolean_simplifier/grammar/boolean_expression.treetop
77
+ - lib/boolean_simplifier/rule/absorbtion.rb
78
+ - lib/boolean_simplifier/rule/annihilator.rb
79
+ - lib/boolean_simplifier/rule/complementation.rb
80
+ - lib/boolean_simplifier/rule/de_morgans.rb
81
+ - lib/boolean_simplifier/rule/distributivity.rb
82
+ - lib/boolean_simplifier/rule/double_negation.rb
83
+ - lib/boolean_simplifier/rule/idempotence.rb
84
+ - lib/boolean_simplifier/rule/identity.rb
85
+ - lib/boolean_simplifier/rule.rb
86
+ - lib/boolean_simplifier.rb
87
+ homepage: https://github.com/tuzz/boolean_simplifier
88
+ licenses: []
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.23
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Boolean Simplifier
111
+ test_files: []