Learning Python
Xavier Morel
xavier.morel at masklinn.net
Sun Feb 5 13:46:25 EST 2006
Byte wrote:
>> parse the expression, extract the operands and the operation, apply the
> operation to the operands
>
> How? Give me some example code, but please keep it simple.
>
>> are you trying to code a calculator?
>
> Not intending to, just trying to learn Python. Suppose what i'm trying
> to code is a but like a CLI calculator, but i'm just writing it for my
> own educational beifits.
>
Side note: have you read Dive into Python (http://diveintopython.org/)
yet? If you haven't -- and are done reading the whole python tutorial --
you should, it's a very good and practical read.
Now about the calculator, let's do a simple RPN notation (they're much
simpler to compute than the "regular" notation, see
http://en.wikipedia.org/wiki/RPN for some informations about RPN)
First, we need an input
-->
expression = raw_input("> ")
<--
Now we need to handle this input. An RPN expression is a space-separated
list of tokens, these tokens being either operands (numbers) or
operations. This means that we merely need to split our expression at
the spaces
-->
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default
split delimiter
<--
Now we have a list of tokens.
RPN calculations involve a stack, to keep the current operands. Python's
`list` has everything you need to emulate a stack
-->
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default
split delimiter
stack = list()
<--
Now, we have to handle each token.
Either a token is a number (an operand), in which case we have to
convert it to an integer (it's currently stored as a string), or it's an
operation, in which case we have to apply the operation on the last two
tokens of the computation stack and store the result back on the stack.
This means that we have to store addition, substraction, multiplication
and division. Luckily, there is the python module `operator` that stores
an accessor to these operations (add, sub, mul, div). We could store
them in a dictionary with an operation token as index: we feed the
operation token to the dict, it outputs the operation function.
-->
import operator
operations = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.div
}
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default
split delimiter
stack = list()
<--
Good, now all we need to know is to feed each token to the `operations`
dict, let's try with the first token of the RPN expression "1 2 + 4 * 3 +"
>>> operations['1']
Traceback (most recent call last):
File "<pyshell#12>", line 1, in -toplevel-
operations['1']
KeyError: '1'
Well, there is no "1" key in our dictionary, so we get an error. Kind of
obvious.
The Python dictionaries also have a "get" method that takes a key and a
value, and they return the value (default) if the key doesn't exist, how
would that one fare?
>>> operations.get('1',None)
>>> operations.get('2',None)
>>> operations.get('+',None)
<built-in function add>
>>>
Ok, it's return the operation function if it knows the token we feed it,
and "None" if it doesn't.
Now all we have to do is check if it returns None, and if it does we
know it's an operand and not an operation.
First we need to loop on the list of tokens:
-->
import operator
operations = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.div
}
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default
split delimiter
stack = list()
for token in tokens:
operation = operations.get(token, None)
if operation: # If operation is not "None", therefore if the token
was an operation token
pass # do nothing for now
else: # if operation is "None" == if the token is an operand token
pass # do nothing either
<--
Ok, what are we supposed to do if the token is an operand? Convert the
operand (token) to an integer (the token is a string), and then add it
to our computational stack.
The last part is done via the `append` methods of Python's list.
If the token is an operation, we have to apply the operation to the last
two operands of the stack, and push the result back on the stack
To get the last operation of a Python list, we use the `pop`method, it
defaults to returning the last value of a list.
-->
import operator
operations = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.div
}
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default
split delimiter
stack = list()
for token in tokens:
operation = operations.get(token, None)
if operation: # If operation is not "None", therefore if the token
was an operation token
result = operation(stack.pop(), stack.pop()) # apply the
operation on the last two items of the stack
stack.append(result) # put the result back on the stack
else: # if operation is "None" == if the token is an operand token
stack.append(int(token)) # Convert the token to an integer and
push it on the stack
<--
Ok, let's test that shall we?
What happens if we feed it "1 2 + 4 * 3 +", this should return 15 =
(1+2)*4+3.
The result is the only item that should be left on our stack
-->
>>> operations = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.div
}
>>> expression = raw_input("> ")
> 1 2 + 4 * 3 +
>>> tokens = expression.split() # split a string using spaces as
default split delimiter
>>> stack = list()
>>> for token in tokens:
operation = operations.get(token, None)
if operation: # If operation is not "None", therefore if the token was
an operation token
result = operation(stack.pop(), stack.pop()) # apply the operation on
the last two items of the stack
stack.append(result) # put the result back on the stack
else: # if operation is "None" == if the token is an operand token
stack.append(int(token)) # Convert the token to an integer and push it
on the stack
>>> stack
[15]
>>>
<--
Gread. Now let's try it again with the expression "1 4 -". This should
return -3.
-->
>>> # snip
>>> stack
[3]
<--
Duh, the result is wrong.
This is because when we do operation(stack.pop(), stack.pop()), we use
the last value of the stack as the first operand of our operation (here,
our expression resolves to "operator.sub(4, 1)" which is equivalent to
"4-1", which is wrong).
Let's get out operands in the good order by replacing that line with
-->
operand2 = stack.pop()
operand1 = stack.pop()
result = operation(operand1, operand2)
<--
And now it works, we have a basic RPN calculator.
Homework: truth is that this calculator kind of sucks: it's very easy to
break it.
Try using "-" as your input expression. Or "1 +", or "1 56 + +". What
happens?
Try to modify the calculator to avoid these errors, or handle them
gracefully.
Likewise, entering the expression "5 0 /" results in a Division By Zero
error. Try to handle it to display a friendly message instead of having
the calculator blow.
You have to rewrite everything every time you want to compute a new
expression. Modify the calculator to always ask for new expression until
a specific command is entered (such as "exit")
The calculator breaks (again) if you use a token that is neither an
integer nor an operation (e.g. "1 foo +", or even "foo")
Modify it to handle that kind of false input without exploding in your
hands.
The output is ugly (a list of only one item), try to improve it.
Computing the expression "5 3 /" (5/3) returns "1". Why? How can you
improve it?
Additionally, you currently cannot use floating point numbers (e.g. "1.5
1.3 +") in your expressions (guess what the calculator does?), see if
you could modify the calculator to handle floating point numbers.
Last, but not the least, try to create a Polish Notation calculator
(prefix notation, it's like RPN but the operation is in front of the
operand) and a "maths notation" calculator.
More information about the Python-list
mailing list