patternrubyMinor
CodeWars mathematical expresion evaluator
Viewed 0 times
expresionevaluatorcodewarsmathematical
Problem
I recently wrote the following code for a codewars.com kata for evaluating mathematical expressions. I've been writing ruby for years, but almost everything I do is for personal use and isn't shared, so I've gotten essentially zero feedback on my ruby style. The following block of code (with more comments than usual) is fairly typical of my style, but it never feels as ruby-ish as the code snippets I see online.
What can be changed to make this code closer to ideal idiomatic ruby?
```
#parse the initial expression, break it up into an array
# of tokens (numbers, parens, operations, etc)
# this also detects negation and cheges the symbol from '-' to 'n'
# and finally, this converts numbers to floats
def tokenize_expression(expression)
#remove spaces
s = expression.gsub(/\s/,'')
#convert negations to 'n' character
while md = s.match(%r{(? ['1', '+', ['2', '+', '3']]
def nest_parens(tokens)
result = []
stack = []
first, rest = tokens
while not first.nil? do
case first #look at first token
when '('
stack.push result #store current partial result on stack if open parens
result = [] #start new result
when ')'
child = result #store result in temp var
result = stack.pop #get previous partial result
result [+ [+ 1 2] 3]
else
result << first
end
first, rest = rest
end
result
end
#take a fully processed, postfix tree and recursively evaluate the expressions
def eval(tree)
return tree if not tree.is_a? Array #if this isn't an array, return it
tree = tree.map {|n| eval(n) } #recursively process everything below the current level
return tree.first if tree.length == 1 #sometimes we end up with a single value as an array, e.g. [5], so just return the inner value
first, second, third = tree #process arguments
What can be changed to make this code closer to ideal idiomatic ruby?
```
#parse the initial expression, break it up into an array
# of tokens (numbers, parens, operations, etc)
# this also detects negation and cheges the symbol from '-' to 'n'
# and finally, this converts numbers to floats
def tokenize_expression(expression)
#remove spaces
s = expression.gsub(/\s/,'')
#convert negations to 'n' character
while md = s.match(%r{(? ['1', '+', ['2', '+', '3']]
def nest_parens(tokens)
result = []
stack = []
first, rest = tokens
while not first.nil? do
case first #look at first token
when '('
stack.push result #store current partial result on stack if open parens
result = [] #start new result
when ')'
child = result #store result in temp var
result = stack.pop #get previous partial result
result [+ [+ 1 2] 3]
else
result << first
end
first, rest = rest
end
result
end
#take a fully processed, postfix tree and recursively evaluate the expressions
def eval(tree)
return tree if not tree.is_a? Array #if this isn't an array, return it
tree = tree.map {|n| eval(n) } #recursively process everything below the current level
return tree.first if tree.length == 1 #sometimes we end up with a single value as an array, e.g. [5], so just return the inner value
first, second, third = tree #process arguments
Solution
Single responsibility principle
Your function
This should be three functions:
Use functional programming when practical
You should not feel forced to stick to imperative programming even when it becomes so hard to follow, for example
Another example is
Surely other parts can be similarly simplified.
#parse the initial expression, break it up into an array
# of tokens (numbers, parens, operations, etc)
# this also detects negation and cheges the symbol from '-' to 'n'
# and finally, this converts numbers to floats
def tokenize_expression(expression)Your function
tokenize_expression does:- break [the initial expression] up into an array
- detects negation and changes the symbol from '-' to 'n'
- converts numbers to floats
This should be three functions:
def negation_to_n(expression)
s = expression.gsub(/\s/,'')
while md = s.match(%r{(?<![\d\)])-}) do
s[md.begin(0)] = 'n'
end
s
end
def split_tokens(expression)
### Implement
end
def numbers_to_floats(tokens)
tokens.map{|t| t.match(%r{^\d}) ? t.to_f : t}
endUse functional programming when practical
You should not feel forced to stick to imperative programming even when it becomes so hard to follow, for example
negation_to_n can be written just like (please note that the string should be passed in already without spaces):def matching_indexes(s, regex)
s
.enum_for(:scan, regex)
.map { Regexp.last_match.begin(0) }
end
def negation_to_n(s)
(0...s.length)
.map{|i| matching_indexes(s, %r{(?<![\d\)])-}).include?(i) ? 'n' : s[i]}
endAnother example is
split_tokens:def split_tokens(s)
s
.chars
.chunk {|d| is_digit?(d)}
.map{|_, xs| xs}
.map {|g| g.all? {|ch| is_digit?(ch)} ? g.join : g}
.flatten
endSurely other parts can be similarly simplified.
Code Snippets
#parse the initial expression, break it up into an array
# of tokens (numbers, parens, operations, etc)
# this also detects negation and cheges the symbol from '-' to 'n'
# and finally, this converts numbers to floats
def tokenize_expression(expression)def negation_to_n(expression)
s = expression.gsub(/\s/,'')
while md = s.match(%r{(?<![\d\)])-}) do
s[md.begin(0)] = 'n'
end
s
end
def split_tokens(expression)
### Implement
end
def numbers_to_floats(tokens)
tokens.map{|t| t.match(%r{^\d}) ? t.to_f : t}
enddef matching_indexes(s, regex)
s
.enum_for(:scan, regex)
.map { Regexp.last_match.begin(0) }
end
def negation_to_n(s)
(0...s.length)
.map{|i| matching_indexes(s, %r{(?<![\d\)])-}).include?(i) ? 'n' : s[i]}
enddef split_tokens(s)
s
.chars
.chunk {|d| is_digit?(d)}
.map{|_, xs| xs}
.map {|g| g.all? {|ch| is_digit?(ch)} ? g.join : g}
.flatten
endContext
StackExchange Code Review Q#119530, answer score: 5
Revisions (0)
No revisions yet.