【问题标题】:Optimizing string parsing with Python使用 Python 优化字符串解析
【发布时间】:2015-04-22 12:59:30
【问题描述】:

我有'AB(AB(DDC)C)A(BAAC)DAB(ABC)'形式的字符串。

  • 每个字符代表一个元素(ABCD)。
  • 在括号之间,右侧是每个元素的子元素(可能不存在)。

例如,拥有'AB(AB(DDC)C)A(BAAC)DA',顶层将是AB(AB(DDC)C)A(BAAC)DA --> [A, B, A, D, A] 和相应的子级将是 [None, AB(DDC)C, BAAC, None, None]。子项也将被递归解析。

我在这里实现了一个解决方案:

def parse_string(string):

    i = 0                                                                       
    parsed = []                                                                 

    while i < len(string):                                                      
        if string[i] in ('A', 'B', 'C', 'D'):                                        
            parsed.append([string[i], None])                                    
            i += 1                                                              
        elif string[i] == '(':                                                  
            open_brakets = 1                                                    
            i += 1                                                              
            j = i                                                               
            while open_brakets:                                                 
                if string[j] == '(':                                            
                    open_brakets += 1                                           
                elif string[j] == ')':                   
                    open_brakets -= 1                    
                j += 1
            # Parse the children as well
            parsed[-1][-1] = parse_string(string[i:j - 1])       
            i = j                                                               
        else:                                                                   
            i += 1                                                              

    return parsed

print parse_string('AB(AB(DDC)C)A(BAAC)DAB(ABC)') 

虽然我觉得有点丑,而且肯定效率不高。

我想知道是否有一种方法可以用 Python 以更清洁/更快/更优雅的方式实现这一点?允许使用外部库(特别是如果它们是用 C 编写的!:-P)。

更新

应该工作的其他字符串示例:

  • ABC(DAB(ACB)BBB(AAA)ABC)DCB

一般来说,字符串的长度是没有限制的,既没有孩子的数量,也没有他们的长度,也没有嵌套层级的数量。

【问题讨论】:

标签: python parsing optimization


【解决方案1】:

如果还需要递归解析内括号:

def parse_tree(tree, string, start=0):
    index = start
    while index < len(string):
        current = string[index]
        if current == "(":
            child = tree[-1][1]
            child_parsed = parse_tree(child, string, index+1)
            index += child_parsed + 2 # adds 2 for the parentheses
        elif current == ")":
            break
        else:
            tree.append((current, []))
            index += 1
    return index - start
tree = []
print(parse_tree(tree, 'abc(abc(defg)d)de(f)gh'))

可以将其工作方式视为状态机。状态机接受节点定义,直到它看到一个开放的括号,在其中它将一个新的上下文(即递归函数调用)推送到解析堆栈以解析括号的内容。解析内部上下文时,右括号弹出上下文。

如果您有更复杂的语法,可以更好地扩展的另一种选择是使用像 PyParsing 这样的解析库:

from pyparsing import OneOrMore, Optional, oneOf, alphas, Word, Group, Forward, Suppress, Dict

# define the grammar
nodes = Forward()
nodeName = oneOf(list(alphas))
nodeChildren = Suppress('(') + Group(nodes) + Suppress( ')')
node = Group(nodeName + Optional(nodeChildren))
nodes <<= OneOrMore(node)

print(nodes.parseString('abc(abc(defg)d)de(f)gh'))

PyParsing 等解析库允许您定义易于阅读的声明性语法。

对原始非递归解析的回答:一种方法是使用 itertools(累积仅来自 Python 3.2 及更高版本,itertools 文档有 pure python implementation of accumulate 用于旧版本) .这避免了使用索引:

from itertools import takewhile, accumulate
PARENS_MAP = {'(': 1, ')': -1}
def parse_tree(tree, string):
    string = iter(string)
    while string:
        current = next(string)
        if current == "(":
            child = iter(string)
            child = ((c, PARENS_MAP.get(c, 0)) for c in child)
            child = accumulate(child, lambda a,b: (b[0], a[1]+b[1]))
            child = takewhile(lambda c: c[1] >= 0, child)
            child = (c[0] for c in child)
            tree[-1][1] = "".join(child)
        else:
            tree.append([current, None])
print(parse_tree('abc(abc(defg)d)de(f)gh'))

我不太确定它是否更快或更优雅,但我认为使用显式索引更容易编写、理解和修改。

【讨论】:

  • +1,很好的 pyparsing 示例!嵌套表达式太常见了,pyparsing 还包含了一个辅助方法nestedExpr,默认的嵌套分隔符是左右括号。使用nestedExpr,你可以写成print nestedExpr().parseString('('+s+')').asList()[0]——因为原始字符串本身不是一个嵌套表达式,我们必须将它包裹在括号中,然后在解析后,取第0个元素。给['abc', ['abc', ['defg'], 'd'], 'de', ['f'], 'gh']。但这只是为了显示 pyparsing 附带了哪些类型的内置函数 - 您的原始答案更具说明性。
【解决方案2】:

您可以使用regex 来解析您的文本。

作为更通用的字符串,请考虑以下字符串:

>>> s ='AB(AB(DDC)C)A(BAAC)DAB(ABC)DDD'

您可以使用re.findall 查找外部模式:

>>> re.findall(r'(?<=\))\w+(?=\(|$)|^\w+(?=\()',s)
['AB', 'A', 'DAB', 'DDD']

并使用带有re.split 的正则表达式来获取括号内绑定的字符串:

>>> re.split(r'(?<=\))\w+(?=\(|$)|^\w+(?=\()',s)
['', '(AB(DDC)C)', '(BAAC)', '(ABC)', '']

简单解释一下前面的正则表达式

此正则表达式包含 2 部分,它们与用作逻辑 or 的 pip 令牌 (|) 连接:

  1. (?&lt;=\))\w+(?=\(|$)

此正则表达式将匹配前面为 ) 和后面是 ($ 的单词字符 (\w+) 的任意组合,其中 $ 是匹配字符串结尾的字符串结尾修饰符。

注意使用$是针对DDD的情况!

  1. ^\w+(?=\()

此正则表达式将匹配出现在字符串开头的任何单词字符组合(修饰符^ 将匹配字符串的开头),然后是(

【讨论】:

  • 感谢您的提示。我会检查这是否最终会更快! :-) 不过,它肯定更优雅。
  • @Peque 欢迎您!如果发现此答案有帮助,您可以通过投票或接受答案告诉社区;)
  • 我会在接受之前等待。需要尝试一下,以防其他人想发布自己的答案。 ;-) 但别担心,不会超过 24-48 小时! :-P(你已经得到了我的支持)
  • @Peque 是的 ;)
  • 这无法解析此输入“ABC(GHI(MNO)PQR(STU)JKL)DEF”。不幸的是,我认为正则表达式不能用于解析这种输入;可能包含任意嵌套括号的语言没有正则语法,因此不能用正则表达式解析。
【解决方案3】:

至于有点丑,那是旁观者的看法。

就速度而言,很难改进您的代码。


添加:这是我在 C++ 中的做法。如果你愿意,你可以适应 Python。 这显示了如何使用递归来做到这一点。 顶层函数是topLevel("blah blah")

bool seeLetter(char* &s){
    if (*s=='A' || *s=='B' || *s=='C' || *s=='D'){
         s++;
         return true;
    } else {
         return false;
    }
}

bool seeChar(char* &s, char c){
    if (*s == c){s++; return true;}
    return false;
}

bool seeList(char* &s){
    while(*s){
        if (seeLetter(s)){
        } else if (seeChar(s, '(')){
            if (!seeList(s)) return false;
            if (!seeChar(s, ')')) return false;
        } else break;
    }
    return true;
}

bool topLevel(char* &s){
    if (!seeList(s)) return false;
    return (*s == '\0'); // check for garbage at end
}

【讨论】:

  • 感谢您抽出宝贵时间撰写此答案。 :-) 是的,在 C 中迭代字符串(使用指针)比在 Python 中更优雅,但正如问题中所述,我正在寻找的是 Python 中更好或更有效的实现,而不是 C/C++ . ;-)
猜你喜欢
  • 2015-03-31
  • 2021-07-26
  • 2017-07-10
  • 1970-01-01
  • 2011-07-14
  • 1970-01-01
  • 1970-01-01
  • 2017-07-10
  • 2016-02-02
相关资源
最近更新 更多