Recursive math expression evalNumeric expression parser - calculatorMath expression solverRecursive...
What wound would be of little consequence to a biped but terrible for a quadruped?
Why does the negative sign arise in this thermodynamic relation?
Does "variables should live in the smallest scope as possible" include the case "variables should not exist if possible"?
Who deserves to be first and second author? PhD student who collected data, research associate who wrote the paper or supervisor?
Is there an elementary proof that there are infinitely many primes that are *not* completely split in an abelian extension?
Aliens englobed the Solar System: will we notice?
How strictly should I take "Candidates must be local"?
Try Catch Block Affecting a Variable in an Enclosing Scope
Do I really need to have a scientific explanation for my premise?
Do items de-spawn in Diablo?
How did the power source of Mar-Vell's aircraft end up with her?
How can I ensure my trip to the UK will not have to be cancelled because of Brexit?
If the Captain's screens are out, does he switch seats with the co-pilot?
How to pass a string to a command that expects a file?
Tricky AM-GM inequality
Why doesn't this Google Translate ad use the word "Translation" instead of "Translate"?
Set and print content of environment variable in cmd.exe subshell?
Single word request: Harming the benefactor
Should I tell my boss the work he did was worthless
Offered promotion but I'm leaving. Should I tell?
In the late 1940’s to early 1950’s what technology was available that could melt a LOT of ice?
Good allowance savings plan?
Is "history" a male-biased word ("his+story")?
Are babies of evil humanoid species inherently evil?
Recursive math expression eval
Numeric expression parser - calculatorMath expression solverRecursive calculator in CPredictive recursive descent parser for math expressionsMath expression parser in C#Math expression on-the-fly interpreterMath eval function for HaskellMath expression parser in JavaScriptMath expression evaluatorCalculator/math expression parser (supporting complex numbers)
$begingroup$
It has been very hard to use recursion, but I think that it made the code shorter and cleaner.
import doctest
import operator as op
START_FOR_SYMBOLS = {'+': 0,
'*': 1,
'/':1,
'-':0
}
OP_FOR_SYMBOL = {'+': op.add,
'*': op.mul,
'/': op.truediv,
'-': op.sub
}
def innermost_parens(text):
"""
Returns the text inside the innermost parenthesis.
>>> innermost_parens("1 + (2 * (4 - 1))")
'4 - 1'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'8 * 7'
"""
if not '(' in text and not ')' in text:
return text
open_ = text.index('(')
close_ = text.rindex(')')
return innermost_parens(text[open_+1:close_])
def polish_eval(expr,start=None):
"""
Unlimited polish eval, works for any number of arguments.
>>> polish_eval('+ 4 1 6')
11.0
>>> polish_eval('* 4 5 5')
100.0
"""
tokens = expr.split(' ')
if start is None:
start = START_FOR_SYMBOLS[tokens[0]]
if len(tokens) == 1:
return start
return polish_eval(' '.join(tokens[:-1]),
start = OP_FOR_SYMBOL[tokens[0]](start,float(tokens[-1]))
)
def infix_eval(expr):
"""
Reduced infix eval, only works with 2 numbers.
>>> infix_eval('9 + 4')
13.0
>>> infix_eval('2 * -6')
-12.0
"""
a, oper, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a),float(b))
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("(* 4 5 (+ 4 1))", polish_eval)
100.0
>>> full_eval("(* 4 (/ 10))", polish_eval)
0.4
>>> full_eval("(1 + (5 * 2))", infix_eval)
11.0
"""
if len(expr.split(' ')) == 1:
return float(expr)
inn = innermost_parens(expr)
new_expr = expr.replace('('+str(inn)+')',str(eval_type(inn)))
return full_eval(new_expr, eval_type)
def interface():
which_expr = input("Polish or infix? ")
if 'polish' in which_expr.lower():
evaller = lambda expr: full_eval(expr, polish_eval)
else:
evaller = lambda expr: full_eval(expr, infix_eval)
while True:
result = evaller(input('> '))
print(result)
if __name__ == "__main__":
doctest.testmod()
interface()
python recursion math-expression-eval
$endgroup$
add a comment |
$begingroup$
It has been very hard to use recursion, but I think that it made the code shorter and cleaner.
import doctest
import operator as op
START_FOR_SYMBOLS = {'+': 0,
'*': 1,
'/':1,
'-':0
}
OP_FOR_SYMBOL = {'+': op.add,
'*': op.mul,
'/': op.truediv,
'-': op.sub
}
def innermost_parens(text):
"""
Returns the text inside the innermost parenthesis.
>>> innermost_parens("1 + (2 * (4 - 1))")
'4 - 1'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'8 * 7'
"""
if not '(' in text and not ')' in text:
return text
open_ = text.index('(')
close_ = text.rindex(')')
return innermost_parens(text[open_+1:close_])
def polish_eval(expr,start=None):
"""
Unlimited polish eval, works for any number of arguments.
>>> polish_eval('+ 4 1 6')
11.0
>>> polish_eval('* 4 5 5')
100.0
"""
tokens = expr.split(' ')
if start is None:
start = START_FOR_SYMBOLS[tokens[0]]
if len(tokens) == 1:
return start
return polish_eval(' '.join(tokens[:-1]),
start = OP_FOR_SYMBOL[tokens[0]](start,float(tokens[-1]))
)
def infix_eval(expr):
"""
Reduced infix eval, only works with 2 numbers.
>>> infix_eval('9 + 4')
13.0
>>> infix_eval('2 * -6')
-12.0
"""
a, oper, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a),float(b))
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("(* 4 5 (+ 4 1))", polish_eval)
100.0
>>> full_eval("(* 4 (/ 10))", polish_eval)
0.4
>>> full_eval("(1 + (5 * 2))", infix_eval)
11.0
"""
if len(expr.split(' ')) == 1:
return float(expr)
inn = innermost_parens(expr)
new_expr = expr.replace('('+str(inn)+')',str(eval_type(inn)))
return full_eval(new_expr, eval_type)
def interface():
which_expr = input("Polish or infix? ")
if 'polish' in which_expr.lower():
evaller = lambda expr: full_eval(expr, polish_eval)
else:
evaller = lambda expr: full_eval(expr, infix_eval)
while True:
result = evaller(input('> '))
print(result)
if __name__ == "__main__":
doctest.testmod()
interface()
python recursion math-expression-eval
$endgroup$
2
$begingroup$
How would you handle the case '- 20 5 3' for Polish notation? Your program returns '-28'. My suggestion is that you restrict the number of numbers (no pun intended) allowed to just 2; this makes the job much easier and allows people to write Polish notation without brackets.
$endgroup$
– wei2912
Apr 9 '15 at 13:11
add a comment |
$begingroup$
It has been very hard to use recursion, but I think that it made the code shorter and cleaner.
import doctest
import operator as op
START_FOR_SYMBOLS = {'+': 0,
'*': 1,
'/':1,
'-':0
}
OP_FOR_SYMBOL = {'+': op.add,
'*': op.mul,
'/': op.truediv,
'-': op.sub
}
def innermost_parens(text):
"""
Returns the text inside the innermost parenthesis.
>>> innermost_parens("1 + (2 * (4 - 1))")
'4 - 1'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'8 * 7'
"""
if not '(' in text and not ')' in text:
return text
open_ = text.index('(')
close_ = text.rindex(')')
return innermost_parens(text[open_+1:close_])
def polish_eval(expr,start=None):
"""
Unlimited polish eval, works for any number of arguments.
>>> polish_eval('+ 4 1 6')
11.0
>>> polish_eval('* 4 5 5')
100.0
"""
tokens = expr.split(' ')
if start is None:
start = START_FOR_SYMBOLS[tokens[0]]
if len(tokens) == 1:
return start
return polish_eval(' '.join(tokens[:-1]),
start = OP_FOR_SYMBOL[tokens[0]](start,float(tokens[-1]))
)
def infix_eval(expr):
"""
Reduced infix eval, only works with 2 numbers.
>>> infix_eval('9 + 4')
13.0
>>> infix_eval('2 * -6')
-12.0
"""
a, oper, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a),float(b))
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("(* 4 5 (+ 4 1))", polish_eval)
100.0
>>> full_eval("(* 4 (/ 10))", polish_eval)
0.4
>>> full_eval("(1 + (5 * 2))", infix_eval)
11.0
"""
if len(expr.split(' ')) == 1:
return float(expr)
inn = innermost_parens(expr)
new_expr = expr.replace('('+str(inn)+')',str(eval_type(inn)))
return full_eval(new_expr, eval_type)
def interface():
which_expr = input("Polish or infix? ")
if 'polish' in which_expr.lower():
evaller = lambda expr: full_eval(expr, polish_eval)
else:
evaller = lambda expr: full_eval(expr, infix_eval)
while True:
result = evaller(input('> '))
print(result)
if __name__ == "__main__":
doctest.testmod()
interface()
python recursion math-expression-eval
$endgroup$
It has been very hard to use recursion, but I think that it made the code shorter and cleaner.
import doctest
import operator as op
START_FOR_SYMBOLS = {'+': 0,
'*': 1,
'/':1,
'-':0
}
OP_FOR_SYMBOL = {'+': op.add,
'*': op.mul,
'/': op.truediv,
'-': op.sub
}
def innermost_parens(text):
"""
Returns the text inside the innermost parenthesis.
>>> innermost_parens("1 + (2 * (4 - 1))")
'4 - 1'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'8 * 7'
"""
if not '(' in text and not ')' in text:
return text
open_ = text.index('(')
close_ = text.rindex(')')
return innermost_parens(text[open_+1:close_])
def polish_eval(expr,start=None):
"""
Unlimited polish eval, works for any number of arguments.
>>> polish_eval('+ 4 1 6')
11.0
>>> polish_eval('* 4 5 5')
100.0
"""
tokens = expr.split(' ')
if start is None:
start = START_FOR_SYMBOLS[tokens[0]]
if len(tokens) == 1:
return start
return polish_eval(' '.join(tokens[:-1]),
start = OP_FOR_SYMBOL[tokens[0]](start,float(tokens[-1]))
)
def infix_eval(expr):
"""
Reduced infix eval, only works with 2 numbers.
>>> infix_eval('9 + 4')
13.0
>>> infix_eval('2 * -6')
-12.0
"""
a, oper, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a),float(b))
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("(* 4 5 (+ 4 1))", polish_eval)
100.0
>>> full_eval("(* 4 (/ 10))", polish_eval)
0.4
>>> full_eval("(1 + (5 * 2))", infix_eval)
11.0
"""
if len(expr.split(' ')) == 1:
return float(expr)
inn = innermost_parens(expr)
new_expr = expr.replace('('+str(inn)+')',str(eval_type(inn)))
return full_eval(new_expr, eval_type)
def interface():
which_expr = input("Polish or infix? ")
if 'polish' in which_expr.lower():
evaller = lambda expr: full_eval(expr, polish_eval)
else:
evaller = lambda expr: full_eval(expr, infix_eval)
while True:
result = evaller(input('> '))
print(result)
if __name__ == "__main__":
doctest.testmod()
interface()
python recursion math-expression-eval
python recursion math-expression-eval
asked Apr 9 '15 at 12:47
CaridorcCaridorc
23k538117
23k538117
2
$begingroup$
How would you handle the case '- 20 5 3' for Polish notation? Your program returns '-28'. My suggestion is that you restrict the number of numbers (no pun intended) allowed to just 2; this makes the job much easier and allows people to write Polish notation without brackets.
$endgroup$
– wei2912
Apr 9 '15 at 13:11
add a comment |
2
$begingroup$
How would you handle the case '- 20 5 3' for Polish notation? Your program returns '-28'. My suggestion is that you restrict the number of numbers (no pun intended) allowed to just 2; this makes the job much easier and allows people to write Polish notation without brackets.
$endgroup$
– wei2912
Apr 9 '15 at 13:11
2
2
$begingroup$
How would you handle the case '- 20 5 3' for Polish notation? Your program returns '-28'. My suggestion is that you restrict the number of numbers (no pun intended) allowed to just 2; this makes the job much easier and allows people to write Polish notation without brackets.
$endgroup$
– wei2912
Apr 9 '15 at 13:11
$begingroup$
How would you handle the case '- 20 5 3' for Polish notation? Your program returns '-28'. My suggestion is that you restrict the number of numbers (no pun intended) allowed to just 2; this makes the job much easier and allows people to write Polish notation without brackets.
$endgroup$
– wei2912
Apr 9 '15 at 13:11
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
Good job on adding the test suites; they make testing code extremely easy.
Recursion often results in a clean solution. However, I think that this problem can be solved just as well with an iterative approach.
The key to innermost_parens
is to return the text between the last (
and the first )
. Recursion is a viable solution, but I find that this for loop does the job better and in a cleaner way:
def innermost_parens(text):
"""
Returns the innermost parenthesis.
>>> innermost_parens('* 1 2')
'* 1 2'
>>> innermost_parens("1 + (2 * (4 - 1))")
'(4 - 1)'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'(8 * 7)'
>>> innermost_parens("(1 + 2) * (3 + 4)")
'(1 + 2)'
"""
start = 0
end = len(text)
for (i, c) in enumerate(text):
if c == '(':
start = i
if c == ')':
end = i + 1
break
return text[start:end]
Take note that this function will return the first innermost expression with brackets. The reason for this will be clear later.
As for polish_eval
, your program doesn't follow the standard Polish notation. In the standard Polish notation you can only perform operations on two numbers. It is also unclear what 'start' does. I modified your code to do the above and added tests for subtraction and division:
def polish_eval(expr):
"""
Reduced Polish notation.
>>> polish_eval('+ 4 1')
5.0
>>> polish_eval('* 4 5')
20.0
>>> polish_eval('- 20 3')
17.0
>>> polish_eval('/ 20 5')
4.0
"""
oper, a, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a), float(b))
I'll leave the documentation of the Polish notation to you. No changes were made to infix_eval
.
Finally, we're on to full_eval
. Since there's been a major change in the notation we support, we'll have to rewrite the code. First, to start off with the tests:
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
Rewriting Polish notation without brackets would require the use of a stack. This will not fit nicely in your full_eval
function, so I will not implement the notation without brackets. In this function, the use of recursion is unnecessary and a while loop will fit the problem nicely.
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
"""
while len(expr.split()) != 1:
inn = innermost_parens(expr)
result = eval_type(inn.strip('(').strip(')').strip())
expr = expr.replace(inn, str(result))
return float(expr)
Before I evaluate inn
, I first strip off the brackets which enclose the expression, as well as any remaining whitespace.
The reason why I wrote innermost_parens
as shown above is to allow for the easy replacement of the expression. Here's a trace of an expression in Polish:
* (/ 9 3) (+ (/ 3 5) (- 20 5))
= * (/ 9 3) (+ 0.6 (- 20 5)) # '(/ 3 5)' -> '0.6'
= * (/ 9 3) (+ 0.6 15.0) # '(- 20 5)' -> '15.0'
= * 3.0 (+ 0.6 15.0) # '(/ 9 3)' -> '3.0'
= * 3.0 15.6 # '(+ 0.6 15.0)' -> '15.6'
= 46.8 # '* 3.0 15.6' -> '46.8'
and the same expression in infix:
(9 / 3) * ((3 / 5) + (20 - 5))
= (9 / 3) * (0.6 + (20 - 5)) # '(3 / 5)' -> '0.6'
= (9 / 3) * (0.6 + 15.0) # '(20 - 5)' -> '15.0'
= 3.0 * (0.6 + 15.0) # '(9 / 3)' -> '3.0'
= 3.0 * 15.6 # '(0.6 + 15.0)' -> '15.6'
= 46.8 # '3.0 * 15.6' -> '46.8'
Extensions:
- Write your program such that it can parse Polish notation without brackets. That's what Polish notation is invented for.
- Handle edge cases such as
((* 3 15))
(Polish). - Write test suites to ensure that the expressions are evaluated correctly. While addition and multiplication is commutative, subtraction and division are not, and the order of the numbers matter.
- Follow suggestions in l0b0's answer, which are cosmetic. I did not make any cosmetic suggestions as I thought that it was more important to settle the logic of the program first.
- Have your program create an abstract syntax tree instead of doing string operations (such as replacing innermost expression with its value).
Feel free to ask questions in the comments.
$endgroup$
add a comment |
$begingroup$
Some suggestions:
- Rather than ask whether all the following expressions are going to be in Polish or infix notation, you can simply check whether the first non-parenthesis character is a number or an operator.
START_FOR_SYMBOLS
could be renamed toOPERATION_IDENTITIES
for mathematical clarity.
Open issues:
- Why
doctest.testmod()
on every run? - There isn't always a unique innermost parenthesis, for example in the case of
(1 + 2) * (3 + 4)
. You probably want to return a list and process each of them in turn.
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f86389%2frecursive-math-expression-eval%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Good job on adding the test suites; they make testing code extremely easy.
Recursion often results in a clean solution. However, I think that this problem can be solved just as well with an iterative approach.
The key to innermost_parens
is to return the text between the last (
and the first )
. Recursion is a viable solution, but I find that this for loop does the job better and in a cleaner way:
def innermost_parens(text):
"""
Returns the innermost parenthesis.
>>> innermost_parens('* 1 2')
'* 1 2'
>>> innermost_parens("1 + (2 * (4 - 1))")
'(4 - 1)'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'(8 * 7)'
>>> innermost_parens("(1 + 2) * (3 + 4)")
'(1 + 2)'
"""
start = 0
end = len(text)
for (i, c) in enumerate(text):
if c == '(':
start = i
if c == ')':
end = i + 1
break
return text[start:end]
Take note that this function will return the first innermost expression with brackets. The reason for this will be clear later.
As for polish_eval
, your program doesn't follow the standard Polish notation. In the standard Polish notation you can only perform operations on two numbers. It is also unclear what 'start' does. I modified your code to do the above and added tests for subtraction and division:
def polish_eval(expr):
"""
Reduced Polish notation.
>>> polish_eval('+ 4 1')
5.0
>>> polish_eval('* 4 5')
20.0
>>> polish_eval('- 20 3')
17.0
>>> polish_eval('/ 20 5')
4.0
"""
oper, a, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a), float(b))
I'll leave the documentation of the Polish notation to you. No changes were made to infix_eval
.
Finally, we're on to full_eval
. Since there's been a major change in the notation we support, we'll have to rewrite the code. First, to start off with the tests:
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
Rewriting Polish notation without brackets would require the use of a stack. This will not fit nicely in your full_eval
function, so I will not implement the notation without brackets. In this function, the use of recursion is unnecessary and a while loop will fit the problem nicely.
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
"""
while len(expr.split()) != 1:
inn = innermost_parens(expr)
result = eval_type(inn.strip('(').strip(')').strip())
expr = expr.replace(inn, str(result))
return float(expr)
Before I evaluate inn
, I first strip off the brackets which enclose the expression, as well as any remaining whitespace.
The reason why I wrote innermost_parens
as shown above is to allow for the easy replacement of the expression. Here's a trace of an expression in Polish:
* (/ 9 3) (+ (/ 3 5) (- 20 5))
= * (/ 9 3) (+ 0.6 (- 20 5)) # '(/ 3 5)' -> '0.6'
= * (/ 9 3) (+ 0.6 15.0) # '(- 20 5)' -> '15.0'
= * 3.0 (+ 0.6 15.0) # '(/ 9 3)' -> '3.0'
= * 3.0 15.6 # '(+ 0.6 15.0)' -> '15.6'
= 46.8 # '* 3.0 15.6' -> '46.8'
and the same expression in infix:
(9 / 3) * ((3 / 5) + (20 - 5))
= (9 / 3) * (0.6 + (20 - 5)) # '(3 / 5)' -> '0.6'
= (9 / 3) * (0.6 + 15.0) # '(20 - 5)' -> '15.0'
= 3.0 * (0.6 + 15.0) # '(9 / 3)' -> '3.0'
= 3.0 * 15.6 # '(0.6 + 15.0)' -> '15.6'
= 46.8 # '3.0 * 15.6' -> '46.8'
Extensions:
- Write your program such that it can parse Polish notation without brackets. That's what Polish notation is invented for.
- Handle edge cases such as
((* 3 15))
(Polish). - Write test suites to ensure that the expressions are evaluated correctly. While addition and multiplication is commutative, subtraction and division are not, and the order of the numbers matter.
- Follow suggestions in l0b0's answer, which are cosmetic. I did not make any cosmetic suggestions as I thought that it was more important to settle the logic of the program first.
- Have your program create an abstract syntax tree instead of doing string operations (such as replacing innermost expression with its value).
Feel free to ask questions in the comments.
$endgroup$
add a comment |
$begingroup$
Good job on adding the test suites; they make testing code extremely easy.
Recursion often results in a clean solution. However, I think that this problem can be solved just as well with an iterative approach.
The key to innermost_parens
is to return the text between the last (
and the first )
. Recursion is a viable solution, but I find that this for loop does the job better and in a cleaner way:
def innermost_parens(text):
"""
Returns the innermost parenthesis.
>>> innermost_parens('* 1 2')
'* 1 2'
>>> innermost_parens("1 + (2 * (4 - 1))")
'(4 - 1)'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'(8 * 7)'
>>> innermost_parens("(1 + 2) * (3 + 4)")
'(1 + 2)'
"""
start = 0
end = len(text)
for (i, c) in enumerate(text):
if c == '(':
start = i
if c == ')':
end = i + 1
break
return text[start:end]
Take note that this function will return the first innermost expression with brackets. The reason for this will be clear later.
As for polish_eval
, your program doesn't follow the standard Polish notation. In the standard Polish notation you can only perform operations on two numbers. It is also unclear what 'start' does. I modified your code to do the above and added tests for subtraction and division:
def polish_eval(expr):
"""
Reduced Polish notation.
>>> polish_eval('+ 4 1')
5.0
>>> polish_eval('* 4 5')
20.0
>>> polish_eval('- 20 3')
17.0
>>> polish_eval('/ 20 5')
4.0
"""
oper, a, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a), float(b))
I'll leave the documentation of the Polish notation to you. No changes were made to infix_eval
.
Finally, we're on to full_eval
. Since there's been a major change in the notation we support, we'll have to rewrite the code. First, to start off with the tests:
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
Rewriting Polish notation without brackets would require the use of a stack. This will not fit nicely in your full_eval
function, so I will not implement the notation without brackets. In this function, the use of recursion is unnecessary and a while loop will fit the problem nicely.
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
"""
while len(expr.split()) != 1:
inn = innermost_parens(expr)
result = eval_type(inn.strip('(').strip(')').strip())
expr = expr.replace(inn, str(result))
return float(expr)
Before I evaluate inn
, I first strip off the brackets which enclose the expression, as well as any remaining whitespace.
The reason why I wrote innermost_parens
as shown above is to allow for the easy replacement of the expression. Here's a trace of an expression in Polish:
* (/ 9 3) (+ (/ 3 5) (- 20 5))
= * (/ 9 3) (+ 0.6 (- 20 5)) # '(/ 3 5)' -> '0.6'
= * (/ 9 3) (+ 0.6 15.0) # '(- 20 5)' -> '15.0'
= * 3.0 (+ 0.6 15.0) # '(/ 9 3)' -> '3.0'
= * 3.0 15.6 # '(+ 0.6 15.0)' -> '15.6'
= 46.8 # '* 3.0 15.6' -> '46.8'
and the same expression in infix:
(9 / 3) * ((3 / 5) + (20 - 5))
= (9 / 3) * (0.6 + (20 - 5)) # '(3 / 5)' -> '0.6'
= (9 / 3) * (0.6 + 15.0) # '(20 - 5)' -> '15.0'
= 3.0 * (0.6 + 15.0) # '(9 / 3)' -> '3.0'
= 3.0 * 15.6 # '(0.6 + 15.0)' -> '15.6'
= 46.8 # '3.0 * 15.6' -> '46.8'
Extensions:
- Write your program such that it can parse Polish notation without brackets. That's what Polish notation is invented for.
- Handle edge cases such as
((* 3 15))
(Polish). - Write test suites to ensure that the expressions are evaluated correctly. While addition and multiplication is commutative, subtraction and division are not, and the order of the numbers matter.
- Follow suggestions in l0b0's answer, which are cosmetic. I did not make any cosmetic suggestions as I thought that it was more important to settle the logic of the program first.
- Have your program create an abstract syntax tree instead of doing string operations (such as replacing innermost expression with its value).
Feel free to ask questions in the comments.
$endgroup$
add a comment |
$begingroup$
Good job on adding the test suites; they make testing code extremely easy.
Recursion often results in a clean solution. However, I think that this problem can be solved just as well with an iterative approach.
The key to innermost_parens
is to return the text between the last (
and the first )
. Recursion is a viable solution, but I find that this for loop does the job better and in a cleaner way:
def innermost_parens(text):
"""
Returns the innermost parenthesis.
>>> innermost_parens('* 1 2')
'* 1 2'
>>> innermost_parens("1 + (2 * (4 - 1))")
'(4 - 1)'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'(8 * 7)'
>>> innermost_parens("(1 + 2) * (3 + 4)")
'(1 + 2)'
"""
start = 0
end = len(text)
for (i, c) in enumerate(text):
if c == '(':
start = i
if c == ')':
end = i + 1
break
return text[start:end]
Take note that this function will return the first innermost expression with brackets. The reason for this will be clear later.
As for polish_eval
, your program doesn't follow the standard Polish notation. In the standard Polish notation you can only perform operations on two numbers. It is also unclear what 'start' does. I modified your code to do the above and added tests for subtraction and division:
def polish_eval(expr):
"""
Reduced Polish notation.
>>> polish_eval('+ 4 1')
5.0
>>> polish_eval('* 4 5')
20.0
>>> polish_eval('- 20 3')
17.0
>>> polish_eval('/ 20 5')
4.0
"""
oper, a, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a), float(b))
I'll leave the documentation of the Polish notation to you. No changes were made to infix_eval
.
Finally, we're on to full_eval
. Since there's been a major change in the notation we support, we'll have to rewrite the code. First, to start off with the tests:
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
Rewriting Polish notation without brackets would require the use of a stack. This will not fit nicely in your full_eval
function, so I will not implement the notation without brackets. In this function, the use of recursion is unnecessary and a while loop will fit the problem nicely.
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
"""
while len(expr.split()) != 1:
inn = innermost_parens(expr)
result = eval_type(inn.strip('(').strip(')').strip())
expr = expr.replace(inn, str(result))
return float(expr)
Before I evaluate inn
, I first strip off the brackets which enclose the expression, as well as any remaining whitespace.
The reason why I wrote innermost_parens
as shown above is to allow for the easy replacement of the expression. Here's a trace of an expression in Polish:
* (/ 9 3) (+ (/ 3 5) (- 20 5))
= * (/ 9 3) (+ 0.6 (- 20 5)) # '(/ 3 5)' -> '0.6'
= * (/ 9 3) (+ 0.6 15.0) # '(- 20 5)' -> '15.0'
= * 3.0 (+ 0.6 15.0) # '(/ 9 3)' -> '3.0'
= * 3.0 15.6 # '(+ 0.6 15.0)' -> '15.6'
= 46.8 # '* 3.0 15.6' -> '46.8'
and the same expression in infix:
(9 / 3) * ((3 / 5) + (20 - 5))
= (9 / 3) * (0.6 + (20 - 5)) # '(3 / 5)' -> '0.6'
= (9 / 3) * (0.6 + 15.0) # '(20 - 5)' -> '15.0'
= 3.0 * (0.6 + 15.0) # '(9 / 3)' -> '3.0'
= 3.0 * 15.6 # '(0.6 + 15.0)' -> '15.6'
= 46.8 # '3.0 * 15.6' -> '46.8'
Extensions:
- Write your program such that it can parse Polish notation without brackets. That's what Polish notation is invented for.
- Handle edge cases such as
((* 3 15))
(Polish). - Write test suites to ensure that the expressions are evaluated correctly. While addition and multiplication is commutative, subtraction and division are not, and the order of the numbers matter.
- Follow suggestions in l0b0's answer, which are cosmetic. I did not make any cosmetic suggestions as I thought that it was more important to settle the logic of the program first.
- Have your program create an abstract syntax tree instead of doing string operations (such as replacing innermost expression with its value).
Feel free to ask questions in the comments.
$endgroup$
Good job on adding the test suites; they make testing code extremely easy.
Recursion often results in a clean solution. However, I think that this problem can be solved just as well with an iterative approach.
The key to innermost_parens
is to return the text between the last (
and the first )
. Recursion is a viable solution, but I find that this for loop does the job better and in a cleaner way:
def innermost_parens(text):
"""
Returns the innermost parenthesis.
>>> innermost_parens('* 1 2')
'* 1 2'
>>> innermost_parens("1 + (2 * (4 - 1))")
'(4 - 1)'
>>> innermost_parens("1 + (2 * (4 * (2 + (8 * 7)) - 1))")
'(8 * 7)'
>>> innermost_parens("(1 + 2) * (3 + 4)")
'(1 + 2)'
"""
start = 0
end = len(text)
for (i, c) in enumerate(text):
if c == '(':
start = i
if c == ')':
end = i + 1
break
return text[start:end]
Take note that this function will return the first innermost expression with brackets. The reason for this will be clear later.
As for polish_eval
, your program doesn't follow the standard Polish notation. In the standard Polish notation you can only perform operations on two numbers. It is also unclear what 'start' does. I modified your code to do the above and added tests for subtraction and division:
def polish_eval(expr):
"""
Reduced Polish notation.
>>> polish_eval('+ 4 1')
5.0
>>> polish_eval('* 4 5')
20.0
>>> polish_eval('- 20 3')
17.0
>>> polish_eval('/ 20 5')
4.0
"""
oper, a, b = expr.split()
return OP_FOR_SYMBOL[oper](float(a), float(b))
I'll leave the documentation of the Polish notation to you. No changes were made to infix_eval
.
Finally, we're on to full_eval
. Since there's been a major change in the notation we support, we'll have to rewrite the code. First, to start off with the tests:
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
Rewriting Polish notation without brackets would require the use of a stack. This will not fit nicely in your full_eval
function, so I will not implement the notation without brackets. In this function, the use of recursion is unnecessary and a while loop will fit the problem nicely.
def full_eval(expr, eval_type):
"""
Evals by the rules of eval_type starting from the inner
parenthesis.
>>> full_eval("* (* 4 5) (+ 4 1)", polish_eval)
100.0
>>> full_eval("* 4 (/ 1 10)", polish_eval)
0.4
>>> full_eval("1 + (5 * 2)", infix_eval)
11.0
"""
while len(expr.split()) != 1:
inn = innermost_parens(expr)
result = eval_type(inn.strip('(').strip(')').strip())
expr = expr.replace(inn, str(result))
return float(expr)
Before I evaluate inn
, I first strip off the brackets which enclose the expression, as well as any remaining whitespace.
The reason why I wrote innermost_parens
as shown above is to allow for the easy replacement of the expression. Here's a trace of an expression in Polish:
* (/ 9 3) (+ (/ 3 5) (- 20 5))
= * (/ 9 3) (+ 0.6 (- 20 5)) # '(/ 3 5)' -> '0.6'
= * (/ 9 3) (+ 0.6 15.0) # '(- 20 5)' -> '15.0'
= * 3.0 (+ 0.6 15.0) # '(/ 9 3)' -> '3.0'
= * 3.0 15.6 # '(+ 0.6 15.0)' -> '15.6'
= 46.8 # '* 3.0 15.6' -> '46.8'
and the same expression in infix:
(9 / 3) * ((3 / 5) + (20 - 5))
= (9 / 3) * (0.6 + (20 - 5)) # '(3 / 5)' -> '0.6'
= (9 / 3) * (0.6 + 15.0) # '(20 - 5)' -> '15.0'
= 3.0 * (0.6 + 15.0) # '(9 / 3)' -> '3.0'
= 3.0 * 15.6 # '(0.6 + 15.0)' -> '15.6'
= 46.8 # '3.0 * 15.6' -> '46.8'
Extensions:
- Write your program such that it can parse Polish notation without brackets. That's what Polish notation is invented for.
- Handle edge cases such as
((* 3 15))
(Polish). - Write test suites to ensure that the expressions are evaluated correctly. While addition and multiplication is commutative, subtraction and division are not, and the order of the numbers matter.
- Follow suggestions in l0b0's answer, which are cosmetic. I did not make any cosmetic suggestions as I thought that it was more important to settle the logic of the program first.
- Have your program create an abstract syntax tree instead of doing string operations (such as replacing innermost expression with its value).
Feel free to ask questions in the comments.
edited Apr 13 '17 at 12:40
Community♦
1
1
answered Apr 9 '15 at 13:41
wei2912wei2912
893523
893523
add a comment |
add a comment |
$begingroup$
Some suggestions:
- Rather than ask whether all the following expressions are going to be in Polish or infix notation, you can simply check whether the first non-parenthesis character is a number or an operator.
START_FOR_SYMBOLS
could be renamed toOPERATION_IDENTITIES
for mathematical clarity.
Open issues:
- Why
doctest.testmod()
on every run? - There isn't always a unique innermost parenthesis, for example in the case of
(1 + 2) * (3 + 4)
. You probably want to return a list and process each of them in turn.
$endgroup$
add a comment |
$begingroup$
Some suggestions:
- Rather than ask whether all the following expressions are going to be in Polish or infix notation, you can simply check whether the first non-parenthesis character is a number or an operator.
START_FOR_SYMBOLS
could be renamed toOPERATION_IDENTITIES
for mathematical clarity.
Open issues:
- Why
doctest.testmod()
on every run? - There isn't always a unique innermost parenthesis, for example in the case of
(1 + 2) * (3 + 4)
. You probably want to return a list and process each of them in turn.
$endgroup$
add a comment |
$begingroup$
Some suggestions:
- Rather than ask whether all the following expressions are going to be in Polish or infix notation, you can simply check whether the first non-parenthesis character is a number or an operator.
START_FOR_SYMBOLS
could be renamed toOPERATION_IDENTITIES
for mathematical clarity.
Open issues:
- Why
doctest.testmod()
on every run? - There isn't always a unique innermost parenthesis, for example in the case of
(1 + 2) * (3 + 4)
. You probably want to return a list and process each of them in turn.
$endgroup$
Some suggestions:
- Rather than ask whether all the following expressions are going to be in Polish or infix notation, you can simply check whether the first non-parenthesis character is a number or an operator.
START_FOR_SYMBOLS
could be renamed toOPERATION_IDENTITIES
for mathematical clarity.
Open issues:
- Why
doctest.testmod()
on every run? - There isn't always a unique innermost parenthesis, for example in the case of
(1 + 2) * (3 + 4)
. You probably want to return a list and process each of them in turn.
answered Apr 9 '15 at 13:22
l0b0l0b0
4,6181023
4,6181023
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f86389%2frecursive-math-expression-eval%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
$begingroup$
How would you handle the case '- 20 5 3' for Polish notation? Your program returns '-28'. My suggestion is that you restrict the number of numbers (no pun intended) allowed to just 2; this makes the job much easier and allows people to write Polish notation without brackets.
$endgroup$
– wei2912
Apr 9 '15 at 13:11