【问题标题】:Occurrences of a pattern in a string and its indices字符串中模式的出现次数及其索引
【发布时间】:2014-01-03 20:03:21
【问题描述】:

如果我有一个模式'haha' 和一个文本'hahahaha',我希望在[0,2,4] 之类的列表中返回模式的索引。 但是,如果我这样做

def find_pat(text, pattern)
    import re
        return[x.start() for x in re.finditer(pattern,text)]

它返回 [0,4],并且无法在索引 2 处看到模式的重复。 我将如何以最 Pythonic 和最有效的方式实现所需的输出?

【问题讨论】:

  • 这里可能有比正则表达式更好的方法,因为您似乎只搜索文字字符串(而不是正则表达式)。

标签: python string python-3.x string-matching


【解决方案1】:
pattern = "haha"
text = "hahahaha"
[i for i in range(len(text)-len(pattern)+1) if text[i:].startswith(pattern)]

iter 将使用它看到的标记,因此它会使用第一个匹配项的前四个字母...

实际上,使用前瞻的解决方案可能是正确的(基于问题而不是用例)但这也可以工作......

【讨论】:

  • 就其价值而言,您的函数比正则表达式查找快得多,而且比我的更正确! :)。你的为a,我的为b,iCodez的正则表达式为ctimeit(a) == 3.385546386501602timeit(b) == 2.024211750664243(但只返回[0,4])、timeit(c) == 4.476999654101547`
【解决方案2】:

使用积极的前瞻断言:

>>> import re
>>> def find_pat(text, pattern):
...     return [x.start() for x in re.finditer("(?={})".format(pattern), text)]
...
>>> find_pat('hahahaha', 'haha')
[0, 2, 4]
>>>

这是reference

【讨论】:

  • 非常感谢!我一定要了解更多关于 re 模块的信息。
【解决方案3】:

正如 Joran Beasley 的回答所示,使用纯字符串函数比使用正则表达式进行静态字符串查找要快得多。

但是如果 N 非常大并且匹配不常见,那么测试 startswith N 次本身可能会大大降低速度。另外,由于您使用的是finditer 而不是findall,我怀疑您可能会担心这种情况。

使用str.find 可以两全其美。当然,最终这与在每个点使用 startswith 所做的工作相同——但它是在 C 中完成的,在没有匹配的情况下进行长距离压缩,速度提高了 20 倍。

另一方面,没有办法将这个重复的find 包装在一个简单的循环表达式中。 (除非您在闭包周围使用iter 构建一个复杂的包装器,但我怀疑这实际上会有所帮助。)因此,代码看起来会比 Joran 的 listcomp 更复杂。当匹配非常普遍时,它可能会更慢(因为在这种情况下,你大部分时间都花在循环中,并且显式循环语句比理解慢)。

从好的方面来说,额外的冗长意味着如何自定义它更明显。例如,如果您决定要跳过重叠匹配,只需使用 i += len(pattern) 而不是 i += 1

def finditer(text, pattern):
    i = 0
    while True:
        i = text.find(pattern, i)
        if i == -1: return
        yield i
        i += 1

来自快速测试(在 64 位 Apple CPython 2.7.5 下):

In [931]: pattern = 'ha'
In [932]: text = 'hahahaha'
In [933]: %timeit [i for i in range(len(text)-len(pattern)+1) if text[i:].startswith(pattern)]
100000 loops, best of 3: 2.69 µs per loop
In [934]: %timeit list(finditer(text, pattern))
100000 loops, best of 3: 3.56 µs per loop
In [935]: text = ('hahahaha' + string.ascii_lowercase + 'ha')*100
pattern = 'ha'
In [936]: %timeit [i for i in range(len(text)-len(pattern)+1) if text[i:].startswith(pattern)]
100000 loops, best of 3: 1.74 ms per loop
In [937]: %timeit list(finditer(text, pattern))
100000 loops, best of 3: 180 µs per loop

因此,即使对于一个非常短的字符串(匹配率为 50%),它也几乎与 Joran 的代码一样快;对于具有 11% 匹配的更长字符串,它已经快了 9.6 倍。如果匹配更不常见,或者如果我们实际上不需要列表,显然它会赢得更大。

【讨论】:

    【解决方案4】:

    我确信有更好的方法来做到这一点。不过,这是我的第一个看法:

    编辑:我误读了这个问题——他试图匹配 [haha]haha、ha[haha]ha 和 haha​​[haha]。我的只匹配 [haha]haha 和 haha​​[haha] 因为我认为他正在寻找独特的匹配。在编程时,阅读理解是一个加分项。

    def find_text(text,pattern):
        indexes = list()
        index = 0
        patlen = len(pattern)
        while index<=len(text)-patlen:
            if text[index:].startswith(pattern):
                indexes.append(index)
                index+=patlen
            else:
                index+=1
        return indexes
    

    【讨论】:

    • 糟糕,这仅匹配独特的模式,例如您的 re.finditer。我的错误,我误解了这个问题! @JoranBeasley 的答案可能是最好的。列表推导比正则表达式快得多。
    猜你喜欢
    • 1970-01-01
    • 2016-11-21
    • 1970-01-01
    • 2012-03-23
    • 2010-09-16
    • 2021-12-16
    • 2018-04-02
    • 1970-01-01
    相关资源
    最近更新 更多