Created
October 11, 2011 15:38
-
-
Save nileshtrivedi/1278438 to your computer and use it in GitHub Desktop.
Simple symbolic math implementation in Ruby (Work in progress)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# TODO | |
# if a var has a value, it also needs a name. This should not be necessary. Variable.initialize should accept hashargs | |
# Expression.to_s should output in infix-notation (like normal humans) | |
# add more syntactic sugar by manipulating Ruby Numeric classes. Add symbolic operators to Ruby's Numeric | |
# If symbolic expression contains variables without value then it should return nil (should it, really?) | |
# Implement additional operators like ** | |
# All symbolic expression should be automatically simplified when created: | |
# cons(0) * x # => 0 | |
# 2 + x + 1 # => x + 3 | |
# -(x-y) + 2*x # => x + y | |
# (x**2)**3 / x # => x**5 | |
# this is purely for syntactic-sugar | |
class Object | |
private | |
def var(name = '_', value = nil) | |
Variable.new(name, value) | |
end | |
def cons(value) # value is a literal constant as supported by ruby | |
Constant.new(value) | |
end | |
end | |
class Numeric | |
def is_zero? | |
0 == self | |
end | |
def is_one? | |
1 == self | |
end | |
end | |
# this module gets included in Variable, Constant and Expression | |
module Operators | |
def +(op) | |
return self if op.is_zero? | |
return op if self.is_zero? | |
return Constant.new(self.value.+(op.value)) if self.is_a?(Constant) && op.is_a?(Constant) | |
Expression.new(:+, self, op) | |
end | |
def -(op) | |
return self if op.is_zero? | |
return Constant.new(self.value.-(op.value)) if self.is_a?(Constant) && op.is_a?(Constant) | |
Expression.new(:-, self, op) | |
end | |
def *(op) | |
return Constant.new(0) if op.is_zero? || self.is_zero? | |
return self if op.is_one? | |
return op if self.is_one? | |
return Constant.new(self.value.*(op.value)) if self.is_a?(Constant) && op.is_a?(Constant) | |
Expression.new(:*, self, op) | |
end | |
def /(op) | |
return self if op.is_one? | |
return Constant.new(self.value./(op.value)) if self.is_a?(Constant) && op.is_a?(Constant) | |
Expression.new(:/, self, op) | |
end | |
end | |
class Variable | |
include Operators | |
attr_accessor :name, :value | |
def initialize(name = '_', value = nil) # nil stands for unknown value | |
@name = name | |
@value = cons(value) if value | |
end | |
def value=(val) | |
@value = (val.is_a?(Constant) ? val : cons(val)) | |
end | |
def to_s | |
@name.to_s | |
end | |
def is_zero? | |
false | |
end | |
def is_one? | |
false | |
end | |
def value_or_self | |
@value || self | |
end | |
def variables | |
[self] | |
end | |
end | |
class Constant | |
include Operators | |
attr_accessor :value # these will be ruby primitive objects like Fixnum, Float etc | |
def initialize(value) | |
raise "constant must have a numeric value, not #{value.inspect}." if value.nil? || !value.is_a?(Numeric) | |
@value = value | |
end | |
def is_zero? | |
@value == 0 | |
end | |
def is_one? | |
@value == 1 | |
end | |
def to_s | |
@value.to_s | |
end | |
def value_or_self | |
self # cannot do @value || self here because @value is a ruby-primitive which we are not modifying to avoid monkeypatching std ruby classes | |
end | |
def variables | |
[] | |
end | |
end | |
class Expression # one operator and two operands in a tree-like structure | |
include Operators | |
attr_accessor :operator, :op_left, :op_right | |
def initialize(operator, op_left, op_right) | |
@operator = operator; @op_left = op_left; @op_right = op_right # op_left and op_right are either expressions or constants or variables. | |
end | |
def to_s | |
"(#{@operator} #{@op_left} #{@op_right})" # lisp-style prefix-notation so that we don't have to worry about operator precedence | |
end | |
def value # returns as simplified expression as possible | |
@op_left.value_or_self.send(@operator, @op_right.value_or_self) | |
end | |
def value_or_self | |
value | |
end | |
def is_zero? | |
false | |
end | |
def is_one? | |
false | |
end | |
def variables | |
(@op_left.variables + @op_right.variables).uniq | |
end | |
def operations | |
# TODO | |
end | |
end | |
x = var | |
x.value = 3 | |
f = cons(2)*x + cons(1) | |
puts f | |
puts f.value | |
z = var | |
puts (z + cons(1)).value # TODO should expression.value return nil if it containst unset variables? Right now, it returns |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment