Cela semble être assez trivial, mais je suis nouveau à Python et je veux le faire de la manière la plus pythonique.
Je veux trouver la nième occurrence d'une sous-chaîne dans une chaîne.
Il doit y avoir quelque chose d'équivalent à ce que je veux faire qui est
mystring.find("substring", 2nd)
Comment pouvez-vous réaliser cela en Python?
L'approche itérative de Mark serait la manière habituelle, je pense.
Voici une alternative au fractionnement de chaîne, qui peut souvent être utile pour les processus liés à la recherche:
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)
Et voici un rapide (et un peu sale, en ce que vous devez choisir une balle qui ne correspond pas à l'aiguille) one-liner:
'foo bar bar bar'.replace('bar', 'XXX', 1).find('bar')
Voici une version plus Pythonic de la solution itérative simple:
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
Exemple:
>>> find_nth("foofoofoofoo", "foofoo", 2)
6
Si vous voulez trouver la nième occurrence chevauchant de needle
, vous pouvez incrémenter de 1
au lieu de len(needle)
, comme suit:
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
Exemple:
>>> find_nth_overlapping("foofoofoofoo", "foofoo", 2)
3
Ceci est plus facile à lire que la version de Mark et ne nécessite pas de mémoire supplémentaire de la version à fractionner ni à importer un module d’expression régulière. Il adhère également à quelques règles du Zen de python , contrairement aux différentes approches re
:
Ceci trouvera la deuxième occurrence de la sous-chaîne dans la chaîne.
def find_2nd(string, substring):
return string.find(substring, string.find(substring) + 1)
Edit: Je n’ai pas beaucoup réfléchi à la performance, mais une rapide récursivité peut aider à trouver la nième occurrence:
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)
Comprendre que l'expression rationnelle n'est pas toujours la meilleure solution, j'en utiliserais probablement une ici:
>>> 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
J'offre quelques résultats comparatifs comparant les approches les plus en vue présentées jusqu'à présent, à savoir la fonction findnth()
de @ bobince (basée sur str.split()
) par rapport à la fonction find_nth()
de @ tgamblin ou @Mark Byers (basée sur str.find()
). Je vais également comparer avec une extension C (_find_nth.so
) pour voir à quelle vitesse nous pouvons aller. Voici 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
Bien entendu, les performances sont primordiales si la chaîne est volumineuse. Supposons donc que nous voulions trouver le 1000001e retour à la ligne ('\ n') dans un fichier de 1,3 Go appelé 'bigfile'. Pour économiser de la mémoire, nous aimerions travailler sur une représentation d'un objet mmap.mmap
du fichier:
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)
Il y a déjà le premier problème avec findnth()
, puisque les objets mmap.mmap
ne prennent pas en charge split()
. Il faut donc copier tout le fichier en mémoire:
In [4]: %time s = mm[:]
CPU times: user 813 ms, sys: 3.25 s, total: 4.06 s
Wall time: 17.7 s
Aie! Heureusement, s
tient toujours dans les 4 Go de mémoire de mon Macbook Air. Par conséquent, comparons findnth()
:
In [5]: %timeit find_nth.findnth(s, '\n', 1000000)
1 loops, best of 3: 29.9 s per loop
Clairement une performance terrible. Voyons comment l'approche basée sur str.find()
fait:
In [6]: %timeit find_nth.find_nth(s, '\n', 1000000)
1 loops, best of 3: 774 ms per loop
Beaucoup mieux! Clairement, le problème de findnth()
est qu’il est obligé de copier la chaîne pendant split()
, ce qui est déjà la deuxième fois que nous copions les 1,3 Go de données après le s = mm[:]
. Voici le deuxième avantage de find_nth()
: Nous pouvons l’utiliser directement sur mm
, de sorte que zéro des copies du fichier sont nécessaires:
In [7]: %timeit find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 1.21 s per loop
Il semble y avoir une petite pénalité de performance opérant sur mm
contre s
, mais cela montre que find_nth()
peut nous obtenir une réponse en 1,2 s par rapport au total de findnth
de 47 s.
Je n'ai trouvé aucun cas où l'approche basée sur str.find()
était significativement pire que l'approche basée sur str.split()
, donc à ce stade, je dirais que la réponse de @ tgamblin ou @Mark Byers devrait être acceptée à la place de celle de @ bobince.
Lors de mes tests, la version de find_nth()
ci-dessus était la solution Python pure la plus rapide que je pouvais trouver (très similaire à la version de @Mark Byers). Voyons ce que nous pouvons faire de mieux avec un module d’extension C. Voici _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);
}
Voici le fichier setup.py
:
from distutils.core import setup, Extension
module = Extension('_find_nth', sources=['_find_nthmodule.c'])
setup(ext_modules=[module])
Installez comme d'habitude avec python setup.py install
. Le code C joue un avantage ici car il est limité à la recherche de caractères uniques, mais voyons à quelle vitesse cela est:
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
Clairement un peu plus rapide encore. Fait intéressant, il n'y a pas de différence au niveau C entre les cas en mémoire et les cas mappés. Il est également intéressant de voir que _find_nth2()
, qui est basé sur la fonction de bibliothèque memchr()
de string.h
, perd face à la simple implémentation de _find_nth()
: Les «optimisations» supplémentaires dans memchr()
sont apparemment des retours en arrière ...
En conclusion, l'implémentation dans findnth()
(basée sur str.split()
) est vraiment une mauvaise idée, car (a) elle fonctionne terriblement pour les chaînes plus volumineuses en raison de la copie requise, et (b) Elle ne fonctionne pas sur les objets mmap.mmap
à tout. L'implémentation dans find_nth()
(basée sur str.find()
) doit être préférée dans toutes les circonstances (et constitue donc la réponse acceptée à cette question).
Il y a encore pas mal de choses à améliorer car l'extension C a été multipliée par 4 plus rapidement que le code Python pur, ce qui indique qu'il pourrait y avoir un cas pour une fonction de bibliothèque Python dédiée.
Je ferais probablement quelque chose comme ceci, en utilisant la fonction find qui prend un paramètre d'index:
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)
Ce n'est pas particulièrement Pythonic je suppose, mais c'est simple. Vous pouvez le faire en utilisant la récursivité à la place:
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)
C'est une façon fonctionnelle de le résoudre, mais je ne sais pas si cela le rend plus pythonique.
Manière la plus simple?
text = "This is a test from a test ok"
firstTest = text.find('test')
print text.find('test', firstTest + 1)
Voici une autre version re
+ itertools
qui devrait fonctionner lors de la recherche de str
ou de RegexpObject
. J'admettrai volontiers que cela est probablement trop technique, mais pour une raison quelconque, cela m'a diverti.
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
Construire sur la réponse de modle13 , mais sans la dépendance du module re
.
def iter_find(haystack, needle):
return [i for i in range(0, len(haystack)) if haystack[i:].startswith(needle)]
Je souhaite un peu que ce soit une méthode de chaîne intégrée.
>>> iter_find("http://stackoverflow.com/questions/1883980/", '/')
[5, 6, 24, 34, 42]
Cela vous donnera un tableau des index de départ pour les correspondances à yourstring
:
import re
indices = [s.start() for s in re.finditer(':', yourstring)]
Alors votre nième entrée serait:
n = 2
nth_entry = indices[n-1]
Bien sûr, vous devez faire attention aux limites de l'index. Vous pouvez obtenir le nombre d'instances de yourstring
comme ceci:
num_instances = len(indices)
# 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
Voici une autre approche utilisant re.finditer.
La différence est que cela ne regarde que dans la botte de foin dans la mesure nécessaire
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()
>>> 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
Que diriez-vous:
c = os.getcwd().split('\\')
print '\\'.join(c[0:-2])
Solution sans utiliser de boucles et de récursivité.
Utilisez le modèle requis dans la méthode de compilation et entrez l'occurrence souhaitée dans la variable 'n' et la dernière instruction imprimera l'index de départ de la nième occurrence du modèle dans la chaîne donnée. Ici, le résultat de finditer, à savoir itérateur, est converti en liste et accède directement au nième index.
import re
n=2
sampleString="this is history"
pattern=re.compile("is")
matches=pattern.finditer(sampleString)
print(list(matches)[n].span()[0])
C'est la réponse que vous voulez vraiment:
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
Fournir une autre solution "délicate" qui utilise split
et join
.
Dans votre exemple, nous pouvons utiliser
len("substring".join([s for s in ori.split("substring")[:2]]))
Voici ma solution pour trouver n
th occurrence de b
dans la chaîne a
:
from functools import reduce
def findNth(a, b, n):
return reduce(lambda x, y: -1 if y > x + 1 else a.find(b, x + 1), range(n), -1)
C'est pur Python et itératif. Pour 0 ou n
trop grand, il renvoie -1. Il est one-liner et peut être utilisé directement. Voici un exemple:
>>> reduce(lambda x, y: -1 if y > x + 1 else 'bibarbobaobaotang'.find('b', x + 1), range(4), -1)
7
Remplacer un liner est génial mais ne fonctionne que parce que XX et la barre ont la même longueur
Un bon et général def serait:
def findN(s,sub,N,replaceString="XXX"):
return s.replace(sub,replaceString,N-1).find(sub) - (len(replaceString)-len(sub))*(N-1)