separar - reemplazar caracteres en python
Encuentre la enésima aparición de subcadena en una cadena (17)
Aquí hay otra versión de re
+ itertools
que debería funcionar al buscar un str
o un RegexpObject
. Admitiré abiertamente que es probable que esto esté sobrediseñado, pero por alguna razón me entretuvo.
import itertools
import re
def find_nth(haystack, needle, n = 1):
"""
Find the starting index of the nth occurrence of ``needle`` in /
``haystack``.
If ``needle`` is a ``str``, this will perform an exact substring
match; if it is a ``RegexpObject``, this will perform a regex
search.
If ``needle`` doesn''t appear in ``haystack``, return ``-1``. If
``needle`` doesn''t appear in ``haystack`` ``n`` times,
return ``-1``.
Arguments
---------
* ``needle`` the substring (or a ``RegexpObject``) to find
* ``haystack`` is a ``str``
* an ``int`` indicating which occurrence to find; defaults to ``1``
>>> find_nth("foo", "o", 1)
1
>>> find_nth("foo", "o", 2)
2
>>> find_nth("foo", "o", 3)
-1
>>> find_nth("foo", "b")
-1
>>> import re
>>> either_o = re.compile("[oO]")
>>> find_nth("foo", either_o, 1)
1
>>> find_nth("FOO", either_o, 1)
1
"""
if (hasattr(needle, ''finditer'')):
matches = needle.finditer(haystack)
else:
matches = re.finditer(re.escape(needle), haystack)
start_here = itertools.dropwhile(lambda x: x[0] < n, enumerate(matches, 1))
try:
return next(start_here)[1].start()
except StopIteration:
return -1
Parece que debería ser bastante trivial, pero soy nuevo en Python y quiero hacerlo de la manera más pitonica.
Quiero encontrar la enésima aparición de una subcadena en una cadena.
Tiene que haber algo equivalente a lo que QUIERO hacer, que es
mystring.find("substring", 2nd)
¿Cómo se puede lograr esto en Python?
Aquí hay otro enfoque usando re.finditer.
La diferencia es que esto solo mira hacia el pajar tanto como sea necesario
from re import finditer
from itertools import dropwhile
needle=''an''
haystack=''bananabanana''
n=2
next(dropwhile(lambda x: x[0]<n, enumerate(re.finditer(needle,haystack))))[1].start()
Aquí hay una versión más Pythonic de la solución iterativa directa:
def find_nth(haystack, needle, n):
start = haystack.find(needle)
while start >= 0 and n > 1:
start = haystack.find(needle, start+len(needle))
n -= 1
return start
Ejemplo:
>>> find_nth("foofoofoofoo", "foofoo", 2)
6
Si desea encontrar la enésima coincidencia de needle
, puede aumentar en 1
lugar de len(needle)
, de esta manera:
def find_nth_overlapping(haystack, needle, n):
start = haystack.find(needle)
while start >= 0 and n > 1:
start = haystack.find(needle, start+1)
n -= 1
return start
Ejemplo:
>>> find_nth_overlapping("foofoofoofoo", "foofoo", 2)
3
Esto 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 la importación de módulo de expresión regular. También se adhiere a algunas de las reglas en el Zen de python , a diferencia de los diversos enfoques de re
:
- Simple es mejor que complejo.
- Flat es mejor que anidado.
- La legibilidad cuenta
Basándose en la respuesta de modle13 , pero sin la dependencia del módulo re
.
def iter_find(haystack, needle):
return [i for i in range(0, len(haystack)) if haystack[i:].startswith(needle)]
Me gustaría que este fuera un método de cuerda incorporado.
>>> iter_find("http://.com/questions/1883980/", ''/'')
[5, 6, 24, 34, 42]
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 búsquedas:
def findnth(haystack, needle, n):
parts= haystack.split(needle, n+1)
if len(parts)<=n+1:
return -1
return len(haystack)-len(parts[-1])-len(needle)
Y aquí hay una línea rápida (y algo sucia, en la que tienes que elegir una paja que no puede coincidir con la aguja):
''foo bar bar bar''.replace(''bar'', ''XXX'', 1).find(''bar'')
El recambio de un trazador de líneas es genial, pero solo funciona porque XX y barra tienen el mismo lentgh
Una definición buena y general sería:
def findN(s,sub,N,replaceString="XXX"):
return s.replace(sub,replaceString,N-1).find(sub) - (len(replaceString)-len(sub))*(N-1)
Entender que la expresión regular no siempre es la mejor solución, probablemente usaría una aquí:
>>> import re
>>> s = "ababdfegtduab"
>>> [m.start() for m in re.finditer(r"ab",s)]
[0, 2, 11]
>>> [m.start() for m in re.finditer(r"ab",s)][2] #index 2 is third occurrence
11
Esta es la respuesta que realmente quieres:
def Find(String,ToFind,Occurence = 1):
index = 0
count = 0
while index <= len(String):
try:
if String[index:index + len(ToFind)] == ToFind:
count += 1
if count == Occurence:
return index
break
index += 1
except IndexError:
return False
break
return False
Esto encontrará la segunda aparición de subcadena en cadena.
def find_2nd(string, substring):
return string.find(substring, string.find(substring) + 1)
Editar: No he pensado mucho sobre el rendimiento, pero una recursión rápida puede ayudar a encontrar la enésima ocurrencia:
def find_nth(string, substring, n):
if (n == 1):
return string.find(substring)
else:
return string.find(substring, find_nth(string, substring, n - 1) + 1)
Esto le dará una matriz de los índices iniciales para las coincidencias con su yourstring
:
import re
indices = [s.start() for s in re.finditer('':'', yourstring)]
Entonces tu enésima entrada sería:
n = 2
nth_entry = indices[n-1]
Por supuesto, debes tener cuidado con los límites del índice. Puede obtener el número de instancias de yourstring
esta manera:
num_instances = len(indices)
Estoy ofreciendo algunos resultados comparativos que comparan los enfoques más destacados presentados hasta ahora, es decir, findnth()
de findnth()
(basado en str.split()
) vs. @ tgamblin o @Mark Byers '' find_nth()
(basado en str.find()
). También compararé con una extensión C ( _find_nth.so
) para ver qué tan rápido podemos avanzar. Aquí está find_nth.py
:
def findnth(haystack, needle, n):
parts= haystack.split(needle, n+1)
if len(parts)<=n+1:
return -1
return len(haystack)-len(parts[-1])-len(needle)
def find_nth(s, x, n=0, overlap=False):
l = 1 if overlap else len(x)
i = -l
for c in xrange(n + 1):
i = s.find(x, i + l)
if i < 0:
break
return i
Por supuesto, el rendimiento es más importante si la cadena es grande, así que supongamos que queremos encontrar la 1000001st nueva línea (''/ n'') en un archivo de 1.3 GB llamado ''bigfile''. Para ahorrar memoria, nos gustaría trabajar en una representación de objeto mmap.mmap
del archivo:
In [1]: import _find_nth, find_nth, mmap
In [2]: f = open(''bigfile'', ''r'')
In [3]: mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
Ya existe el primer problema con findnth()
, ya que los objetos mmap.mmap
no admiten split()
. Entonces, tenemos que copiar todo el archivo en la memoria:
In [4]: %time s = mm[:]
CPU times: user 813 ms, sys: 3.25 s, total: 4.06 s
Wall time: 17.7 s
¡Ay! Afortunadamente todavía cabe en los 4 GB de memoria de mi Macbook Air, así que vamos a comparar findnth()
:
In [5]: %timeit find_nth.findnth(s, ''/n'', 1000000)
1 loops, best of 3: 29.9 s per loop
Claramente una actuación terrible. Veamos cómo funciona el enfoque basado en str.find()
:
In [6]: %timeit find_nth.find_nth(s, ''/n'', 1000000)
1 loops, best of 3: 774 ms per loop
¡Mucho mejor! Claramente, el problema de findnth()
es que está forzado a copiar la cadena durante la split()
, que ya es la segunda vez que copiamos los 1.3 GB de datos alrededor después de s = mm[:]
. Aquí viene la segunda ventaja de find_nth()
: podemos usarlo en mm
directamente, de modo que se requieren cero copias del archivo:
In [7]: %timeit find_nth.find_nth(mm, ''/n'', 1000000)
1 loops, best of 3: 1.21 s per loop
Parece que hay una pequeña penalización de rendimiento que opera en mm
vs. s
, pero esto ilustra que find_nth()
puede obtener una respuesta en 1.2 s en comparación con el total de 47 s de findnth
.
No encontré ningún caso en el que el enfoque basado en str.find()
fuera significativamente peor que el enfoque basado en str.split()
, por lo que en este punto, argumentaría que se debería aceptar la respuesta de @tgamblin o @Mark Byers en lugar de @ bobince .
En mis pruebas, la versión de find_nth()
anterior fue la solución de Python pura más rápida que pude encontrar (muy similar a la versión de @Mark Byers). Veamos cuánto mejor podemos hacer con un módulo de extensión C. Aquí está _find_nthmodule.c
:
#include <Python.h>
#include <string.h>
off_t _find_nth(const char *buf, size_t l, char c, int n) {
off_t i;
for (i = 0; i < l; ++i) {
if (buf[i] == c && n-- == 0) {
return i;
}
}
return -1;
}
off_t _find_nth2(const char *buf, size_t l, char c, int n) {
const char *b = buf - 1;
do {
b = memchr(b + 1, c, l);
if (!b) return -1;
} while (n--);
return b - buf;
}
/* mmap_object is private in mmapmodule.c - replicate beginning here */
typedef struct {
PyObject_HEAD
char *data;
size_t size;
} mmap_object;
typedef struct {
const char *s;
size_t l;
char c;
int n;
} params;
int parse_args(PyObject *args, params *P) {
PyObject *obj;
const char *x;
if (!PyArg_ParseTuple(args, "Osi", &obj, &x, &P->n)) {
return 1;
}
PyTypeObject *type = Py_TYPE(obj);
if (type == &PyString_Type) {
P->s = PyString_AS_STRING(obj);
P->l = PyString_GET_SIZE(obj);
} else if (!strcmp(type->tp_name, "mmap.mmap")) {
mmap_object *m_obj = (mmap_object*) obj;
P->s = m_obj->data;
P->l = m_obj->size;
} else {
PyErr_SetString(PyExc_TypeError, "Cannot obtain char * from argument 0");
return 1;
}
P->c = x[0];
return 0;
}
static PyObject* py_find_nth(PyObject *self, PyObject *args) {
params P;
if (!parse_args(args, &P)) {
return Py_BuildValue("i", _find_nth(P.s, P.l, P.c, P.n));
} else {
return NULL;
}
}
static PyObject* py_find_nth2(PyObject *self, PyObject *args) {
params P;
if (!parse_args(args, &P)) {
return Py_BuildValue("i", _find_nth2(P.s, P.l, P.c, P.n));
} else {
return NULL;
}
}
static PyMethodDef methods[] = {
{"find_nth", py_find_nth, METH_VARARGS, ""},
{"find_nth2", py_find_nth2, METH_VARARGS, ""},
{0}
};
PyMODINIT_FUNC init_find_nth(void) {
Py_InitModule("_find_nth", methods);
}
Aquí está el archivo setup.py
:
from distutils.core import setup, Extension
module = Extension(''_find_nth'', sources=[''_find_nthmodule.c''])
setup(ext_modules=[module])
Instalar como de costumbre con python setup.py install
. El código C juega aquí una ventaja, ya que está limitado a encontrar caracteres individuales, pero veamos qué tan rápido es esto:
In [8]: %timeit _find_nth.find_nth(mm, ''/n'', 1000000)
1 loops, best of 3: 218 ms per loop
In [9]: %timeit _find_nth.find_nth(s, ''/n'', 1000000)
1 loops, best of 3: 216 ms per loop
In [10]: %timeit _find_nth.find_nth2(mm, ''/n'', 1000000)
1 loops, best of 3: 307 ms per loop
In [11]: %timeit _find_nth.find_nth2(s, ''/n'', 1000000)
1 loops, best of 3: 304 ms per loop
Claramente, un poco más rápido aún. Curiosamente, no hay diferencia en el nivel C entre los casos in-memory y mmapped. También es interesante ver que _find_nth2()
, que se basa en la función de biblioteca memchr()
string.h
, pierde frente a la implementación directa en _find_nth()
: las "optimizaciones" adicionales en memchr()
aparentemente están fallando. ..
En conclusión, la implementación en findnth()
(basada en str.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 mmap.mmap
objetos en absoluto. La implementación en find_nth()
(basada en str.find()
) debe preferirse en todas las circunstancias (y, por lo tanto, debe ser la respuesta aceptada a esta pregunta).
Todavía hay bastante margen de mejora, ya que la extensión C corrió casi un factor de 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 de Python dedicada.
La forma más simple?
text = "This is a test from a test ok"
firstTest = text.find(''test'')
print text.find(''test'', firstTest + 1)
Probablemente haga algo como esto, usando la función de búsqueda que toma un parámetro de índice:
def find_nth(s, x, n):
i = -1
for _ in range(n):
i = s.find(x, i + len(x))
if i == -1:
break
return i
print find_nth(''bananabanana'', ''an'', 3)
No es particularmente Pythonic, supongo, pero es simple. Puedes hacerlo usando la recursión en su lugar:
def find_nth(s, x, n, i = 0):
i = s.find(x, i)
if n == 1 or i == -1:
return i
else:
return find_nth(s, x, n - 1, i + len(x))
print find_nth(''bananabanana'', ''an'', 3)
Es una forma funcional de resolverlo, pero no sé si eso lo hace más pitónico.
Proporcionando otra solución "engañosa", que usa split
y join
.
En tu ejemplo, podemos usar
len("substring".join([s for s in ori.split("substring")[:2]]))
Qué tal si:
c = os.getcwd().split(''//')
print ''//'.join(c[0:-2])
# return -1 if nth substr (0-indexed) d.n.e, else return index
def find_nth(s, substr, n):
i = 0
while n >= 0:
n -= 1
i = s.find(substr, i + 1)
return i
>>> s="abcdefabcdefababcdef"
>>> j=0
>>> for n,i in enumerate(s):
... if s[n:n+2] =="ab":
... print n,i
... j=j+1
... if j==2: print "2nd occurence at index position: ",n
...
0 a
6 a
2nd occurence at index position: 6
12 a
14 a