使用Python

时间:2015-04-22 12:59:30

标签: python parsing optimization

我的格式为'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

一般来说,字符串的长度不受限制,既不是儿童的数量,也不是长度,也不是嵌套级别的数量。

3 个答案:

答案 0 :(得分:3)

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

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 :(得分:1)

您可以使用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个与pip标记(|)连接的部分,用作逻辑or

  1. (?<=\))\w+(?=\(|$)
  2. 此正则表达式将匹配前面加\w+后跟)(的单词字符($)的任意组合,即$是字符串修饰符的结尾匹配字符串的结尾。

    使用$

    注意适用于案例DDD

    1. ^\w+(?=\()
    2. 此正则表达式将匹配字符串开头出现的任何单词字符组合(修饰符^将匹配字符串的开头),然后是(

答案 2 :(得分:0)

至于有点丑陋,这是在旁观者的眼中。

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

ADDED:我将在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
}