【问题标题】:Finding multiple occurrences of a string within a string in Python [duplicate]在Python中的字符串中查找多次出现的字符串
【发布时间】:2011-04-21 20:42:29
【问题描述】:

如何在 Python 中找到一个字符串中多次出现的字符串?考虑一下:

>>> text = "Allowed Hello Hollow"
>>> text.find("ll")
1
>>> 

所以ll 的第一次出现正如预期的那样是 1。如何找到它的下一次出现?

同样的问题对列表有效。考虑:

>>> x = ['ll', 'ok', 'll']

如何找到所有ll 及其索引?

【问题讨论】:

  • >>> text.count("ll")
  • @blackappy 这会计算出现次数,不会本地化它们

标签: python string


【解决方案1】:

对于第一个版本,检查一个字符串:

def findall(text, sub):
    """Return all indices at which substring occurs in text"""
    return [
        index
        for index in range(len(text) - len(sub) + 1)
        if text[index:].startswith(sub)
    ]

print(findall('Allowed Hello Hollow', 'll'))
# [1, 10, 16]

无需导入re。这应该在线性时间内运行,因为它只在字符串中循环一次(并且在结束前停止,一旦没有足够的字符来容纳子字符串)。我个人也觉得它的可读性很强。

请注意,这会发现重叠的事件:

print(findall('aaa', 'aa'))
# [0, 1]

【讨论】:

    【解决方案2】:

    这段代码可能不是最短/最高效的,但它简单易懂

    def findall(f, s):
        l = []
        i = -1
        while True:
            i = s.find(f, i+1)
            if i == -1:
                return l
            l.append(s.find(f, i))
    
    findall('test', 'test test test test')
    # [0, 5, 10, 15]
    

    【讨论】:

      【解决方案3】:

      以下函数查找一个字符串在另一个字符串中的所有匹配项,同时告知每个匹配项的位置。

      您可以使用下表中的测试用例调用该函数。您可以尝试将单词、空格和数字全部混合。

      该功能适用​​于重叠字符。

      |         theString          | aString |
      | -------------------------- | ------- |
      | "661444444423666455678966" |  "55"   |
      | "661444444423666455678966" |  "44"   |
      | "6123666455678966"         |  "666"  |
      | "66123666455678966"        |  "66"   |
      
      Calling examples:
      1. print("Number of occurrences: ", find_all("123666455556785555966", "5555"))
         
         output:
                 Found in position:  7
                 Found in position:  14
                 Number of occurrences:  2
         
      2. print("Number of occorrences: ", find_all("Allowed Hello Hollow", "ll "))
      
         output:
                Found in position:  1
                Found in position:  10
                Found in position:  16
                Number of occurrences:  3
      
      3. print("Number of occorrences: ", find_all("Aaa bbbcd$#@@abWebbrbbbbrr 123", "bbb"))
      
         output:
               Found in position:  4
               Found in position:  21
               Number of occurrences:  2
               
      
      def find_all(theString, aString):
          count = 0
          i = len(aString)
          x = 0
      
          while x < len(theString) - (i-1): 
              if theString[x:x+i] == aString:        
                  print("Found in position: ", x)
                  x=x+i
                  count=count+1
              else:
                  x=x+1
          return count
      

      【讨论】:

        【解决方案4】:

        我不久前偶然想到了这个主意。使用带有字符串拼接和字符串搜索的 While 循环可以工作,即使对于重叠字符串也是如此。

        findin = "algorithm alma mater alison alternation alpines"
        search = "al"
        inx = 0
        num_str = 0
        
        while True:
            inx = findin.find(search)
            if inx == -1: #breaks before adding 1 to number of string
                break
            inx = inx + 1
            findin = findin[inx:] #to splice the 'unsearched' part of the string
            num_str = num_str + 1 #counts no. of string
        
        if num_str != 0:
            print("There are ",num_str," ",search," in your string.")
        else:
            print("There are no ",search," in your string.")
        

        我是 Python 编程(实际上是任何语言的编程)的业余爱好者,我不确定它可能还有什么其他问题,但我想它工作正常吗?

        如果需要的话,我猜 lower() 也可以在其中的某个地方使用。

        【讨论】:

          【解决方案5】:

          您也可以像这样使用条件列表理解来做到这一点:

          string1= "Allowed Hello Hollow"
          string2= "ll"
          print [num for num in xrange(len(string1)-len(string2)+1) if string1[num:num+len(string2)]==string2]
          # [1, 10, 16]
          

          【讨论】:

            【解决方案6】:

            我认为没有必要测试文本的长度;继续寻找,直到找不到任何东西。像这样:

                >>> text = 'Allowed Hello Hollow'
                >>> place = 0
                >>> while text.find('ll', place) != -1:
                        print('ll found at', text.find('ll', place))
                        place = text.find('ll', place) + 2
            
            
                ll found at 1
                ll found at 10
                ll found at 16
            

            【讨论】:

              【解决方案7】:

              也许不那么 Pythonic,但更不言自明。它返回单词在原始字符串中的位置。

              def retrieve_occurences(sequence, word, result, base_counter):
                   indx = sequence.find(word)
                   if indx == -1:
                       return result
                   result.append(indx + base_counter)
                   base_counter += indx + len(word)
                   return retrieve_occurences(sequence[indx + len(word):], word, result, base_counter)
              

              【讨论】:

                【解决方案8】:

                您可以拆分以获得相对位置,然后将列表中的连续数字相加并同时添加(字符串长度 * 出现顺序)以获得所需的字符串索引。

                >>> key = 'll'
                >>> text = "Allowed Hello Hollow"
                >>> x = [len(i) for i in text.split(key)[:-1]]
                >>> [sum(x[:i+1]) + i*len(key) for i in range(len(x))]
                [1, 10, 16]
                >>> 
                

                【讨论】:

                • 不幸的是,这不会出现重叠。在aaa 中搜索aa 将只返回[0],而不是[0, 1]
                【解决方案9】:

                一个简单的迭代代码,它返回子字符串出现的索引列表。

                        def allindices(string, sub):
                           l=[]
                           i = string.find(sub)
                           while i >= 0:
                              l.append(i)
                              i = string.find(sub, i + 1)
                           return l
                

                【讨论】:

                  【解决方案10】:

                  我想你要找的是string.count

                  "Allowed Hello Hollow".count('ll')
                  >>> 3
                  

                  希望这会有所帮助
                  注意:这只捕获不重叠的事件

                  【讨论】:

                  【解决方案11】:

                  这是我查找多次出现的函数。与这里的其他解决方案不同,它支持切片的可选开始和结束参数,就像str.index

                  def all_substring_indexes(string, substring, start=0, end=None):
                      result = []
                      new_start = start
                      while True:
                          try:
                              index = string.index(substring, new_start, end)
                          except ValueError:
                              return result
                          else:
                              result.append(index)
                              new_start = index + len(substring)
                  

                  【讨论】:

                    【解决方案12】:
                    #!/usr/local/bin python3
                    #-*- coding: utf-8 -*-
                    
                    main_string = input()
                    sub_string = input()
                    
                    count = counter = 0
                    
                    for i in range(len(main_string)):
                        if main_string[i] == sub_string[0]:
                            k = i + 1
                            for j in range(1, len(sub_string)):
                                if k != len(main_string) and main_string[k] == sub_string[j]:
                                    count += 1
                                    k += 1
                            if count == (len(sub_string) - 1):
                                counter += 1
                            count = 0
                    
                    print(counter) 
                    

                    这个程序计算所有子字符串的数量,即使它们在不使用正则表达式的情况下重叠。但这是一个幼稚的实现,为了在最坏的情况下获得更好的结果,建议通过后缀树、KMP 和其他字符串匹配数据结构和算法。

                    【讨论】:

                      【解决方案13】:

                      这个版本的字符串长度应该是线性的,只要序列不是太重复就应该没问题(在这种情况下,您可以用 while 循环替换递归)。

                      def find_all(st, substr, start_pos=0, accum=[]):
                          ix = st.find(substr, start_pos)
                          if ix == -1:
                              return accum
                          return find_all(st, substr, start_pos=ix + 1, accum=accum + [ix])
                      

                      bstpierre 的列表理解对于短序列来说是一个很好的解决方案,但看起来具有二次复杂性,并且从未完成我正在使用的长文本。

                      findall_lc = lambda txt, substr: [n for n in xrange(len(txt))
                                                         if txt.find(substr, n) == n]
                      

                      对于非平凡长度的随机字符串,这两个函数给出相同的结果:

                      import random, string; random.seed(0)
                      s = ''.join([random.choice(string.ascii_lowercase) for _ in range(100000)])
                      
                      >>> find_all(s, 'th') == findall_lc(s, 'th')
                      True
                      >>> findall_lc(s, 'th')[:4]
                      [564, 818, 1872, 2470]
                      

                      但二次版的速度要慢 300 倍左右

                      %timeit find_all(s, 'th')
                      1000 loops, best of 3: 282 µs per loop
                      
                      %timeit findall_lc(s, 'th')    
                      10 loops, best of 3: 92.3 ms per loop
                      

                      【讨论】:

                        【解决方案14】:

                        使用正则表达式,您可以使用re.finditer 查找所有(非重叠)出现:

                        >>> import re
                        >>> text = 'Allowed Hello Hollow'
                        >>> for m in re.finditer('ll', text):
                                 print('ll found', m.start(), m.end())
                        
                        ll found 1 3
                        ll found 10 12
                        ll found 16 18
                        

                        或者,如果你不想要正则表达式的开销,你也可以重复使用str.find来获取next索引:

                        >>> text = 'Allowed Hello Hollow'
                        >>> index = 0
                        >>> while index < len(text):
                                index = text.find('ll', index)
                                if index == -1:
                                    break
                                print('ll found at', index)
                                index += 2 # +2 because len('ll') == 2
                        
                        ll found at  1
                        ll found at  10
                        ll found at  16
                        

                        这也适用于列表和其他序列。

                        【讨论】:

                        • 列表没有find。但它适用于index,你只需要except ValueError 而不是测试-1
                        • 既然您提到了整个index += 2,如果您将其应用于字符串“llll”,它将错过四次出现的“ll”中的两次。最好也坚持使用index += 1 字符串。
                        • 通常我不想寻找重叠的字符串。正则表达式也不会发生这种情况..
                        • 好的,这行得通。但是,如果我们有一个子字符串重叠的字符串,我们将如何找到它们?像 txt = 'abcdcdc' 和 subs = 'cdc' 一样,subs 出现两次,但它会给出答案 1。
                        • @lavee_singh 使用迭代解决方案,无论子字符串的长度如何,索引都只增加一。
                        【解决方案15】:

                        全新的一般编程和在线教程。我也被要求这样做,但只使用我目前学到的方法(基本上是字符串和循环)。不确定这是否在这里增加了任何价值,我知道这不是你会做的,但我让它与这个一起工作:

                        needle = input()
                        haystack = input()
                        counter = 0
                        n=-1
                        for i in range (n+1,len(haystack)+1):
                           for j in range(n+1,len(haystack)+1):
                              n=-1
                              if needle != haystack[i:j]:
                                 n = n+1
                                 continue
                              if needle == haystack[i:j]:
                                 counter = counter + 1
                        print (counter)
                        

                        【讨论】:

                          【解决方案16】:

                          FWIW,这里有几个我认为比 poke's solution 更简洁的非 RE 替代方案。

                          第一个使用str.index 并检查ValueError

                          def findall(sub, string):
                              """
                              >>> text = "Allowed Hello Hollow"
                              >>> tuple(findall('ll', text))
                              (1, 10, 16)
                              """
                              index = 0 - len(sub)
                              try:
                                  while True:
                                      index = string.index(sub, index + len(sub))
                                      yield index
                              except ValueError:
                                  pass
                          

                          第二个测试使用str.find并使用iter检查-1的哨兵:

                          def findall_iter(sub, string):
                              """
                              >>> text = "Allowed Hello Hollow"
                              >>> tuple(findall_iter('ll', text))
                              (1, 10, 16)
                              """
                              def next_index(length):
                                  index = 0 - length
                                  while True:
                                      index = string.find(sub, index + length)
                                      yield index
                              return iter(next_index(len(sub)).next, -1)
                          

                          要将这些函数中的任何一个应用于字符串列表、元组或其他可迭代字符串,您可以使用更高级别的函数——将函数作为一个函数它的论点——比如这个:

                          def findall_each(findall, sub, strings):
                              """
                              >>> texts = ("fail", "dolly the llama", "Hello", "Hollow", "not ok")
                              >>> list(findall_each(findall, 'll', texts))
                              [(), (2, 10), (2,), (2,), ()]
                              >>> texts = ("parallellized", "illegally", "dillydallying", "hillbillies")
                              >>> list(findall_each(findall_iter, 'll', texts))
                              [(4, 7), (1, 6), (2, 7), (2, 6)]
                              """
                              return (tuple(findall(sub, string)) for string in strings)
                          

                          【讨论】:

                          • .next 更改为 .__next__ 以使 findall_iter 在 python 3.7 中工作
                          【解决方案17】:

                          对于列表示例,请使用推导:

                          >>> l = ['ll', 'xx', 'll']
                          >>> print [n for (n, e) in enumerate(l) if e == 'll']
                          [0, 2]
                          

                          字符串也一样:

                          >>> text = "Allowed Hello Hollow"
                          >>> print [n for n in xrange(len(text)) if text.find('ll', n) == n]
                          [1, 10, 16]
                          

                          这将列出“ll”的相邻运行,这可能是也可能不是您想要的:

                          >>> text = 'Alllowed Hello Holllow'
                          >>> print [n for n in xrange(len(text)) if text.find('ll', n) == n]
                          [1, 2, 11, 17, 18]
                          

                          【讨论】:

                          • 哇,我喜欢这个。谢谢你。这是完美的。
                          • 这效率极低。
                          • @Clément 发布一个更有效的示例
                          • @Clément print [n for n in xrange(len(text)) if text[n-1:n] == 'll']
                          • 我的意思是:print [n for n in xrange(len(text)) if text[n:n+2] == 'll']
                          【解决方案18】:

                          对于您的列表示例:

                          In [1]: x = ['ll','ok','ll']
                          
                          In [2]: for idx, value in enumerate(x):
                             ...:     if value == 'll':
                             ...:         print idx, value       
                          0 ll
                          2 ll
                          

                          如果您想要列表中包含“ll”的所有项目,您也可以这样做。

                          In [3]: x = ['Allowed','Hello','World','Hollow']
                          
                          In [4]: for idx, value in enumerate(x):
                             ...:     if 'll' in value:
                             ...:         print idx, value
                             ...:         
                             ...:         
                          0 Allowed
                          1 Hello
                          3 Hollow
                          

                          【讨论】:

                            【解决方案19】:
                            >>> for n,c in enumerate(text):
                            ...   try:
                            ...     if c+text[n+1] == "ll": print n
                            ...   except: pass
                            ...
                            1
                            10
                            16
                            

                            【讨论】:

                              猜你喜欢
                              • 2011-04-21
                              • 1970-01-01
                              • 2017-05-08
                              • 1970-01-01
                              • 2021-03-19
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多