【问题标题】:Efficiently find whether a string contains a group of characters (like substring but ignoring order)?有效查找字符串是否包含一组字符(如子字符串但忽略顺序)?
【发布时间】:2017-03-06 23:00:28
【问题描述】:

在 Python 中查找字符串中是否存在一组字符的最有效方法是什么?

例如,如果我有string="hello world" 和子字符串"roll",则该函数将返回true,因为"roll" 中的所有4 个字母都存在于"hello world" 中。

有明显的蛮力方法,但我想知道是否有一种有效的 Python 特定方法来实现这一点。

编辑: 字母数很重要。例如rollll 不包含在hello world 中(只有三个l)。

【问题讨论】:

  • 明显的蛮力方法set(string).issuperset(set("roll"))
  • 这是为了规范吗? @muru 即使是,也将其发布为答案。比Counter好多了。
  • 你关心字母的数量吗?例如thefunc("hello world", "rollllllll") 应该是真还是假?
  • "hello world" 包含三个l's 所以"rolll" 应该包含在"hello world"
  • @Abhijit 已修复,谢谢

标签: python string algorithm


【解决方案1】:

你可以使用collections.Counter:

from collections import Counter

substring_counts = Counter(substring)
text_counts = Counter(text)

if all(text_counts[letter] >= count for letter, count in substring_counts.items()):
    # All the letters in `substring` are in `count`

【讨论】:

  • 所以最后还是还是 Counter :P :)
【解决方案2】:

对于“包含”检查,我通常会选择集合:

set(string).issuperset(set(substring))
# or
set(string) >= set(substring)

我不确定这里的复杂性,但this page 说集合构造和超集检查都是 O(n),所以这将是 O(n+m),与 Daniel Pryden's method 相当。

正如 Kasramvd 所指出的,使用issuperset 时,您不需要创建一组子字符串:

set(string).issuperset(substring)

不过,使用>= 仍然需要转换。

【讨论】:

  • 子字符串无需转换为set
  • @muru 的集合不限于独特的元素吗?请参阅我的说明:编辑:字母计数很重要。因此,例如 rolll 不包含在 hello world 中(只有两个 l)。
  • @RoeeAdler 我明白你的意思,但你的例子有一个问题:“Hello world”有三个 l; “世界”也有一个。
  • @RoeeAdler 如果字母数很重要,collections.Counter 是要走的路。
【解决方案3】:

构建每个字符串中字符的直方图,然后您可以验证子字符串中的每个字母是否出现在较大的字符串中。运行时是线性的 (O(n + m)),空间与字母的大小成正比。

这是Counting Sort 的一种形式。

注意collections.Counter是直方图数据结构,所以算法大致相同。由于Counter 使用哈希表,它的空间复杂度与实际遇到的项目(字母)的数量成正比,但比鸽笼方法具有更高的常数因子,因此Counter 的效率稍低,但不太可能如此。

【讨论】:

  • Daniel Pryden 的回答效果很好,如果您对同一个字符串运行多个查询,这尤其好。但我认为,在大多数情况下,如果不是计数,而是简单地检查一个布尔值,并保留遇到的字母数量,如果遇到所有字母都中断,那么答案会更快。
  • 确实,如果在一个小得多的初始序列中找到匹配项,但它实际上花费的时间与 n 成正比,那么 O(n+m) 就不是很好。应该有办法解决这个问题,所以它是 O(n'+m) 其中 n' 是产生结果所需的最小前缀的长度。
  • @hassanarafat:也许我理解错了,但你还是需要建立针的直方图,才能有效地在大海捞针中搜索,对吧?如果您有针的直方图,那么您可以通过大海捞针进行 O(n) 传递,并沿途减少直方图中的计数。如果直方图计数为零,则表示成功;如果大海捞针中剩余的字符数少于直方图中剩余的字符数,则说明失败。
  • 我的建议只是一种优化,以防您在字符串末尾找到所有字母,(顺便说一句,使用我的优化意味着必须覆盖默认的直方图行为,或者实现您自己的)这特别有用如果字符串很长但所有字母都在开头。
【解决方案4】:

使用Hashing的概念:

在python中,hashing是使用dict()实现的

hashmap = dict()
string = "hello world"
substring = "roll"
for char in string:
    if char in hashmap:
        hashmap[char] += 1
    else:
        hashmap[char] = 1

flag = 0    
for char in substring:
    if char in hashmap and hashmap[char] >= 1:
        hashmap[char] -= 1
    else:
        flag = 1
        break

if flag == 1:
    print False
else:
    print True

对于字符串中的字符,我们制作了一个哈希图,记录了可用的不同字符及其各自的计数。

接下来我们遍历子字符串并找出是否所有字符都可用。如果可用,我们减少哈希图中该字符的计数并继续前进。如果不存在,则只需将break 输出并打印False..... 如此简单

希望对你有帮助!!!

【讨论】:

  • 这种方法比使用 Counter 快大约 4 倍(对于较长的输入,它们都以每秒约 10 万次调用的速度运行)。
【解决方案5】:

如果你想提高效率,你可以构造一个Counter 来计算string 中的所有字母,并为substring 中的每个字母递减这些计数。如果任何计数低于 0,则 string 中没有足够的字符实例来创建 substring。这是一个 O(string + substring) 算法。

from collections import Counter

def unordered_substring(string, substring):
    string_counter = Counter(string)

    for char in substring:
        string_counter[char] -= 1
        if string_counter[char] < 0:
            return False

    return True

string很长,经常会找到substring的情况下,可以逆向计算substring中的字母数,然后遍历string,当找到所有字符后中断。

def unordered_substring_long(string, substring):
    substring_counter = Counter(substring)
    total_count = sum(substring_counter.values())

    for char in string:
        if substring_counter[char] > 0:
            substring_counter[char] -= 1
            total_count -= 1
            if total_count == 0:
                return True

    return False

【讨论】:

  • (如果您想了解内存,请为 group 中的字符创建计数器,并在字符串中出现时减少它们。)
【解决方案6】:

您可以使用这样的东西,即True 当且仅当sub 中的每个字符都存在于s 中,如OP 所述:

all(sub.count(i) <= s.count(i) for i in set(sub))

如果sub 中的每个不同字符在s 中的数量至少与sub 中一样多,则为True

【讨论】:

    【解决方案7】:

    我认为循环遍历子字符串而不是字符串本身更好。作为一种更 Pythonic 的方法,您可以在 sum 中使用生成器表达式,方法是将主字符串中子字符串字母的出现次数相加。然后将其与子字符串的长度进行比较:

    sum(i in s for i in sub) == len(sub)
    

    如您所见,当您的字符串变大时,它比 counterset 方法快得多:

    In [45]: s = "hello world"
    
    In [46]: sub  = "roll"
    
    In [47]: s *= 1000
    
    In [48]: %timeit set(s).issuperset(sub)
    10000 loops, best of 3: 136 µs per loop
    
    In [53]: %timeit substring_counts = Counter(sub); text_counts = Counter(s); all(text_counts[letter] >= count for letter, count in substring_counts.items())
    100 loops, best of 3: 2.21 ms per loop
    
    In [49]: %timeit sum(i in s for i in sub) == len(sub)
    1000000 loops, best of 3: 739 ns per loop
    

    【讨论】:

      【解决方案8】:

      我想到的第一件事是,您可以轻松地获取原始字符串的单个字符数,然后获取每个子字符串的字符数。然后交叉检查原始字符串的字符数,以查看创建子字符串所需的每种类型的字符数是否存在。这是一种非常简单的方法来判断一个给定的字符串是否是另一个字符串的变位词。这同样适用于您的场景。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-14
        • 2011-11-09
        • 2013-05-18
        • 2015-08-26
        • 2021-12-05
        • 2019-06-29
        • 2015-07-18
        • 2017-01-20
        相关资源
        最近更新 更多