Class: Calculus::Expression
- Inherits:
-
Object
- Object
- Calculus::Expression
- Includes:
- Latex
- Defined in:
- lib/calculus/expression.rb
Overview
This class represent some expression and optionaly transform it to the postfix notation for later analysis.
Expression can introduce variables which could be substituted later
exp = Expression.new("x + 2 * 4")
exp.to_s #=> "x + 2 * 4"
exp.calculate # raises UnboundVariableError
exp["x"] = 5
exp.to_s #=> "5 + 2 * 4"
exp.calculate #=> 40
Constant Summary
Constants included from Latex
Instance Attribute Summary collapse
-
#postfix_notation ⇒ Object
(also: #rpn)
readonly
Array with postfix notation of expression.
-
#sha1 ⇒ Object
readonly
Represents unique identifier of expression.
-
#source ⇒ Object
readonly
Source expression string.
Instance Method Summary collapse
-
#[](name) ⇒ Object
Getter for given variable.
-
#[]=(name, value) ⇒ Object
Setter for given variable.
-
#abstract_syntax_tree ⇒ Object
(also: #ast)
Builds abstract syntax tree (AST) as alternative expression notation.
-
#calculate ⇒ Object
Traverse postfix notation and calculate the actual value of expression.
-
#equation? ⇒ Boolean
Returns
true
if there equals sign presented. -
#initialize(source, options = {}) ⇒ Expression
constructor
Initialize instance with given string expression.
-
#inspect ⇒ Object
:nodoc:.
-
#parsed? ⇒ Boolean
Returns
true
when postfix notation has been built. -
#to_s ⇒ Object
Returns string representation of expression.
-
#traverse(&block) ⇒ Object
Perform traverse along postfix notation.
-
#unbound_variables ⇒ Object
Returns array of variables which have nil value.
-
#variables ⇒ Object
Returns array of strings with variable names.
Methods included from Latex
Constructor Details
#initialize(source, options = {}) ⇒ Expression
Initialize instance with given string expression.
It is possible to skip parser.
# raises Calculus::ParserError: Invalid character...
x = Expression.new("\sum_{i=1}^n \omega_i \x_i")
# just stores source string and allows rendering to PNG
x = Expression.new("\sum_{i=1}^n \omega_i \x_i", :parse => false)
x.parsed? #=> false
It raises ArgumentError if there are more than one equal sign because if you need to represent the system of equations you should you two instances of Expression
class and no equals sign for just calculation.
Also it initializes SHA1 fingerprint of particular expression
47 48 49 50 51 52 53 54 |
# File 'lib/calculus/expression.rb', line 47 def initialize(source, = {}) = {:parse => true}.merge() @source = source @postfix_notation = [:parse] ? Parser.new(source).parse : [] raise ArgumentError, "Should be no more that one equals sign" if @postfix_notation.count(:eql) > 1 @variables = extract_variables update_sha1 end |
Instance Attribute Details
#postfix_notation ⇒ Object (readonly) Also known as: rpn
Array with postfix notation of expression
28 29 30 |
# File 'lib/calculus/expression.rb', line 28 def postfix_notation @postfix_notation end |
#sha1 ⇒ Object (readonly)
Represents unique identifier of expression. Should be changed when some variables are binding
22 23 24 |
# File 'lib/calculus/expression.rb', line 22 def sha1 @sha1 end |
#source ⇒ Object (readonly)
Source expression string
25 26 27 |
# File 'lib/calculus/expression.rb', line 25 def source @source end |
Instance Method Details
#[](name) ⇒ Object
Getter for given variable. Raises an Argument
exception when there no such variable.
78 79 80 81 |
# File 'lib/calculus/expression.rb', line 78 def [](name) raise ArgumentError, "No such variable defined: #{name}" unless @variables.keys.include?(name) @variables[name] end |
#[]=(name, value) ⇒ Object
Setter for given variable. Raises an Argument
exception when there no such variable.
85 86 87 88 89 |
# File 'lib/calculus/expression.rb', line 85 def []=(name, value) raise ArgumentError, "No such variable defined: #{name}" unless @variables.keys.include?(name) @variables[name] = value update_sha1 end |
#abstract_syntax_tree ⇒ Object Also known as: ast
Builds abstract syntax tree (AST) as alternative expression notation. Return nested array where first member is an operation and the other operands
Calculus::Expression.new("x + 2 * 4").ast #=> [:plus, "x", [:mul, 2, 4]]
143 144 145 146 147 |
# File 'lib/calculus/expression.rb', line 143 def abstract_syntax_tree traverse do |operation, left, right, stack| [operation, left, right] end end |
#calculate ⇒ Object
Traverse postfix notation and calculate the actual value of expression. Raises NotImplementedError
when equation detected (currently it cannot solve equation) and UnboundVariableError
if there unbound variables found.
Note that there some rounding errors here in root operation because of general approach to calculate it:
1000 ** (1.0 / 3) #=> 9.999999999999998
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/calculus/expression.rb', line 121 def calculate raise NotImplementedError, "Equation detected. This class can't calculate equations yet." if equation? raise UnboundVariableError, "Can't calculate. Unbound variables found: #{unbound_variables.join(', ')}" unless unbound_variables.empty? traverse do |operation, left, right, stack| case operation when :sqrt then left ** (1.0 / right) # could cause some rounding errors when :exp then left ** right when :plus then left + right when :minus then left - right when :mul then left * right when :div then left / right end end end |
#equation? ⇒ Boolean
Returns true
if there equals sign presented
62 63 64 |
# File 'lib/calculus/expression.rb', line 62 def equation? @postfix_notation.include?(:eql) end |
#inspect ⇒ Object
:nodoc:
163 164 165 |
# File 'lib/calculus/expression.rb', line 163 def inspect # :nodoc: "#<Expression:#{@sha1} postfix_notation=#{@postfix_notation.inspect} variables=#{@variables.inspect}>" end |
#parsed? ⇒ Boolean
Returns true
when postfix notation has been built
57 58 59 |
# File 'lib/calculus/expression.rb', line 57 def parsed? !@postfix_notation.empty? end |
#to_s ⇒ Object
Returns string representation of expression. Substitutes bound variables.
152 153 154 155 156 157 158 159 160 161 |
# File 'lib/calculus/expression.rb', line 152 def to_s result = source.dup (variables - unbound_variables).each do |var| result.gsub!(/(\W)#{var}(\W)/, "\\1#{@variables[var]}\\2") result.gsub!(/^#{var}(\W)/, "#{@variables[var]}\\1") result.gsub!(/(\W)#{var}$/, "\\1#{@variables[var]}") result.gsub!(/^#{var}$/, @variables[var].to_s) end result end |
#traverse(&block) ⇒ Object
Perform traverse along postfix notation. Yields operation
with left
and right
operands and the latest argument is the current state of stack
(note you can amend this stack from outside)
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/calculus/expression.rb', line 95 def traverse(&block) # :yields: operation, left, right, stack stack = [] @postfix_notation.each do |node| case node when Symbol operation, right, left = node, stack.pop, stack.pop stack.push(yield(operation, left, right, stack)) when Numeric stack.push(node) when String stack.push(@variables[node] || node) end end stack.pop end |
#unbound_variables ⇒ Object
Returns array of variables which have nil value
72 73 74 |
# File 'lib/calculus/expression.rb', line 72 def unbound_variables @variables.keys.select{|k| @variables[k].nil?} end |
#variables ⇒ Object
Returns array of strings with variable names
67 68 69 |
# File 'lib/calculus/expression.rb', line 67 def variables @variables.keys end |