【问题标题】:Remove redundant parentheses from an arithmetic expression从算术表达式中删除多余的括号
【发布时间】:2013-08-23 10:37:38
【问题描述】:

这是一个面试问题,我在stackoverflow或外部都没有找到任何令人满意的答案。问题陈述:

给定一个算术表达式,删除多余的括号。例如。 ((a*b)+c) 应该变成 a*b+c

我可以想到一种将中缀表达式转换为后缀表达式并将其转换回中缀的明显方法 - 但有更好的方法吗?

【问题讨论】:

标签: algorithm data-structures stack permutation


【解决方案1】:

一对括号是必要的,当且仅当它们包含 X % X % ... % X 形式的无括号表达式,其中 X 是带括号的表达式或原子,并且 % 是二元运算符,并且如果至少一个of the operator % 的优先级低于直接附加到其任一侧括号表达式的运算符;或者如果它是整个表达式。所以例如在

q * (a * b * c * d) + c

周围的运算符是{+, *},括号内最低优先级的运算符是*,所以括号是不必要的。另一方面,在

q * (a * b + c * d) + c

括号内的运算符 + 的优先级低于周围的运算符 *,因此它们是必需的。然而,在

z * q + (a * b + c * d) + c

括号不是必需的,因为外部 * 没有附加到带括号的表达式。

为什么这是真的,如果表达式中的所有运算符 (X % X % ... % X) 的优先级高于周围的运算符,那么即使括号被删除,内部运算符也会首先计算出来.

因此,您可以通过此算法直接检查任何一对匹配的括号是否存在冗余:

Let L be operator immediately left of the left parenthesis, or nil
Let R be operator immediately right of the right parenthesis, or nil
If L is nil and R is nil:
  Redundant
Else:
  Scan the unparenthesized operators between the parentheses
  Let X be the lowest priority operator
  If X has lower priority than L or R:
    Not redundant
  Else:
    Redundant

您可以重复此操作,删除冗余对,直到所有剩余的对都是非冗余的。

例子:

((a * b) + c * (e + f))

(从左到右处理对):

((a * b) + c * (e + f))   L = nil R = nil --> Redundant
^                     ^   
 (a * b) + c * (e + f)    L = nil R = nil --> Redundant
 ^     ^                  L = nil R = + X = * --> Redundant
  a * b  + c * (e + f)    L = * R = nil X = + --> Not redundant
               ^     ^

最终结果:

a * b + c * (e + f)

【讨论】:

  • 除法会失​​败,对吗? a/(b/c)。示例 a = 12,b = 4,c = 3。a/b/c = 1 但 a/(b/c) = 9。
  • @JasonLepack 减法有同样的问题,例如a-b-c 与 a-(b-c) 不同。您只需要将减法符号解释为向右递减优先级,因此在 a-b-c 中,第二个 - 的优先级低于第一个。然后算法起作用; a-(b-c) 有非冗余括号,因为低优先级运算符是括号,但 (a-b)-c 有冗余括号。
  • @AnttiHuima 。我们如何知道要匹配哪些括号?例如,此时 。 (a+b) + c* (e+f),不要 LR 指向第一个左括号指向 0 和 12 处的字符?由于他们的左右没有运算符,所以表达式不会变成a+b) +c * (e +
  • 另外 - 如果你将 c 表示为 (c) - 或者它自己的任何单个变量也总是多余的 - 在你的 a b c d e 示例中......
【解决方案2】:

我刚刚想出了一个答案:

前提是:

1. the expression has been tokenized
2. no syntax error
3. there are only binary operators

输入:

list of the tokens, for example:
   (, (, a, *, b, ), +, c, )

输出:

set of the redundant parentheses pairs (the orders of the pairs are not important),
for example,
   0, 8
   1, 5

请注意:集合不是唯一的,例如((a+b))*c,我们可以去掉外括号或内括号,但最终表达式是唯一的

数据结构:

a stack, each item records information in each parenthese pair
the struct is:
   left_pa: records the position of the left parenthese
   min_op: records the operator in the parentheses with minimum priority
   left_op: records current operator

算法

1.push one empty item in the stack
2.scan the token list
    2.1 if the token is operand, ignore
    2.2 if the token is operator, records the operator in the left_op, 
        if min_op is nil, set the min_op = this operator, if the min_op 
        is not nil, compare the min_op with this operator, set min_op as 
        one of the two operators with less priority
    2.3 if the token is left parenthese, push one item in the stack, 
        with left_pa = position of the parenthese
    2.4 if the token is right parenthese, 
        2.4.1 we have the pair of the parentheses(left_pa and the 
             right parenthese)
        2.4.2 pop the item
        2.4.3 pre-read next token, if it is an operator, set it 
             as right operator
        2.4.4 compare min_op of the item with left_op and right operator
             (if any of them exists), we can easily get to know if the pair 
             of the parentheses is redundant, and output it(if the min_op
             < any of left_op and right operator, the parentheses are necessary,
             if min_op = left_op, the parentheses are necessary, otherwise
             redundant) 
        2.4.5 if there is no left_op and no right operator(which also means 
             min_op = nil) and the stack is not empty, set the min_op of top 
             item as the min_op of the popped-up item

例子

示例一

((a*b)+c)

扫描到 b 后,我们有堆栈:

index left_pa min_op left_op
0
1     0       
2     1       *      *       <-stack top

现在我们遇到第一个')'(在位置 5),我们弹出项目

left_pa = 1 
min_op = *
left_op = *

和预读运算符'+',由于min_op优先级'*'>'+',所以pair(1,5)是多余的,所以输出。 然后扫描直到我们遇到最后一个')',此刻,我们有堆栈

index left_pa min_op left_op
0
1     0       +      + 

我们弹出这个项目(因为我们在pos 8遇到')'),并且预读next运算符,因为没有运算符并且在索引0处没有left_op,所以输出pair(0, 8)

示例二

a*(b+c)

当我们遇到')'时,栈是这样的:

index  left_pa  min_op left_op
0               *      *
1      2        +      +

现在,我们在 index = 1 处弹出 item,比较 min_op '+' 和 left_op '*' 在 index 0 处,我们可以发现 '(',')' 是必要的

【讨论】:

    【解决方案3】:

    如果表达式有效,则此解决方案有效。我们需要将运算符映射到优先级值。

    一个。从数组的两端遍历,找出两端匹配的括号。 设索引分别为 i 和 j。

    b.现在从 i 遍历到 j 并找出不包含在任何括号内的最低优先级运算符。

    c。将此运算符的优先级与左括号左侧和右括号右侧的运算符进行比较。如果不存在这样的运算符,则将其优先级视为-1。如果运算符的优先级高于这两个,则去掉 i 和 j 处的括号。

    d。继续 a 到 c 的步骤,直到 i

    【讨论】:

      【解决方案4】:
      1. 在堆栈中压入一个空项
      2. 扫描令牌列表

        2.1 如果标记是操作数,则忽略。

        2.2 如果token是operator,将operator记录在left_op中, 如果 min_op 为 nil,则设置 min_op = this 运算符,如果 min_op 不为零,将 min_op 与此运算符进行比较,将 min_op 设置为 优先级较低的两个运算符之一。

        2.3 如果token是左括号,则压栈一项, with left_pa = 括号的位置。

        2.4 如果token是右括号:

        2.4.1 我们有一对括号(left_pa 和 右括号)

        2.4.2 弹出项目

        2.4.3 预读下一个token,如果是算子,设置 作为右运算符

        2.4.4 将 item 的 min_op 与 left_op 和 right 运算符进行比较 (如果它们中的任何一个存在),我们可以很容易地知道这对 括号是多余的,并输出它(如果 min_op

        2.4.5 如果没有 left_op 也没有 right 运算符(这也意味着 min_op = nil) 且栈不为空,设置 top 的 min_op item 作为弹出项的 min_op 例子

      【讨论】:

        【解决方案5】:

        以防万一有人在寻找快速简便的解决方案时发现此问题:如果您的表达式已被清理并且您的语言(或库)为您的表达式提供了 eval 函数,您可以尝试删除一对括号会改变你的表达式的值。如果是这样,您将不得不保留括号。如果没有,您可以删除它们。

        但是,请记住,这当然不是一个有效的解决方案,而是“蛮力”路径。

        这是 Python 中的一个示例性实现,适用于整数和内置 eval

        def remove_brackets(term):
            a = 0
            while True:
                # Find opening bracket
                try:
                    a = term.index("(", a)
                except ValueError:
                    # No (more) opening brackets found
                    break
                # Find corresponding closing bracket
                b = a
                while True:
                    b = term.index(")", b + 1)
                    if term[a + 1:b].count("(") == term[a + 1:b].count(")"):
                        break
                # Assemble new term by removing current pair of brackets
                new_term = term[:a] + term[a + 1:b] + term[b + 1:]
                # If new term produces a different value, keep term as it is and try with the next pair of brackets
                if eval(term) != eval(new_term):
                    a += 1
                    continue
                # Adopt new term
                term = new_term
            return term
        

        示例调用:

        >>> remove_brackets("1 + (2 * 3)")
        '1 + 2 * 3'
        >>> remove_brackets("1 + (2 * 3) / 4")
        '1 + 2 * 3 / 4'
        >>> remove_brackets("1 + (2 * 3) / 4 / (5 * 6)")
        '1 + 2 * 3 / 4 / (5 * 6)'
        

        【讨论】:

        • 当所有术语都是常量时,这可能会起作用,但这并不是一个非常有用的情况。如果有变量,那么您必须为变量选择“合适”的值,并且冒着风险选择那些巧合地不需要括号的值,即使它们在一般情况下也是如此。
        【解决方案6】:

        下面的代码实现了一个简单的解决方案。它仅限于+-*/,但如果需要,可以扩展以处理其他运算符。

        #include <iostream>
        #include <set>
        #include <stack>
        
        int loc;
        
        std::string parser(std::string input, int _loc) {
          std::set<char> support = {'+', '-', '*', '/'};
        
          std::string expi;
          std::set<char> op;
          loc = _loc;
        
          while (true) {
            if (input[loc] == '(') {
              expi += parser(input, loc + 1);
            } else if (input[loc] == ')') {
              if ((input[loc + 1] != '*') && (input[loc + 1] != '/')) {
                return expi;
              } else {
                if ((op.find('+') == op.end()) && (op.find('-') == op.end())) {
                  return expi;
                } else {
                  return '(' + expi + ')';
                }
              }
            } else {
              char temp = input[loc];
              expi = expi + temp;
        
              if (support.find(temp) != support.end()) {
                op.insert(temp);
              }
            }
        
            loc++;
        
            if (loc >= input.size()) {
              break;
            }
          }
        
          return expi;
        }
        
        int main() {
          std::string input("(((a)+((b*c)))+(d*(f*g)))");
          std::cout << parser(input, 0);
        
          return 0;
        }
        

        【讨论】:

          【解决方案7】:

          我认为您正在寻找如下图所示的算法。

          这个算法“几乎”准备好了,因为一旦它变得越复杂,就会出现很多错误,它变得越复杂。我在这件事上的工作方式是“即时构建和编写代码”,这意味着对于最多 4 个括号,事情很容易。但是在表达变得更复杂之后,有些事情我在纸上写下想法时无法预测。编译器会告诉我要纠正什么。如果我说不是我编写算法,而是(C#)编译器,那将不是谎言!到目前为止,我花了 1400 行。并不是说这些命令很难写。真正令人困惑的是他们的安排。您正在寻找的这个程序的特点是非常复杂。好吧,如果您需要任何主要想法,请告诉我,我会回复。谢谢!

          Algorithm

          【讨论】:

          • ...在下面的照片中。 什么照片?此外,不要鼓励用户与您联系或回复以获取解决方案。在此处发布整个解决方案,或提供一些上下文和链接。像这样不完整的答案对 SO 不利。
          猜你喜欢
          • 1970-01-01
          • 2021-10-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-04
          • 2021-02-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多