【问题标题】:Modify piece of code to allow floats and negative numbers as well as an arbitrary amount of spaces in between the characters in the inputted string修改一段代码以允许浮点数和负数以及输入字符串中字符之间的任意数量的空格
【发布时间】:2023-10-18 01:19:01
【问题描述】:

以下代码获取一个中缀字符串并将其转换为后缀并将新表达式作为字符串输出。但是它不支持负数或浮点数。以下代码仅允许单个数字值:

如 (0-9) 与 10 或 11 完全不同。否则会抛出 “key error”。此外,如果我添加一个负号,它也会引发一个关键错误。

class Stack:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items)-1]

    def size(self):
        return len(self.items)

    def isNumber(self, txt):
        if not isinstance(txt,str) or len(txt.strip())==0:
            print("Argument error in isNumber")
            return False
        # YOUR CODE STARTS HERE
        try:
            float(txt)
            return True
        except ValueError:
            return False

#########################################################################################################

    def infixToPostfix(infixexpr):
        prec = {}
        prec["^"] = 4
        prec["*"] = 3
        prec["/"] = 3
        prec["+"] = 2
        prec["-"] = 2
        prec["("] = 1
        opStack = Stack()
        postfixList = []
        tokenList = infixexpr.split()

        for token in tokenList:
            if token in "0123456789":
                postfixList.append(token)
            elif token == '(':
                opStack.push(token)
            elif token == ')':
                topToken = opStack.pop()
                while topToken != '(':
                    postfixList.append(topToken)
                    topToken = opStack.pop()
            else:
                while (not opStack.isEmpty()) and \
                   (prec[opStack.peek()] >= prec[token]):
                      postfixList.append(opStack.pop())
                opStack.push(token)

        while not opStack.isEmpty():
            postfixList.append(opStack.pop())
        return " ".join(postfixList)

所以这是我允许浮动的解决方法:

我添加了这个功能:

def isNumber(x):
    try:
        float(x)
        return True
    except ValueError:
        return False

并将这一行:if token in "0123456789": 改为:if Stack.isNumber(token):

现在代码允许浮动。


那么另一个问题是什么?另一个问题是我的代码假设输入字符串在每个字符之间只有一个空格,因此我 string.split() 将所有字符放入列表中。除了输入字符串在字符之间可以有任意数量的空格,如果没有空格,我的代码会将“((”)与我的字符列表进行比较,但找不到它并抛出 Key 错误。因此,由于我必须允许负数(用减号表示)。如何修改我的代码,使其不再抛出 keyerror 并允许我使用负数?


当我这样做时:

print(Stack.infixToPostfix("( ( 1 + 3 ) ) * 4 - ( 9.2 - 0 ) * ( 5 + 8 )"))

我的代码输出如下: 1 3 + 4 * 9.2 0 - 5 8 + * -

效果很好,但是如果我删除一个空格:

"(( 1 + 3 ) ) * 4 - ( 9.2 - 0 ) * ( 5 + 8 )"

我的代码不再有效。 Key error '(('我知道它为什么会抛出这个错误(上面的解释),但我不知道如何解决它。


所以最后一个问题 TL:DR

如何修改我的中缀后缀代码以允许字符之间有任意数量的空格并允许负数?

【问题讨论】:

  • 所以你的代码会移动负号,即使它应该在转换后保持在数字前面,对吗? (假设中间有一个空格,否则会抛出该关键错误)。这很难,我建议重写你的整个代码。
  • @QuoraExpert 是的,它会移动负号。
  • 它只需要解析文本(tokenize)。天真地分割空间是实现状态引擎的一个很好的起点,但它对输入数据施加了太多的限制。这是一个很好的编程,不过,一次只担心一项任务。
  • 变量和函数名应该遵循lower_case_with_underscores风格。

标签: python string list postfix-notation infix-notation


【解决方案1】:

首先创建一个单独的函数,该函数将从您的字符串中生成一个标记列表。标记是数字(不带符号)或单个字符:

def tokenize(s):
    s = re.sub(r"\s+", "", s)
    result = []
    while (s):
        if s[0] in "0123456789":
            number = s[0]
            s = s[1:]
            while (s and s[0] in "0123456789."):
                number += s[0]
                s = s[1:]
            result.append(number)
            if s:
                result.append(s[0])
                s = s[1:]
        else:
            result.append(s[0])
            s = s[1:]
    return result

然后您需要跟踪一元加减运算。为此,我们引入了一个特殊的“否定”操作——当您以后缀表示法处理此操作时,您只需取反操作数堆栈顶部的值。

您希望在字符串的开头或在开头的“(”之后进行一元加减运算。在处理数字操作数或结束“)”之后,您将一元标志重置为 False,因为一元加号或减号不能出现在这些位置。当一元标志为真时,您必须跟踪传入的'+'和'-',使用布尔标志'neg'。在每个“-”处更改“否定”状态。当您最终找到一个操作数时 - 检查“否定”标志的状态。如果为真,那么您需要将我们特殊的“否定”操作放在操作数之后。在关闭 ')' 之后放置一个 'neg' 操作有点棘手,需要使用 opStack。

def infixToPostfix(infixexpr):
        prec = {}
        prec["^"] = 3
        prec["*"] = 3
        prec["/"] = 3
        prec["+"] = 2
        prec["-"] = 2
        prec["("] = 1
        prec["neg"] = 1
        opStack = Stack()
        postfixList = []
        tokenList = tokenize(infixexpr)
        print(tokenList)

        unary = True
        neg = False

        for token in tokenList:
            if unary and token in "+-":
                if token == '-':
                     neg = not neg
            elif isNumber(token):
                postfixList.append(token)
                if neg:
                    postfixList.append("neg")
                    neg = False
                unary = False
            elif token == '(':
                if neg:
                    opStack.push("neg")
                    neg = False
                opStack.push(token)
                unary = True
            elif token == ')':
                topToken = opStack.pop()
                unary = False
                while topToken != '(':
                    postfixList.append(topToken)
                    topToken = opStack.pop()
                if not opStack.isEmpty() and opStack.peek() == "neg":
                    postfixList.append(opStack.pop())
            else:
                while (not opStack.isEmpty()) and \
                   (prec[opStack.peek()] >= prec[token]):
                      postfixList.append(opStack.pop())
                opStack.push(token)

        while not opStack.isEmpty():
            postfixList.append(opStack.pop())
        return " ".join(postfixList)

输入:

"-(( 1 + 3 ) ) * 4 - ( -9.2 - 0 ) * ( 5 + 8 ) - 4 * (-2)"

输出:

1 3 + neg 4 * 9.2 neg 0 - 5 8 + * - 4 2 neg * -

2020 年 3 月 12 日更新

如果您想将负数作为单个负操作数处理,而不是像一个正操作数后跟一个“neg”操作,那么您只需要对 infixToPostfix 方法进行非常小的修改。您只需要修改elif isNumber(token) 分支。不过我会把它完整地放在这里:

def infixToPostfix(infixexpr):
        prec = {}
        prec["^"] = 3
        prec["*"] = 3
        prec["/"] = 3
        prec["+"] = 2
        prec["-"] = 2
        prec["("] = 1
        prec["neg"] = 1
        opStack = Stack()
        postfixList = []
        tokenList = tokenize(infixexpr)

        unary = True
        neg = False

        for token in tokenList:
            if unary and token in "+-":
                if token == '-':
                     neg = not neg
            elif isNumber(token):
                if neg:
                    postfixList.append("-" + token)
                else:
                    postfixList.append(token)
                neg = False
                unary = False
            elif token == '(':
                if neg:
                    opStack.push("neg")
                    neg = False
                opStack.push(token)
                unary = True
            elif token == ')':
                topToken = opStack.pop()
                unary = False
                while topToken != '(':
                    postfixList.append(topToken)
                    topToken = opStack.pop()
                if not opStack.isEmpty() and opStack.peek() == "neg":
                    postfixList.append(opStack.pop())
            else:
                while (not opStack.isEmpty()) and \
                   (prec[opStack.peek()] >= prec[token]):
                      postfixList.append(opStack.pop())
                opStack.push(token)

        while not opStack.isEmpty():
            postfixList.append(opStack.pop())
        return " ".join(postfixList)

现在输出是

1 3 + neg 4 * -9.2 0 - 5 8 + * - 4 -2 * -

2020-03-13 更新

在原帖中我放了以下句子:

你希望一元加减运算在字符串的开头或在开头的 '(' 之后。

那里和之前更新中的代码也反映了这一点。我知道这在技术上并不完全正确。在操作之后也可以预期一元操作。但是,我不想允许像2+--+-+3 这样的表达式,所以我排除了操作后一元操作的可能性。不幸的是,它也排除了2^-3 的可能性。如果你想解析2^-3这样的表达式,那么你只需要在另一个操作之后允许一元操作,它需要在else分支中添加一行unary = True

            else:
                while (not opStack.isEmpty()) and \
                   (prec[opStack.peek()] >= prec[token]):
                      postfixList.append(opStack.pop())
                opStack.push(token)
                unary = True   # This is the only new line

现在您可以将2^-3 解析为2^(-3)。但是,它也允许将2+-3 解析为2+(-3)。我总是发现最后一种可能性在计算机语言中非常难看,但如果它对你来说没问题 - 很好。当然,你也可以只在^之后才允许解析一元运算,而在其他运算之后不允许解析。 这将需要检查当前令牌,并且仅当令牌位于允许其后的一元减号的操作列表中时才将 unary 设置为 True。

【讨论】:

  • 我看到您也在处理一元减号作为运算符。固体添加。 +1(另见*.com/questions/17254080/…
  • 如何处理 4*(-2)?你推 4,推 2,然后否定 2 得到 -2,然后相乘。所以这就是为什么它是 4, 2, negate, *.
  • 当然可以处理成4,-2,*。然而,在这个实现中,所有数字操作数都是正数(嗯,非负数)。因此,您不能有单个操作数“-2”。您需要将其解释为+2,然后是否定。无论如何都需要求反运算来处理非数字操作数前面的一元减号,即括号前面。例如,在输出的开头,您会看到 1、3、+、否定。这是处理'-((1+3))'的结果。因为在这种情况下需要进行 neg 操作,所以我决定将所有数字操作数设为正数,并将负数作为数字后跟 neg 处理。
【解决方案2】:

您可以简单地使用 try-except 测试整数或浮点数,这也将处理负数。问题在于,空间分割比实际解析标记要灵活和可靠得多,并且给使用该函数的人带来了巨大的负担。

您需要一个分词器功能。幸运的是,python 有一个分词器模块,尽管第一次进入并不是那么容易。或者你可以自己写。

这是使用该库的快速实现

from io import StringIO
from tokenize import generate_tokens, NUMBER, OP

def tokenizer(s):
    generator = generate_tokens(StringIO(s).readline)
    for toknum, tokval, _, _, _ in generator:
        if toknum in (NUMBER, OP):
            yield tokval        

只需更改您的代码即可使用

for token in tokenizer(infixexpr):

这修复了更长的数字和十进制数字,并在删除所有空格的情况下处理您的测试用例:

print (infixToPostfix("((1+3))*4-(9.2-0)*(5+8)"))
1 3 + 4 * 9.2 0 - 5 8 + * -

(我认为这应该是一个独立的函数,而不是类成员。您可能希望通过取消缩进函数来实现。)

负数需要更多,因为分词器会立即返回“-”作为运算符。您可以编写自己的标记器函数,将 -55 读取为一个标记,或者您可以跟踪状态并意识到如果您不期望一个运算符,则减号必须表示下一个标记是负数.见How to differentiate '-' operator from a negative number for a tokenizer

除了您询问的问题之外,还有一个问题是一元运算符。如果您允许在表达式前使用减号,则必须将其作为运算符处理。亚历克斯在另一个答案中处理了它们,您可以查看Infix to postfix algorithm that takes care of unary operators 一些实现在后缀中将负数打印为“(-5)”。有些人使用空格,虽然如果你没有空格它可以节省空间 - 无论如何它并不是真正的人类可读。

【讨论】:

  • 实际上,您可能希望在您的类中使用分词器的类型输出来帮助识别数字和运算符,而不是像我在这里所做的那样将其隐藏在分词器函数中。
  • 您好,谢谢您的回答。这远远超出了我对 python 的了解范围。我现在会尝试了解它。
最近更新 更多