递归拆分包含一组已定义前缀的字符串 - Python

Posted

技术标签:

【中文标题】递归拆分包含一组已定义前缀的字符串 - Python【英文标题】:Recursively split strings that contains a defined set of prefixes - Python 【发布时间】:2013-01-11 13:19:41 【问题描述】:

如果我有一个可以附加到字符串的前缀列表,我如何将一个字符串拆分为它的前缀和下一个子字符串中的其他字符。例如:

prefixes = ['over','under','re','un','co']

str1 = "overachieve"
output: ["over","achieve"]

str2 = "reundo"
output = ["re","un","do"]

是否有更好的方法来完成上述任务,可能使用正则表达式或一些字符串函数,而不是:

str1 = "reundo"
output = []

for x in [p for p in prefixes if p in str1]:
    output.append(x)    
    str1 =  str1.replace(x,"",1)
output.append(str1)

【问题讨论】:

【参考方案1】:

正则表达式是搜索许多替代前缀的有效方法:

import re

def split_prefixes(word, prefixes):
    regex = re.compile('|'.join(sorted(prefixes, key=len, reverse=True)))
    result = []
    i = 0
    while True:
        mo = regex.match(word, i)
        if mo is None:
            result.append(word[i:])
            return result
        result.append(mo.group())
        i = mo.end()


>>> prefixes = ['over', 'under', 're', 'un', 'co']
>>> for word in ['overachieve', 'reundo', 'empire', 'coprocessor']:
        print word, '-->', split_prefixes(word, prefixes)

overachieve --> ['over', 'achieve']
reundo --> ['re', 'un', 'do']
empire --> ['empire']
coprocessor --> ['co', 'processor']

【讨论】:

+1 表示match(word, i),我从来没有注意到match 也有posendpos 但是underachieve有问题,我最终得到["un","under","achieve"] =) 嗯,它对我有用。 split_prefixes('underachieve', ['over', 'under', 're', 'un', 'co']) --> ['under', 'achieve']【参考方案2】:
prefixes = ['over','under','re','un','co']

def test(string, prefixes, existing=None):
    prefixes.sort(key = lambda s: len(s))
    prefixes.reverse() # This and the previous line ensure that longer prefixes are searched first regardless of initial sorting.
    if existing is None:
        existing = [] # deals with the fact that placing [] as a default parameter and modifying it modifies it for the entire session
    for prefix in prefixes:
        if string.startswith(prefix):
            existing.append(prefix)
            return test(string[len(prefix):], prefixes, existing)
    existing.append(string)
    return existing

这段代码递归地遍历一个字符串,删除已知的前缀直到它用完,然后返回整个列表。在较长的字符串上,生成器可能是更好的路线,但在较短的字符串上,不需要生成器的额外开销可能会使其成为更好的解决方案。

【讨论】:

您能确认“生成器的额外开销”吗?在这种情况下,我会更关心递归的额外开销。 (小记) 我一半是指添加将生成器输出转换为列表(而不是可迭代)的要求,还有生成器的(可能感知到的)心理开销。很多与我一起工作和学习的人都认为它们基本上是魔法。 我鼓励大家熟悉生成器,有一般的(甚至已经泄露到 C# 中)。也就是说,如果你坚持要返回一个列表,重写生成器的最简单方法是创建一个result = [] 并将yield foo 替换为result.append(foo)。 Python 不折叠尾递归,但你可以:)。我不建议避免使用生成器;请注意 Python 3 是多么愉快地在任何地方使用可迭代对象。这只是一个很好的概念。 我一直推迟实际使用比[foo(x) for x in bar] 更简单的生成器。我认为这可能是开始更加努力的背后推动力。【参考方案3】:

我会使用str.startswith 方法

for p in prefixes:
    if str1.startswith(p):
        output.append(p)
        str1 = str1.replace(p, '', 1)
output.append(str1)

你的代码最大的缺陷是像'found'这样的字符串会输出['un', 'fod']

但是,如果您有一个假设字符串 'reuncoundo',那么您将需要多次遍历该列表。

while True:
    if not any(str1.startswith(i) for i in prefixes):
        output.append(str1)
        break
    for p in prefixes:
        if str1.startswith(p):
            output.append(p)
            str1 = str1.replace(p, '', 1)

这会输出['re', 'un', 'co', 'un', 'do']

【讨论】:

假设输入"reuncoundo" 怎么样?你的代码肯定会错过第二个"un" 吗? @Drakekin 刚刚解决了这个问题【参考方案4】:

考虑到“两个问题”的谚语,我仍然会说这是正则表达式的工作。正则表达式编译成状态机,并行检查所有可能的变体,而不是一个一个地检查。

这是一个利用它的实现:

import re

def split_string(string, prefixes):
    regex = re.compile('|'.join(map(re.escape, prefixes))) # (1)
    while True:
        match = regex.match(string)
        if not match:
            break
        end = match.end()
        yield string[:end]
        string = string[end:]
    if string:
        yield string # (2)

prefixes = ['over','under','re','un','co']
assert (list(split_string('recouncoundo',prefixes))
        == ['re','co','un','co','un','do'])

注意(1)中的正则表达式是如何构造的:

前缀使用re.escape 转义,这样特殊字符就不会干扰 转义前缀使用|(或)正则表达式运算符连接 整个东西都被编译了。

第 (2) 行产生最后一个单词,如果在拆分前缀后有剩余的话。您可能需要删除 if string 检查是否希望函数在前缀剥离后没有任何内容时返回空字符串。

还要注意re.match(与re.search相反)仅在输入字符串的开头查找模式,因此无需将^附加到正则表达式。

【讨论】:

Python 正则表达式(与大多数其他当前正则表达式引擎一样)确实按顺序检查所有可能的变体,而不是并行检查。因此,变体的顺序很重要。例如,(re|reun) 会将reundo 拆分为reundo,而不是reundo,因为DFA 正则表达式引擎(最左最长规则)会这样做。 我的意思是正则表达式会首先检查第一个字母,并在发现r 后继续检查rereun,但不是overunder。因此,我希望它比重复的 for p in prefix 风格的解决方案工作得更快。【参考方案5】:

如果你在处理前缀,你不需要正则表达式,你只需要startswith()。你当然可以使用正则表达式,但它更难阅读和维护,即使是这样一个简单的。在我看来,startswith() 更简单。

对于这样一个简单的问题,其他答案似乎太复杂了。我建议使用这样的递归函数:

def split_prefixes (word, prefixes):
    split = [p for p in prefixes if word.startswith(p)]
    if split:
        return split + split_prefixes (word[len(split[0]):], prefixes)
    else:
        return [word]

这是结果:

"overachieve" -> ['over', 'achieve']
"reundo" -> ['re', 'un', 'do']
"reuncoundo" -> ['re', 'un', 'co', 'un', 'do']
"empire" -> ['empire']

【讨论】:

以上是关于递归拆分包含一组已定义前缀的字符串 - Python的主要内容,如果未能解决你的问题,请参考以下文章

递归提升拆分并附加到集合

递归算法

递归,动态规划实现

我可以将自定义令牌规则应用于由 spaCy 中的前缀拆分的令牌吗?

Vertica:将字符串拆分为数组并将其分组以获取一组唯一值?

GO 数组