使用递归来解决用户输入的表达式(计算器)

时间:2012-12-02 01:43:15

标签: python recursion calculator

作为一项任务,我必须编写一个接受和解决用户输入的计算器,例如:

3 + 7 * (!3)^2

必须使用递归解决这个问题。

def solve(exp):
     #Recursion if statement
     for op in exp:
       if op == "*":
           ...
       elif op == "/":
           ...
       ...
     return solve(exp)

这就是我假设我的代码看起来像的样子,但是我无法确定递归的停止条件。
另外,我不确定这种循环是否有用,因为我将无法处理-3 +5等几种情况,或处理括号的使用。

我认为我对如何解决它的基本想法并不好,我想就如何解决这个问题提出建议。

2 个答案:

答案 0 :(得分:3)

使用'标准'语法解析一个表达式,通过递归正确几乎是不可能的:

E = E + E | E - E | ... | VALUE

这是因为调用E可能会产生无限递归,还有其他问题,例如运算符优先级以及关联性......

有两种众所周知的处理此类问题的方法,实际上这两种方法适用于大多数问题。

1)使用自下而上的方法,一个例子是shift-reduce解析http://en.wikipedia.org/wiki/Shift-reduce_parser这个方法以代码复杂性为代价来保持你的语法。

2)使用自上而下的方法,一个示例是递归 - 体面但具有不同的语法,http://en.wikipedia.org/wiki/LL_parser一个没有左递归,这通常通过修改原始语法并更新所有规则来删除任何直接左边递归调用。这会修改语法(如果可能的话),使其更长,但代码更简单。

由于您要求该方法是递归的,可能第二种方法是更好的方法。

我们首先必须定义这个新语法,然后为每个规则创建一个新函数。

你举了这个例子:3 + 7 * (!3)^2所以我按降序顺序推导出以下运算符。

operators: +, -, *, /, ^, -, !
values: 0,1,2,3 ...

这是一个简单的,众所周知的LL(1)语法,它具有优先权......

<arith_expr>  : <term> {+ <term>}
              | <term> {- <term>}

<term>        : <power> {* <power>}
              | <power> {/ <power>}

<power>       : <factor> <exponent>
<exponent>    : ^ <factor> <exponent> | ''

<factor>      | NUMERIC_VALUE
              | - <factor>
              | ! <factor>
              | ( <arith_expr> )

它的一些等价于前一个语法,但没有直接的左递归调用,将语法转换为LL(*)语法的过程有点机械......

我通常不会给出作业的代码,特别是简单的代码,但这只是为了让你开始,你应该能够实现其余的。

因为一般的空格是忽略的,所以我们将输入量化为一组适用于我们语法的每个规则的值,排除任何空格,除非我们的语法另有规定,这个过程叫做tokinization,每个值都称为虽然使用regexp是常见的,但我更喜欢手动方法以便于阅读...... Tokinization非常简单......

"""
`{}` means 0 or more  
<arith_expr>  : <term> {+ <term>}
              | <term> {- <term>}

<term>        : <factor> {* <factor>}


<factor>      | NUMERIC_VALUE
              | - <factor>
              | ( <arith_expr> )
"""

import string

def evaluate(str_value):

    def tokenize(value):
        all_symbols = set(('+', '-', '*',)) | set(('(', ')'))
        white_space = set((' ', '\n', '\t'))
        tokens = []
        index = 0
        while index < len(value):
            current_char = value[index]
            if current_char in white_space:
                index += 1
                continue 
            if current_char in all_symbols:
                tokens.append(current_char)
                index += 1
                continue
            if (current_char in string.digits) or (current_char == '.'):
                numeric_value = current_char
                index += 1                
                while (index < len(value)) and ((value[index] in string.digits) or (value[index] == '.')):
                    if (values[index] == '.') and ('.' in numeric_value):
                        raise Exception('Multiple decimal points detected while tokenizing numeric value!')
                    numeric_value.append(value[index])
                    index += 1
                tokens.append(float(numeric_value) if '.' in numeric_value else int(numeric_value))
                continue
            raise Exception('Unable to tokenize symbol %s' % value[index])
        return tokens


    def factor(tokens):
        '''
        <factor>      | NUMERIC_VALUE
                      | - <factor>
                      | ( <arith_expr> )
        '''        
        if not tokens:
            return None
        if type(tokens[0]) in set((int, float)): # NUMERIC_VALUE
            return tokens.pop(0)
        if tokens[0] == '-':                     # - <factor>
            tokens.pop(0)
            return -1 * factor(tokens)  
        if tokens[0] == '(':                     # ( <arith_expr> )
            tokens.pop(0)
            value = arith_expr(tokens)
            assert tokens.pop(0) == ')'
            return value

    def term(tokens):
        '''
        <term>        : <factor> {* <factor>}

        '''
        left_value = factor(tokens)
        operators = set(('*',))
        while tokens and (tokens[0] in operators):  # {* <factor>}
            op = tokens.pop(0)
            right_value = factor(tokens)
            left_value *= right_value
        return left_value




    def arith_expr(tokens):
        '''
        <arith_expr>  : <term> {+ <term>}
                      | <term> {- <term>}
        '''

        left_value = term(tokens)
        operators = set(('+', '-'))
        while tokens and (tokens[0] in operators):
            op = tokens.pop(0)
            right_value = term(tokens)
            if op == '+':
                left_value += right_value
            else:
                left_value -= right_value
        return left_value 


    return arith_expr(tokenize(str_value))

注意:这还没有经过测试!

答案 1 :(得分:0)

两条评论:

  1. 您当前的解决方案不会返回任何有趣的内容,实际上它似乎无限循环,因为您传递到递归的下一个阶段的exp与最初传递的相同,所以没有指示任何一个循环通过递归实际上对最终解决方案有任何影响。
  2. 停止条件应为空表达式,即如果将表达式存储为字符串,则应为“”。