大海捞针,什么是更好的解决方案?

finding needle in haystack, what is a better solution?

所以给定 "needle" 和 "there is a needle in this but not thisneedle haystack"

我写了

def find_needle(n,h):
    count = 0
    words = h.split(" ")
    for word in words:
        if word == n:
            count += 1
    return count

这是 O(n) 但想知道是否有更好的方法?也许根本不用拆分?

您将如何为这种情况编写测试以检查它是否处理所有边缘情况?

这并没有解决复杂性问题,而是简化了代码:

def find_needle(n,h):
    return h.split().count(n)

您可以使用Counter

from collections import Counter

def find_needle(n,h):
    return Counter(h.split())[n]

即:

n = "portugal"
h = 'lobito programmer from portugal hello fromportugal portugal'

print find_needle(n,h)

输出:

2

DEMO

这仍然是 O(n),但它使用了 re 模块和 python 的生成器表达式的强大功能。

import re

def find_needle(n,h):
    g = re.finditer(r'\b%s\b'%n, h)  # use regex word boundaries
    return sum(1 for _ in g)  # return the length of the iterator

对于相对较大的 'haystack'。

使用的内存应该比 .split 少得多

请注意,这与OP中的代码并不完全相同,因为它不仅会找到'needle'而且还会找到'needle,'和'needle.'它不会找到'needles' 不过

我认为用这个 O(n) 是不可能的(因为你需要至少遍历字符串一次)。你可以做一些优化。

我假设你想匹配“whole words”,例如查找 foo 应该像这样匹配:

foo and foo, or foobar and not foo.
^^^     ^^^                    ^^^

所以仅基于 space 的夹板无法完成这项工作,因为:

>>> 'foo and foo, or foobar and not foo.'.split(' ')
['foo', 'and', 'foo,', 'or', 'foobar', 'and', 'not', 'foo.']
#                  ^                                     ^

这就是 re module 派上用场的地方,它可以让您创造迷人的条件。例如,正则表达式中的 \b 表示:

Matches the empty string, but only at the beginning or end of a word. A word is defined as a sequence of Unicode alphanumeric or underscore characters, so the end of a word is indicated by whitespace or a non-alphanumeric, non-underscore Unicode character. Note that formally, \b is defined as the boundary between a \w and a \W character (or vice versa), or between \w and the beginning/end of the string. This means that r'\bfoo\b' matches 'foo', 'foo.', '(foo)', 'bar foo baz' but not 'foobar' or 'foo3'.

因此 r'\bfoo\b' 将仅匹配 整个单词 foo。也不要忘记使用 re.escape():

>>> re.escape('foo.bar+')
'foo\.bar\+'
>>> r'\b{}\b'.format(re.escape('foo.bar+'))
'\bfoo\.bar\+\b'

您现在要做的就是使用re.finditer() 扫描字符串。基于文档:

Return an iterator yielding match objects over all non-overlapping matches for the RE pattern in string. The string is scanned left-to-right, and matches are returned in the order found. Empty matches are included in the result unless they touch the beginning of another match.

我假设匹配项是动态生成的,因此它们永远不必立即存储在内存中(这可能会派上用场 large 字符串,有很多匹配项)。最后数一下:

>>> r = re.compile(r'\bfoo\b')
>>> it = r.finditer('foo and foo, or foobar and not foo.')
>>> sum(1 for _ in it)
3

实际上,当您说 O(n) 时,您忘记了这样一个事实,即在匹配第一个字母之后,您还必须匹配其余的字母(从针到句子匹配 n,然后匹配 e,然后匹配下一个e...) 您本质上是在尝试复制 grep 的功能,因此您可以查看 grep 算法。您可以通过构建有限状态机来做得很好。有很多链接可以帮助您,您可以从 How does grep run so fast?

开始

如果您关心它所花费的时间(不同于时间复杂度),请多处理它。基本上使 n 变小。这是在 2 个进程中 运行 的示例。

from multiprocessing import Process

def find(word, string):
    return string.count(word)

def search_for_words(word, string):
    full_length = len(string)
    part1 = string[:full_length/2]
    proc1 = Process(target=find, args=(word, part1,))
    proc1.start()
    part2 = string[full_lenght/2:]
    proc2 = Process(target=find, args=(word, part2,))
    proc2.start()
    proc1.join()
    proc2.join()

如果你担心它的 O(n) - 那么,我不确定你能做多少,除非有可能在另一个数据结构中获取字符串。比如一套什么的。 (但是把它放在那个集合中也是O(n),如果你已经在其他地方迭代字符串,你可以节省时间,然后再制作这个结构。一次写入,多次读取。

为了保证大海捞针,你需要检查每一片干草,直到找到针。这是 O(n) 无论如何,一个严格的下限。

def find_needle(haystack):
    for item in haystack:
        if item  == 'needle':
            haystack.append(item)
            return 'found the needle at position ' + str(haystack.index(item))

这是我的。

def find_needle(haystack, needle):
    return haystack.count(needele)

这里,我们简单地使用内置的计数方法来计算大海捞针的数量。