Encontrar múltiples ocurrencias de una cadena dentro de una cadena en Python

81

¿Cómo encuentro múltiples apariciones de una cadena dentro de una cadena en Python? Considera esto:

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

Entonces, la primera aparición de llestá en 1 como se esperaba. ¿Cómo encuentro la siguiente aparición?

La misma pregunta es válida para una lista. Considerar:

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

¿Cómo encuentro todos los llcon sus índices?

user225312
fuente
3
>>> text.count ("ll")
blackappy
3
@blackappy esto cuenta las ocurrencias, no las localiza
pcko1

Respuestas:

122

Usando expresiones regulares, puede usar re.finditerpara encontrar todas las ocurrencias (no superpuestas):

>>> 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

Alternativamente, si no desea la sobrecarga de expresiones regulares, también puede usar repetidamente str.findpara obtener el siguiente índice:

>>> 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

Esto también funciona para listas y otras secuencias.

dar un toque
fuente
1
¿No hay forma de hacerlo sin usar expresiones regulares?
user225312
1
No es que tenga ningún problema, solo curiosidad.
user225312
2
las listas no tienen find. Pero funciona con index, solo necesitas en except ValueErrorlugar de probar para -1
aaronasterling
@Aaron: Me refería a la idea básica, claro que hay que modificarla un poco para las listas (por ejemplo index += 1).
empuje el
4
ahora que lo menciona todo index += 2, si aplica esto a la cadena 'lllll', perderá dos de cada cuatro apariciones de 'll'. Es mejor quedarse con las index += 1cuerdas también.
aaronasterling
31

Creo que lo que estas buscando es string.count

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

Espero que esto ayude
NOTA: esto solo captura ocurrencias que no se superponen

inspectorG4dget
fuente
Wow gracias. esta fue una respuesta de trabajo muy simple
Derrick
Esta es una respuesta a Contar el número de apariciones de una subcadena determinada en una cadena , no a esta pregunta real que pide encontrar los índices de las coincidencias, no su recuento ...
Tomerikoo
26

Para el ejemplo de la lista, use una comprensión:

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

De manera similar para las cadenas:

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

esto mostrará una lista de ejecuciones adyacentes de "ll", que pueden ser o no lo que desea:

>>> text = 'Alllowed Hello Holllow'
>>> print [n for n in xrange(len(text)) if text.find('ll', n) == n]
[1, 2, 11, 17, 18]
bstpierre
fuente
Wow me gusta esto. Gracias. Esto es perfecto.
user225312
5
Esto es extremadamente ineficiente.
Clément
1
@ Clément publica un ejemplo más eficiente
sirvon
@ Clément print [n para n en xrange (len (texto)) si texto [n-1: n] == 'll']
Stephen
Quiero decir: imprime [n para n en xrange (len (texto)) si texto [n: n + 2] == 'll']
Stephen
14

FWIW, aquí hay un par de alternativas que no son de RE que creo que son mejores que la solución de poke .

Los primeros usos str.indexy comprobaciones de 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

La segunda prueba usa str.findy verifica el centinela de -1usando iter:

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)

Para aplicar cualquiera de estas funciones a una lista, tupla u otro iterable de cadenas, puede usar una función de nivel superior, una que tome una función como uno de sus argumentos, como esta:

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)
intuido
fuente
3

Para su ejemplo de lista:

In [1]: x = ['ll','ok','ll']

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

Si quisiera todos los elementos en una lista que contenía "ll", también podría hacerlo.

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
chauncey
fuente
2
>>> for n,c in enumerate(text):
...   try:
...     if c+text[n+1] == "ll": print n
...   except: pass
...
1
10
16
ghostdog74
fuente
1

Nuevo en programación en general y trabajando a través de un tutorial en línea. También se me pidió que hiciera esto, pero solo usando los métodos que había aprendido hasta ahora (básicamente cadenas y bucles). No estoy seguro de si esto agrega algún valor aquí, y sé que no es así como lo harías, pero lo hice funcionar con esto:

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)
Aaron Semeniuk
fuente
1

Esta versión debe tener una longitud lineal de la cadena y debe estar bien siempre que las secuencias no sean demasiado repetitivas (en cuyo caso puede reemplazar la recursividad con un bucle 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])

La lista de comprensión de bstpierre es una buena solución para secuencias cortas, pero parece tener complejidad cuadrática y nunca terminó en un texto largo que estaba usando.

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

Para una cadena aleatoria de longitud no trivial, las dos funciones dan el mismo resultado:

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]

Pero la versión cuadrática es unas 300 veces más lenta

%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
beardc
fuente
0
#!/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) 

Este programa cuenta el número de todas las subcadenas incluso si están superpuestas sin el uso de expresiones regulares. Pero esta es una implementación ingenua y para obtener mejores resultados en el peor de los casos, se recomienda pasar por Suffix Tree, KMP y otras estructuras y algoritmos de datos de coincidencia de cadenas.

pmsh.93
fuente
0

Aquí está mi función para encontrar múltiples ocurrencias. A diferencia de las otras soluciones aquí, admite los parámetros de inicio y finalización opcionales para el corte, al igual que 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)
Elias Zamaria
fuente
0

Un código iterativo simple que devuelve una lista de índices donde ocurre la subcadena.

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

Puede dividir para obtener posiciones relativas, luego sumar números consecutivos en una lista y agregar (longitud de cadena * orden de aparición) al mismo tiempo para obtener los índices de cadena deseados.

>>> 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]
>>> 
Kenly
fuente
0

Quizás no tan Pythonic, pero algo más autoexplicativo. Devuelve la posición de la palabra buscada en la cadena original.

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)
blasrodri
fuente
0

Creo que no es necesario probar la longitud del texto; sigue buscando hasta que no quede nada por encontrar. Me gusta esto:

    >>> 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
rdo
fuente
0

También puede hacerlo con la comprensión de lista condicional como esta:

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]
Stefan Gruenwald
fuente
0

Había tenido esta idea al azar hace un tiempo. El uso de un bucle While con empalme de cadenas y búsqueda de cadenas puede funcionar, incluso para cadenas superpuestas.

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.")

Soy un aficionado a la programación de Python (programación de cualquier lenguaje, en realidad), y no estoy seguro de qué otros problemas podría tener, pero supongo que está funcionando bien.

Supongo que lower () también podría usarse en algún lugar si fuera necesario.

Mystearica Primal Fende
fuente
0

La siguiente función encuentra todas las ocurrencias de una cadena dentro de otra mientras informa la posición donde se encuentra cada ocurrencia.

Puede llamar a la función utilizando los casos de prueba en la siguiente tabla. Puedes probar con palabras, espacios y números mezclados.

La función funciona bien con caracteres superpuestos.

|         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
jhrozo
fuente