Esto parece que debería ser bastante trivial, pero soy nuevo en Python y quiero hacerlo de la manera más Pythonic.
Quiero encontrar el índice correspondiente a la n-ésima aparición de una subcadena dentro de una cadena.
Tiene que haber algo equivalente a lo que QUIERO hacer, que es
mystring.find("substring", 2nd)
¿Cómo puedes lograr esto en Python?
Respuestas:
El enfoque iterativo de Mark sería la forma habitual, creo.
Aquí hay una alternativa con la división de cadenas, que a menudo puede ser útil para procesos relacionados con la búsqueda:
Y aquí hay un resumen rápido (y algo sucio, en el que debes elegir un poco de paja que no puede coincidir con la aguja) de una sola línea:
fuente
.rfind('XXX')
, pero eso se desmoronaría si de'XXX'
todos modos aparece más adelante en la entrada.Aquí hay una versión más Pythonic de la sencilla solución iterativa:
Ejemplo:
Si desea encontrar la enésima aparición superpuesta de
needle
, puede incrementar en en1
lugar delen(needle)
, así:Ejemplo:
Es más fácil de leer que la versión de Mark y no requiere la memoria adicional de la versión de división o el módulo de importación de expresiones regulares. También se adhiere a algunas de las reglas del Zen de Python , a diferencia de los diversos
re
enfoques:fuente
Esto encontrará la segunda aparición de subcadena en cadena.
Editar: no he pensado mucho en el rendimiento, pero una recursividad rápida puede ayudar a encontrar la enésima aparición:
fuente
n
ocurrencias de la subcadena. (En este caso, el valor de retorno circulará periódicamente a través de todas las posiciones de ocurrencia).Entendiendo que la expresión regular no siempre es la mejor solución, probablemente usaría una aquí:
fuente
(m.start() for m in re.finditer(r"ab",s))[2]
itertools.islice
función:next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
Estoy ofreciendo algunos resultados de evaluación comparativa que comparan los enfoques más destacados presentados hasta ahora, a saber, @ bobince
findnth()
(basado enstr.split()
) frente a @ tgamblin o @Mark Byers 'find_nth()
(basado enstr.find()
). También compararé con una extensión C (_find_nth.so
) para ver qué tan rápido podemos ir. Aqui estafind_nth.py
:Por supuesto, el rendimiento es más importante si la cadena es grande, así que suponga que queremos encontrar la línea nueva 1000001 ('\ n') en un archivo de 1.3 GB llamado 'bigfile'. Para ahorrar memoria, nos gustaría trabajar en una
mmap.mmap
representación de objeto del archivo:Ya existe el primer problema con
findnth()
, ya que losmmap.mmap
objetos no son compatiblessplit()
. Entonces, en realidad, tenemos que copiar todo el archivo en la memoria:¡Ay! Afortunadamente,
s
todavía cabe en los 4 GB de memoria de mi Macbook Air, así que hagamos una evaluación comparativafindnth()
:Claramente una actuación terrible. Veamos cómo funciona el enfoque basado en
str.find()
:¡Mucho mejor! Claramente,
findnth()
el problema es que se ve obligado a copiar la cadena durantesplit()
, que ya es la segunda vez que copiamos los 1.3 GB de datos despuéss = mm[:]
. Aquí entra en juego la segunda ventaja defind_nth()
: Podemos usarlo enmm
forma directa, de tal manera que cero se requieren copias del archivo:Parece haber una pequeña penalización de rendimiento operando en
mm
vs.s
, pero esto ilustra quefind_nth()
puede darnos una respuesta en 1.2 s en comparación confindnth
el total de 47 s.No encontré casos en los que el
str.find()
enfoque basado fuera significativamente peor que elstr.split()
enfoque basado, por lo que en este punto, diría que la respuesta de @ tgamblin o @Mark Byers debería aceptarse en lugar de la de @ bobince.En mis pruebas, la versión
find_nth()
anterior fue la solución Python pura más rápida que se me ocurrió (muy similar a la versión de @Mark Byers). Veamos cuánto mejor podemos hacer con un módulo de extensión C. Aqui esta_find_nthmodule.c
:Aquí está el
setup.py
archivo:Instale como de costumbre con
python setup.py install
. El código C tiene una ventaja aquí, ya que se limita a encontrar caracteres individuales, pero veamos qué tan rápido es esto:Claramente, aún un poco más rápido. Curiosamente, no hay diferencia en el nivel C entre los casos en memoria y mmapeados. También es interesante ver que
_find_nth2()
, que se basa enstring.h
lamemchr()
función de la biblioteca, pierde frente a la implementación sencilla en_find_nth()
: Las "optimizaciones" adicionales enmemchr()
aparentemente son contraproducentes ...En conclusión, la implementación en
findnth()
(basada enstr.split()
) es realmente una mala idea, ya que (a) funciona terriblemente para cadenas más grandes debido a la copia requerida, y (b) no funciona en losmmap.mmap
objetos en absoluto. La implementación enfind_nth()
(basado enstr.find()
) debe ser preferida en todas las circunstancias (y por lo tanto ser la respuesta aceptada a esta pregunta).Todavía hay bastante margen de mejora, ya que la extensión C se ejecutó casi un factor 4 más rápido que el código Python puro, lo que indica que podría haber un caso para una función de biblioteca dedicada de Python.
fuente
¿La forma más sencilla?
fuente
Probablemente haría algo como esto, usando la función de búsqueda que toma un parámetro de índice:
No es particularmente Pythonic, supongo, pero es simple. Podrías hacerlo usando la recursividad en su lugar:
Es una forma funcional de resolverlo, pero no sé si eso lo hace más Pythonic.
fuente
for _ in xrange(n):
se puede utilizar en lugar dewhile n: ... n-=1
return find_nth(s, x, n - 1, i + 1)
debería serreturn find_nth(s, x, n - 1, i + len(x))
. No es gran cosa, pero ahorra algo de tiempo de cálculo.Esto le dará una matriz de índices iniciales para coincidencias con
yourstring
:Entonces tu enésima entrada sería:
Por supuesto, debes tener cuidado con los límites del índice. Puede obtener el número de instancias de
yourstring
este tipo:fuente
Aquí hay otro enfoque que usa re.finditer.
La diferencia es que esto solo mira hacia el pajar hasta donde sea necesario
fuente
Aquí hay otra versión
re
+itertools
que debería funcionar cuando se busca astr
o aRegexpObject
. Admitiré libremente que es probable que esto esté demasiado diseñado, pero por alguna razón me entretuvo.fuente
Basado en la respuesta de modle13 , pero sin la
re
dependencia del módulo.Me gustaría un poco que este fuera un método de cadena incorporado.
fuente
fuente
Proporcionar otra solución "complicada", que utiliza
split
yjoin
.En su ejemplo, podemos usar
fuente
fuente
find_nth('aaa', 'a', 0)
regresa1
mientras debería regresar0
. Necesitas algo comoi = s.find(substr, i) + 1
y luego regresai - 1
.Solución sin usar bucles y recursividad.
fuente
Reemplazar un forro es excelente, pero solo funciona porque XX y la barra tienen la misma longitud
Una definición buena y general sería:
fuente
Esta es la respuesta que realmente desea:
fuente
Aquí está mi solución para encontrar
n
la aparición deb
en cadenaa
:Es Python puro e iterativo. Para 0 o
n
demasiado grande, devuelve -1. Es de una sola línea y se puede utilizar directamente. Aquí hay un ejemplo:fuente
Para el caso especial en el que busca la enésima aparición de un carácter (es decir, una subcadena de longitud 1), la siguiente función funciona construyendo una lista de todas las posiciones de las ocurrencias del carácter dado:
Si hay menos
n
ocurrencias del carácter dado, daráIndexError: list index out of range
.Esto se deriva de la respuesta de @ Zv_oDD y se simplifica para el caso de un solo carácter.
fuente
Def:
Usar:
Salida:
fuente
Qué tal si:
fuente