【问题标题】:Fastest way to check if a string contains specific characters in any of the items in a list检查字符串是否在列表中的任何项目中包含特定字符的最快方法
【发布时间】:2013-01-02 21:55:36
【问题描述】:

检查字符串是否包含列表中任何项目的某些字符的最快方法是什么?

目前,我正在使用这种方法:

lestring = "Text123"

lelist = ["Text", "foo", "bar"]

for x in lelist:
    if lestring.count(x):
        print 'Yep. "%s" contains characters from "%s" item.' % (lestring, x)

有没有什么方法可以在没有迭代的情况下做到这一点(我想这会使其更快。)?

【问题讨论】:

  • 我认为没有迭代你就做不到。 if x in lestring 也可以工作,而且可能更快,因为它不需要计算它出现了多少次。
  • 几乎任何时候您进行字符串比较或子字符串搜索,您都将至少进行一级迭代。顺便说一句,count() 方法几乎肯定是迭代实现的,所以我们至少要考虑 O(mn),这对于这个问题可能是接近最优的。此外,严格来说,您的解决方案会搜索子字符串(这可能是您的意思),但不会说 lestring 至少包含“T”、“e”、“x”或“t”之一。是我看错了你,还是后一种情况是你真正想要的?
  • 您是否需要知道是否可以在测试字符串中找到列表中的多个字符串?另外,在实际应用中,您是否希望列表中有很多字符串,有很多要测试的字符串,您希望这些字符串大概有多长?我有一个 Python 脚本,它基本上以一种相当幼稚的方式为几十个 MS-Word 和 PDF 文件完成了你所要求的工作,而且只需要几秒钟就可以完成这项工作,所以我没有发现需要进一步优化它.
  • 构建一个包含列出的字符串的正则表达式是一种选择,但只有使用真实的测试数据对不同的实现选项进行计时才能告诉您哪个选项最快。
  • @Volatility:嗯,好点子@jpm:不,如果lestring 完全包含x

标签: python performance list iteration


【解决方案1】:

esmre 库可以解决问题。在您的情况下,您想要更简单的 esm(esmre 的一部分)。

https://pypi.python.org/pypi/esmre/

https://code.google.com/p/esmre/

他们有很好的文档和示例: 取自他们的例子:

>>> import esm
>>> index = esm.Index()
>>> index.enter("he")
>>> index.enter("she")
>>> index.enter("his")
>>> index.enter("hers")
>>> index.fix()
>>> index.query("this here is history")
[((1, 4), 'his'), ((5, 7), 'he'), ((13, 16), 'his')]
>>> index.query("Those are his sheep!")
[((10, 13), 'his'), ((14, 17), 'she'), ((15, 17), 'he')]
>>> 

我进行了一些性能测试:

import random, timeit, string, esm

def uz(lelist, lestring):
    for x in lelist:
        if lestring.count(x):
            return 'Yep. "%s" contains characters from "%s" item.' % (lestring, x)



def ab(lelist, lestring):
    return [e for e in lelist if e in lestring]


def use_esm(index, lestring):
    return index.query(lestring)

for TEXT_LEN in [5, 50, 1000]:
    for SEARCH_LEN in [5, 20]:
        for N in [5, 50, 1000, 10000]:
            if TEXT_LEN < SEARCH_LEN:
                continue

            print 'TEXT_LEN:', TEXT_LEN, 'SEARCH_LEN:', SEARCH_LEN, 'N:', N

            lestring = ''.join((random.choice(string.ascii_uppercase + string.digits) for _ in range(TEXT_LEN)))
            lelist = [''.join((random.choice(string.ascii_uppercase + string.digits) for _ in range(SEARCH_LEN))) for _
                      in range(N)]

            index = esm.Index()
            for i in lelist:
                index.enter(i)
            index.fix()

            t_ab = timeit.Timer("ab(lelist, lestring)", setup="from __main__ import lelist, lestring, ab")
            t_uz = timeit.Timer("uz(lelist, lestring)", setup="from __main__ import lelist, lestring, uz")
            t_esm = timeit.Timer("use_esm(index, lestring)", setup="from __main__ import index, lestring, use_esm")

            ab_time = t_ab.timeit(1000)
            uz_time = t_uz.timeit(1000)
            esm_time = t_esm.timeit(1000)

            min_time = min(ab_time, uz_time, esm_time)
            print '  ab%s: %f' % ('*' if ab_time == min_time else '', ab_time)
            print '  uz%s: %f' % ('*' if uz_time == min_time else '', uz_time)
            print '  esm%s %f:' % ('*' if esm_time == min_time else '', esm_time)

得到的结果主要取决于一个人正在寻找的项目数量(在我的例子中,'N'):

TEXT_LEN: 1000 SEARCH_LEN: 20 N: 5
  ab*: 0.001733
  uz: 0.002512
  esm 0.126853:

TEXT_LEN: 1000 SEARCH_LEN: 20 N: 50
  ab*: 0.017564
  uz: 0.023701
  esm 0.079925:

TEXT_LEN: 1000 SEARCH_LEN: 20 N: 1000
  ab: 0.370371
  uz: 0.489523
  esm* 0.133783:

TEXT_LEN: 1000 SEARCH_LEN: 20 N: 10000
  ab: 3.678790
  uz: 4.883575
  esm* 0.259605:

【讨论】:

  • 从链接中提供相关代码,不要只是发布它们
  • 由于讨论速度很快,您可能应该通过timeit 运行它以验证您的声明。
【解决方案2】:

您可以尝试通过成员资格检查来理解列表

>>> lestring = "Text123"
>>> lelist = ["Text", "foo", "bar"]
>>> [e for e in lelist if e in lestring]
['Text']

与您的实现相比,虽然 LC 有一个隐式循环,但它更快,因为没有像 count 那样的显式函数调用

与 Joe 的实现相比,您的实现要快得多,因为过滤器函数需要在循环中调用两个函数,lambdacount

>>> def joe(lelist, lestring):
    return ''.join(random.sample(x + 'b'*len(x), len(x)))

>>> def uz(lelist, lestring):
    for x in lelist:
        if lestring.count(x):
            return 'Yep. "%s" contains characters from "%s" item.' % (lestring, x)


>>> def ab(lelist, lestring):
    return [e for e in lelist if e in lestring]

>>> t_ab = timeit.Timer("ab(lelist, lestring)", setup="from __main__ import lelist, lestring, ab")
>>> t_uz = timeit.Timer("uz(lelist, lestring)", setup="from __main__ import lelist, lestring, uz")
>>> t_joe = timeit.Timer("joe(lelist, lestring)", setup="from __main__ import lelist, lestring, joe")
>>> t_ab.timeit(100000)
0.09391469893125759
>>> t_uz.timeit(100000)
0.1528471407273173
>>> t_joe.timeit(100000)
1.4272649857800843

Jamie 的注释解决方案对于较短的字符串来说较慢。这是测试结果

>>> def jamie(lelist, lestring):
    return next(itertools.chain((e for e in lelist if e in lestring), (None,))) is not None

>>> t_jamie = timeit.Timer("jamie(lelist, lestring)", setup="from __main__ import lelist, lestring, jamie")
>>> t_jamie.timeit(100000)
0.22237164127909637

如果您需要布尔值,对于较短的字符串,只需修改上面的 LC 表达式

[e in lestring for e in lelist if e in lestring]

或者对于较长的字符串,您可以执行以下操作

>>> next(e in lestring for e in lelist if e in lestring)
True

>>> any(e in lestring for e in lelist)

【讨论】:

  • +1 我想你可以把它变成一个生成器,itertools.chain 它带有 None,并使用 next 获取第一个匹配项,如果有巧合,应该更快:if next(itertools.chain((e for e in lelist if e in lestring), (None,))) is not None : ...
  • @Jaime:我也可以使用any(因为 OP 只想要一个布尔结果),但是对于较短的字符串,即使使用短路,LC 也会比生成器更快。几个月前在 SO 上对此进行了辩论。
  • 如果我只需要一个布尔值怎么办?无论项目在字符串中出现多少次。
  • 感谢您通过timeit 运行它以验证您的声明;因为讨论是速度。不过我很好奇,您认为更长的字符串是什么?
【解决方案3】:

如果测试是否有任何共同的字符(不是单词或段),请从列表中的字母创建一个集合,然后再次检查字符串:

char_list = set(''.join(list_of_words))
test_set = set(string_to_teat)
common_chars = char_list.intersection(test_set)

不过,我假设您只是在寻找一个共同的角色......

【讨论】:

  • 如果干草或针有重复字符怎么办?
  • 您可以通过在输入字符串上使用 split() 并比较整个单词集来执行相同的想法检查单词。但这不适用于子字符串
  • 由于讨论速度很快,您可能应该通过timeit 运行它以验证您的声明。
【解决方案4】:
filter(lambda x: lestring.count(x), lelist)

这会将您尝试查找的所有字符串作为列表返回。

【讨论】:

  • 这比OP的实现要慢。
  • 由于讨论速度很快,您可能应该通过timeit 运行它以验证您的声明。
猜你喜欢
  • 1970-01-01
  • 2012-12-27
  • 2015-03-03
  • 2020-01-19
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 2021-03-12
  • 1970-01-01
相关资源
最近更新 更多