Search code examples
pythonfunctionparsingrpn

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?


Solution

  • 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.