Sinisterly

Full Version: How My CLI Calculator Works
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Ok, so I recently posted the code for my CLI Calculator in a challenge. It was requested of me to explain the code, so here is an explanation of why it works. Smile
If you don't understand something, just ask.

This is the code:
Code:
import re

def lex(equation):
    # remove all the whitespaces from the equation string
    equation = equation.replace(" ", "")
    return re.findall("[0-9]+|[\+, \-, \*, \/]", equation)

def evalMulDiv(tokens):
    i = 0
    while i < len(tokens):
        if tokens[i] == "*":
            tokens = tokens[:i - 1] + [int(tokens[i - 1]) * int(tokens[i + 1])] + tokens[i + 2:]
            i -= 2

        elif tokens[i] == "/":
            tokens = tokens[:i - 1] + [int(tokens[i - 1]) / int(tokens[i + 1])] + tokens[i + 2:]
            i -= 2

        i += 1

    return tokens

def evalAddSub(tokens):
    i = 0
    while i < len(tokens):
        if tokens[i] == "+":
            tokens = tokens[:i - 1] + [int(tokens[i - 1]) + int(tokens[i + 1])] + tokens[i + 2:]
            i -= 2

        elif tokens[i] == "-":
            tokens = tokens[:i - 1] + [int(tokens[i - 1]) - int(tokens[i + 1])] + tokens[i + 2:]
            i -= 2

        i += 1

    return tokens

def calc(equation):
    tokens = lex(equation)
    tokens = evalMulDiv(tokens)
    return int(evalAddSub(tokens)[0])

equation = raw_input("> ")
print(calc(equation))

There are four function in our code: lex, evalMulDiv, evalAddSub, calc.
The function lex just takes our input string and splits up the operands and operators into an array.
Our calc function is just for organization, it calls all the needed functions to calculate the value of our equation.
There are two separate eval functions because of operator precedence. Operator precedence is just the math rule that you must calculate the multiplication and division of a problem before the addition and subtraction.
You don't have to fully understand the eval functions, but here's how they work. First, in the evalMulDiv function, we loop through all the tokens in our equation... if we find either the * or / operator, we will do that specific math (* or /) on the two operands before and after us. The same is true for our evalAddSub function, except we find the + and - operators.
So, this is how it all works together:
Code:
lex("2+ 3*50") -> ["2", "+", "3", "*", "50"]
evalMulDiv(["2", "+", "3", "*", "50"]) -> ["2", "+", "150"]
evalAddSub(["2", "+", "150"]) -> ["152"]
Thanks m0dem! Is tokens a default variable in Python or am I just missing something in the code?

Could you just further explain what exactly this returns?
Code:
return re.findall("[0-9]+|[\+, \-, \*, \/]", equation)

Can you also explain the logic behind these further?
Code:
if tokens[i] == "*":
            tokens = tokens[:i - 1] + [int(tokens[i - 1]) * int(tokens[i + 1])] + tokens[i + 2:]
            i -= 2

I'm assuming you're just trying to find a multiplication symbol in the equation, and then just multiply the number that comes before and after it.
But wouldn't these be the only two you need: [int(tokens[i - 1]) * int(tokens[i + 1])]. I want to know the significance of the rest of that code.
(12-15-2015, 06:15 PM)God Wrote: [ -> ]Thanks m0dem! Is tokens a default variable in Python or am I just missing something in the code?

Could you just further explain what exactly this returns?
Code:
return re.findall("[0-9]+|[\+, \-, \*, \/]", equation)

Can you also explain the logic behind these further?
Code:
if tokens[i] == "*":
            tokens = tokens[:i - 1] + [int(tokens[i - 1]) * int(tokens[i + 1])] + tokens[i + 2:]
            i -= 2

I'm assuming you're just trying to find a multiplication symbol in the equation, and then just multiply the number that comes before and after it.
But wouldn't these be the only two you need: [int(tokens[i - 1]) * int(tokens[i + 1])]. I want to know the significance of the rest of that code.

A token is not a default variable, it is just a piece of data that you need to split your whole string up into.
That regular expression just splits the string at any number or operator (*, /, +, -) without removing that value. It's a bit hard to understand if you don't know RE.

The logic is, you have to put the new value into the tokens list correctly. Just can't just make a new list with that single number. You have to remove all 3 tokens that used to be there and insert in the one new calculated value.

I hope that make sense. Smile
(12-15-2015, 07:45 PM)m0dem Wrote: [ -> ]A token is not a default variable, it is just a piece of data that you need to split your whole string up into.
That regular expression just splits the string at any number or operator (*, /, +, -) without removing that value. It's a bit hard to understand if you don't know RE.

The logic is, you have to put the new value into the tokens list correctly. Just can't just make a new list with that single number. You have to remove all 3 tokens that used to be there and insert in the one new calculated value.

I hope that make sense. Smile

Okay, thanks. I guess you recommend reading into the re library more. I'm sure it'll help me understand the logic even more and how everything is being split up and stored.

So just to clarify, basically tokens is the variable for whatever "return re.findall("[0-9]+|[\+, \-, \*, \/]", equation)" returns? And then you're passing it into the eval functions to do whatever you need with it?
(12-15-2015, 08:03 PM)God Wrote: [ -> ]Okay, thanks. I guess you recommend reading into the re library more. I'm sure it'll help me understand the logic even more and how everything is being split up and stored.

So just to clarify, basically tokens is the variable for whatever "return re.findall("[0-9]+|[\+, \-, \*, \/]", equation)" returns? And then you're passing it into the eval functions to do whatever you need with it?

Our tokens variable is just the list of characters (e.g. ["5", "+", "8", "*", "10"]) our regular expression returns. And then I pass those tokens to the eval's.
btw: I'm not a huge fan of regular expressions.
(12-15-2015, 08:22 PM)m0dem Wrote: [ -> ]Our tokens variable is just the list of characters (e.g. ["5", "+", "8", "*", "10"]) our regular expression returns. And then I pass those tokens to the eval's.
btw: I'm not a huge fan of regular expressions.

Okay makes sense. Thanks for the explanation.

Do you have a Skype?
Nice work. I made a oneline calculator challenge on another forum thinking my ruby answer was hot shit, then someone reminded me that eval() existed Tongue

Anyway, you could change your lex function to the below code, because the findall skips over anything that isn't an integer, period, or operator (which would support float input with some tweaking):
Code:
def lex(equation):
    #\d is the same as [0-9]
    return re.findall("\d+|\.|[\+, \-, \*, \/]",equation)
(12-16-2015, 04:40 AM)Nevermore Wrote: [ -> ]Nice work. I made a oneline calculator challenge on another forum thinking my ruby answer was hot shit, then someone reminded me that eval() existed Tongue

Anyway, you could change your lex function to the below code, because the findall skips over anything that isn't an integer, period, or operator (which would support float input with some tweaking):
Code:
def lex(equation):
    #\d is the same as [0-9]
    return re.findall("\d+|\.|[\+, \-, \*, \/]",equation)

Hey @Nevermore! Good to see you back. Smile
That person who reminded you about eval() was wrong. Eval() can be a big security risk, and it is just not good practice. (kind of like os.system())
Plus a lot can be learned by making your own parsers. xD
This stuff is very applicable to making programming languages! Wink (really cool stuff)
Thanks for the suggestions! Smile As you can probably tell, I'm not great with RE yet. (haven't used it much)