Simon
Simon

Reputation: 35

Building a function with x,y parameters using Reverse Polish notation

I'm trying to build a python function from a input given in Reverse Polish notation. My function is supposed to be able to take in x and y values as parameters and return the value of the function for the respective x and y.

My input is for example:

"x 1 + y 3 * +"

My expected result would be a function object similar to:

fun = lambda x,y: x+1+y*3

So that I can call fun(1,2) and get 9 as the result.

So far I can evaluate input without variables and give the result.

bin_ops = {
    "+": (lambda a, b: a + b),
    "-": (lambda a, b: a - b),
    "*": (lambda a, b: a * b),
    "/": (lambda a, b: a / b),
}

def eval(expression):
    tokens = expression.split()
    stack = []

    for token in tokens:
        if token in bin_ops:
            arg2 = stack.pop()
            arg1 = stack.pop()

            result = bin_ops[token](arg1, arg2)
            stack.append(result)
        else:
            stack.append(float(token))

    return stack.pop()

txt = '2 1 + 2 3 * +'
print(eval(txt)) # prints 9.0

Can you help me how to build a function instead of directly the result and how I can process variables (such as x and y) properly?

Upvotes: 2

Views: 494

Answers (1)

Blckknght
Blckknght

Reputation: 104752

You can change your parsing code from creating values into creating functions that can be called with keyword arguments for final evaluation:

bin_ops = {
    "+": lambda a, b: lambda **namespace: a(**namespace) + b(**namespace),
    "-": lambda a, b: lambda **namespace: a(**namespace) - b(**namespace),
    "*": lambda a, b: lambda **namespace: a(**namespace) * b(**namespace),
    "/": lambda a, b: lambda **namespace: a(**namespace) / b(**namespace),
}

def eval(expression):
    tokens = expression.split()
    stack = []

    for token in tokens:
        if token in bin_ops:
            arg2 = stack.pop()
            arg1 = stack.pop()

            result = bin_ops[token](arg1, arg2)
            stack.append(result)
        else:
            try:
                num = float(token)
            except ValueError:
                stack.append(lambda *, _token=token, **namespace: namespace[_token])
            else:
                stack.append(lambda *, _num=num, **namespace: _num)

    return stack.pop()

txt = "x 1 + y 3 * +"
fun = eval(txt)
print(fun(x=2, y=4)) # prints 15.0

The way Python scopes work makes it a little tricky to generate functions that use variables from a loop. This is why I use token and num to set default values for arguments in our generated lambda functions, rather than using their names directly. If you just did lambda **namespace: namespace[token], you'd find that token had changed value as the loop kept running. The bin_ops code doesn't have that issue because the final functions are already nested in an extra scope, since the lambda a, b function is where we build the inner lambdas.

Upvotes: 1

Related Questions