Skip to content

Instantly share code, notes, and snippets.

@chucai
Created July 31, 2014 12:48
Show Gist options
  • Save chucai/938e375785407d96caf2 to your computer and use it in GitHub Desktop.
Save chucai/938e375785407d96caf2 to your computer and use it in GitHub Desktop.
expression calculate
# Given an expression: ( 2 + ( ( 4 + 6 ) * (9 * 2) - ( 5 - 1)))
# 1. Write a program calculate the given expression
# 2. Do not simply use “eval” directly
# 3. Need unit test
# 4. Submit your question using gist
#
# Bouns:
# ( 2 + ( ( 4 + 6 ) * sqrt(5)) / 2 )
#
require "pry"
module Expression
class Base
MATH_COMPLEX_OPERATOR = ['sqrt']
PRECEDENCE = [
['(', ')'],
['+', '-'],
['*', '/'],
MATH_COMPLEX_OPERATOR
]
REG_VARIABLE = /\d+(?:\.\d+)?/
REG_OPERATOR = /[\+\-\*\/]|(sqrt)/
REG_PARENTHESIS = /[\(\)]/
REG_SPACE = /\s+/
attr_reader :original
def initialize(expression)
@original = expression
@parse_expression_array = self.class.parse(@original)
end
def calculate
stack = []
@parse_expression_array.each do |token|
case token.to_s
when REG_VARIABLE
stack << token
when REG_OPERATOR
if MATH_COMPLEX_OPERATOR.include?(token)
d1 = stack.pop
d2 = d1
else
d2, d1 = stack.pop, stack.pop
end
stack << self.class.execute(d1, token, d2)
end
end
stack.first
end
private
def self.execute(d1, op, d2)
if MATH_COMPLEX_OPERATOR.include?(op)
Math.send(op, d1)
elsif op =~ REG_OPERATOR
d1.send(op, d2)
else
raise "operator '#{op}' wrong"
end
end
end
class Infix < Base
def self.parse(original)
result, stack, exp = [], [], original.dup
exp.gsub!(REG_SPACE, '')
while !exp.nil? && !exp.empty?
case exp
when /^(#{REG_VARIABLE})/
token = $1
result << (token['.'].nil? ? token.to_i : token.to_f)
when /^(#{REG_OPERATOR})/
token = $1
if stack.empty?
stack << token
else
while stack.size > 0 && precedence(token) <= precedence(stack.last)
result << stack.pop
end
stack << token
end
when /^(#{REG_PARENTHESIS})/
token = $1
case token
when "("
stack << token
when ")"
result << stack.pop while !stack.empty? && stack.last != '('
stack.pop
end
end
exp = exp[token.size..-1]
end
result << stack.pop until stack.empty?
result
end
def self.precedence(op)
PRECEDENCE.index { |group| group.include?(op) }
end
end
end
require "test/unit"
require_relative "./expression"
class ExpressionTest < Test::Unit::TestCase
def test_success_case
assert true
end
def test_initialize
expression = Expression::Infix.new("3+1")
assert_equal expression.original, "3+1"
end
def test_parse_simple_expression
assert_parse({
'2*(3-1)' => [2, 3, 1, '-', '*'],
'2 * ( 3 - 1 )' => [2, 3, 1, '-', '*'],
'(3-1) * 2 - (5 - 1)' => [3, 1, '-', 2, '*', 5, 1, '-', '-']
})
end
def test_calculate_simple_expression
assert_calculate({
'2*(3-1)' => 4,
'2 * (3-1)' => 4,
'(3-1) * 2 - (5 - 1)' => 0
})
end
def test_calculate_complex_expression
assert_calculate({
'( 2 + ( ( 4 + 6 ) * (9 * 2) - ( 5 - 1)))' => 178
})
end
def test_parse_sqrt_expression
assert_parse('( 2 + sqrt(9) / 2 )' => [2, 9, 'sqrt', 2, '/', '+'])
end
def test_calculate_sqrt_expression
assert_calculate({
'( 2 + sqrt(9) / 2 )' => 3.5,
'( 2 + ( ( 4 + 6 ) * sqrt(9)) / 2 )' => 17
})
end
def assert_parse(options)
options.each do |exp, expect|
assert_equal Expression::Infix.parse(exp), expect
end
end
def assert_calculate(options)
options.each do |exp, expect|
expression = Expression::Infix.new(exp)
assert_equal expression.calculate, expect
end
end
end
@chucai
Copy link
Author

chucai commented Jul 31, 2014

Required:

Ruby 2.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment