Dies scheint ziemlich trivial zu sein, aber ich bin neu bei Python und möchte es auf die pythonischste Art und Weise tun.
Ich möchte den Index finden, der dem n-ten Vorkommen eines Teilstrings innerhalb eines Strings entspricht.
Es muss etwas geben, das dem entspricht, was ich tun möchte, nämlich
mystring.find("substring", 2nd)
Wie können Sie dies in Python erreichen?
Antworten:
Marks iterativer Ansatz wäre der übliche Weg, denke ich.
Hier ist eine Alternative mit String-Aufteilung, die häufig nützlich sein kann, um verwandte Prozesse zu finden:
Und hier ist ein kurzer (und etwas schmutziger, da Sie eine Spreu auswählen müssen, die nicht zur Nadel passt) Einzeiler:
quelle
.rfind('XXX')
, aber das würde auseinanderfallen, wenn es'XXX'
später in der Eingabe erscheint.Hier ist eine pythonischere Version der einfachen iterativen Lösung:
Beispiel:
Wenn Sie die n - te finden wollen überlappende Vorkommen
needle
, können Sie erhöhen , indem1
stattlen(needle)
, wie folgt aus :Beispiel:
Dies ist einfacher zu lesen als Marks Version und erfordert weder den zusätzlichen Speicher der Teilungsversion noch das Importieren eines Moduls für reguläre Ausdrücke. Im Gegensatz zu den verschiedenen Ansätzen werden auch einige Regeln im Zen of Python
re
eingehalten:quelle
Dies findet das zweite Vorkommen von Teilzeichenfolgen in Zeichenfolgen.
Bearbeiten: Ich habe nicht viel über die Leistung nachgedacht, aber eine schnelle Rekursion kann helfen, das n-te Vorkommen zu finden:
quelle
n
der Teilstring vorkommt. (In diesem Fall durchläuft der Rückgabewert regelmäßig alle Vorkommenspositionen.)Da Regex nicht immer die beste Lösung ist, würde ich hier wahrscheinlich eine verwenden:
quelle
(m.start() for m in re.finditer(r"ab",s))[2]
itertools.islice
Funktion möglich:next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
Ich biete einige Benchmarking-Ergebnisse an, in denen die wichtigsten bisher vorgestellten Ansätze verglichen werden, nämlich @ bobince's
findnth()
(basierend aufstr.split()
) mit @ tgamblin's oder @Mark Byersfind_nth()
(basierend aufstr.find()
). Ich werde auch mit einer C-Erweiterung (_find_nth.so
) vergleichen, um zu sehen, wie schnell wir gehen können. Hier istfind_nth.py
:Natürlich ist die Leistung am wichtigsten, wenn die Zeichenfolge groß ist. Nehmen wir also an, wir möchten die 1000001. erste Zeile ('\ n') in einer 1,3-GB-Datei namens 'bigfile' finden. Um Speicherplatz zu sparen, möchten wir an einer
mmap.mmap
Objektdarstellung der Datei arbeiten:Es gibt bereits das erste Problem mit
findnth()
, dammap.mmap
Objekte nicht unterstützensplit()
. Wir müssen also tatsächlich die gesamte Datei in den Speicher kopieren:Autsch! Zum Glück
s
passt immer noch in die 4 GB Speicher meines Macbook Air. Lassen Sie uns also einen Benchmark erstellenfindnth()
:Offensichtlich eine schreckliche Leistung. Mal sehen, wie der darauf basierende Ansatz
str.find()
funktioniert:Viel besser!
findnth()
Das Problem ist natürlich, dass es gezwungen ist, die Zeichenfolge während zu kopieren. Diessplit()
ist bereits das zweite Mal, dass wir die 1,3 GB Daten danach kopierens = mm[:]
. Hier kommt der zweite Vorteil vonfind_nth()
: Wir können esmm
direkt verwenden, so dass keine Kopien der Datei erforderlich sind:Es scheint eine kleine Leistungseinbuße Betriebs auf sein
mm
gegenübers
, aber dies zeigt , dassfind_nth()
uns eine Antwort in 1,2 s im Vergleich zu bekommenfindnth
‚s insgesamt 47 s.Ich fand keine Fälle, in denen der
str.find()
basierte Ansatz signifikant schlechter war als derstr.split()
basierte Ansatz, daher würde ich an dieser Stelle argumentieren, dass die Antwort von @ tgamblin oder @ Mark Byers anstelle der von @ bobince akzeptiert werden sollte.In meinen Tests war die
find_nth()
obige Version die schnellste reine Python-Lösung, die ich finden konnte (sehr ähnlich der Version von @Mark Byers). Mal sehen, wie viel besser wir mit einem C-Erweiterungsmodul arbeiten können. Hier ist_find_nthmodule.c
:Hier ist die
setup.py
Datei:Installieren Sie wie gewohnt mit
python setup.py install
. Der C-Code spielt hier eine Rolle, da er sich darauf beschränkt, einzelne Zeichen zu finden. Mal sehen, wie schnell dies geht:Klar noch ein bisschen schneller. Interessanterweise gibt es auf C-Ebene keinen Unterschied zwischen In-Memory- und Mmapped-Fällen. Es ist auch interessant zu sehen, dass das
_find_nth2()
, was aufstring.h
dermemchr()
Bibliotheksfunktion basiert , gegen die unkomplizierte Implementierung in verliert_find_nth()
: Die zusätzlichen "Optimierungen" inmemchr()
sind anscheinend nach hinten los ...Zusammenfassend ist die Implementierung in
findnth()
(basierend aufstr.split()
) wirklich eine schlechte Idee, da (a) sie aufgrund des erforderlichen Kopierens für größere Zeichenfolgen eine schreckliche Leistung erbringt und (b) überhaupt nicht fürmmap.mmap
Objekte funktioniert . Die Implementierung infind_nth()
(basierend aufstr.find()
) sollte unter allen Umständen bevorzugt werden (und daher die akzeptierte Antwort auf diese Frage sein).Es gibt noch viel Raum für Verbesserungen, da die C-Erweiterung fast um den Faktor 4 schneller lief als der reine Python-Code, was darauf hinweist, dass möglicherweise eine dedizierte Python-Bibliotheksfunktion vorliegt.
quelle
Einfachster Weg?
quelle
Ich würde wahrscheinlich so etwas mit der Suchfunktion machen, die einen Indexparameter akzeptiert:
Es ist nicht besonders pythonisch, aber es ist einfach. Sie können dies stattdessen mit Rekursion tun:
Es ist ein funktionaler Weg, um es zu lösen, aber ich weiß nicht, ob es dadurch pythonischer wird.
quelle
for _ in xrange(n):
kann anstelle vonwhile n: ... n-=1
return find_nth(s, x, n - 1, i + 1)
sollte seinreturn find_nth(s, x, n - 1, i + len(x))
. Keine große Sache, spart aber Rechenzeit.Dadurch erhalten Sie eine Reihe von Startindizes für Übereinstimmungen mit
yourstring
:Dann wäre Ihr n-ter Eintrag:
Natürlich muss man mit den Indexgrenzen vorsichtig sein. Sie können die Anzahl der Instanzen
yourstring
wie folgt ermitteln:quelle
Hier ist ein anderer Ansatz mit re.finditer.
Der Unterschied besteht darin, dass dies nur so weit wie nötig in den Heuhaufen schaut
quelle
Hier ist eine weitere
re
+itertools
Version, die bei der Suche nach astr
oder a funktionieren sollteRegexpObject
. Ich werde frei zugeben, dass dies wahrscheinlich überarbeitet ist, aber aus irgendeinem Grund hat es mich unterhalten.quelle
Aufbauend auf der Antwort von modle13 , jedoch ohne die Modulabhängigkeit
re
.Ich wünschte, dies wäre eine eingebaute String-Methode.
quelle
quelle
Bereitstellung einer weiteren "kniffligen" Lösung, die
split
und verwendetjoin
.In Ihrem Beispiel können wir verwenden
quelle
quelle
find_nth('aaa', 'a', 0)
kehrt zurück,1
während es zurückkehren sollte0
. Sie brauchen so etwasi = s.find(substr, i) + 1
und kehren dann zurücki - 1
.Lösung ohne Schleifen und Rekursion.
quelle
Der Ersatz-Liner ist großartig, funktioniert aber nur, weil XX und Bar die gleiche Länge haben
Ein guter und allgemeiner Def wäre:
quelle
Dies ist die Antwort, die Sie wirklich wollen:
quelle
Hier ist meine Lösung, um das
n
Vorkommen vonb
in string zu findena
:Es ist reines Python und iterativ. Für 0 oder
n
zu groß wird -1 zurückgegeben. Es ist einzeilig und kann direkt verwendet werden. Hier ist ein Beispiel:quelle
Für den Sonderfall, in dem Sie nach dem n-ten Vorkommen eines Zeichens suchen (dh Teilzeichenfolge der Länge 1), erstellt die folgende Funktion eine Liste aller Vorkommenspositionen des angegebenen Zeichens:
Wenn es weniger als
n
Vorkommen des angegebenen Zeichens gibt, gibt esIndexError: list index out of range
.Dies wird aus der Antwort von @ Zv_oDD abgeleitet und für den Fall eines einzelnen Zeichens vereinfacht.
quelle
Def:
Benutzen:
Ausgabe:
quelle
Wie wäre es mit:
quelle