Wydaje się, że powinno to być dość trywialne, ale jestem nowy w Pythonie i chcę to zrobić jak najbardziej w Pythonie.
Chcę znaleźć indeks odpowiadający n-temu wystąpieniu podciągu w ciągu.
Musi być coś równoważnego temu, co CHCĘ zrobić, czyli
mystring.find("substring", 2nd)
Jak możesz to osiągnąć w Pythonie?
Odpowiedzi:
Myślę, że iteracyjne podejście Marka byłoby typowym sposobem.
Oto alternatywa z dzieleniem ciągów, która często może być przydatna do znajdowania procesów powiązanych:
A oto szybki (i nieco brudny, ponieważ musisz wybrać plewy, które nie pasują do igły):
źródło
.rfind('XXX')
, ale to by się rozpadło, gdyby i tak'XXX'
pojawiło się później na wejściu.Oto bardziej Pythonic wersja prostego rozwiązania iteracyjnego:
Przykład:
Jeśli chcesz znaleźć n-te nakładające się wystąpienie
needle
, możesz zwiększyć o1
zamiastlen(needle)
, na przykład:Przykład:
Jest to łatwiejsze do odczytania niż wersja Marka i nie wymaga dodatkowej pamięci wersji dzielącej lub importowania modułu wyrażeń regularnych. W przeciwieństwie do różnych podejść, przestrzega również kilku zasad Zen Pythona
re
:źródło
To znajdzie drugie wystąpienie podciągu w ciągu.
Edycja: Nie myślałem dużo o wydajności, ale szybka rekurencja może pomóc w znalezieniu n-tego wystąpienia:
źródło
n
wystąpień podciągu. (W tym przypadku wartość zwracana będzie cyklicznie przechodzić przez wszystkie pozycje występowania).Rozumiejąc, że regex nie zawsze jest najlepszym rozwiązaniem, prawdopodobnie użyłbym jednego tutaj:
źródło
(m.start() for m in re.finditer(r"ab",s))[2]
itertools.islice
funkcji:next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
Przedstawiam wyniki testów porównawczych, porównujące najbardziej znaczące podejścia zaprezentowane do tej pory, a mianowicie @ bobince's
findnth()
(na podstawiestr.split()
) vs. @ tgamblin's lub @Mark Byers 'find_nth()
(na podstawiestr.find()
). Porównam również z rozszerzeniem C (_find_nth.so
), aby zobaczyć, jak szybko możemy jechać. Otofind_nth.py
:Oczywiście wydajność ma największe znaczenie, jeśli łańcuch jest duży, więc przypuśćmy, że chcemy znaleźć 1000001. znak nowej linii („\ n”) w pliku o nazwie „bigfile” o wielkości 1,3 GB. Aby zaoszczędzić pamięć, chcielibyśmy popracować nad
mmap.mmap
reprezentacją obiektową pliku:Jest już pierwszy problem
findnth()
, ponieważmmap.mmap
obiekty nie obsługująsplit()
. Więc właściwie musimy skopiować cały plik do pamięci:Auć! Na szczęście
s
nadal mieści się w 4 GB pamięci mojego Macbooka Air, więc zróbmy benchmarkfindnth()
:Najwyraźniej okropny występ. Zobaczmy, jak działa podejście oparte na
str.find()
:Dużo lepiej! Najwyraźniej
findnth()
problem polega na tym, że jest on zmuszony do skopiowania ciągu w trakciesplit()
, co jest już drugim razem, gdy kopiowaliśmy 1,3 GB danych dookołas = mm[:]
. Tu pojawia się druga zaletafind_nth()
: Możemy go używaćmm
bezpośrednio, tak że nie są wymagane żadne kopie pliku:Wydaje się, że działanie na
mm
vs. jest niewielki spadek wydajnościs
, ale to pokazuje, żefind_nth()
możemy uzyskać odpowiedź w 1,2 sekundy w porównaniu dofindnth
47 sekund.Nie znalazłem przypadków, w których
str.find()
podejście oparte było znacznie gorsze niżstr.split()
podejście oparte, więc w tym miejscu argumentowałbym, że odpowiedź @ tgamblin lub @Mark Byers powinna zostać zaakceptowana zamiast @ bobince.W moich testach
find_nth()
powyższa wersja była najszybszym czystym rozwiązaniem Pythona, jakie mogłem wymyślić (bardzo podobnym do wersji @Mark Byers). Zobaczmy, o ile lepiej możemy zrobić z modułem rozszerzającym C. Oto_find_nthmodule.c
:Oto
setup.py
plik:Zainstaluj jak zwykle z
python setup.py install
. Kod C odgrywa tutaj przewagę, ponieważ ogranicza się do znajdowania pojedynczych znaków, ale zobaczmy, jak szybko to jest:Najwyraźniej jeszcze trochę szybciej. Co ciekawe, na poziomie C nie ma różnicy między obudowami in-memory i mmapped. Warto również zauważyć, że
_find_nth2()
, który opiera się nastring.h
„smemchr()
funkcja biblioteki, traci się przeciwko zwykłej realizacji w_find_nth()
: dodatkowy«optymalizacje»wmemchr()
widocznie mści ...Podsumowując, implementacja w
findnth()
(oparta nastr.split()
) jest naprawdę złym pomysłem, ponieważ (a) działa strasznie w przypadku większych ciągów z powodu wymaganego kopiowania i (b) w ogóle nie działa nammap.mmap
obiektach. Implementacja wfind_nth()
(oparta nastr.find()
) powinna być preferowana we wszystkich okolicznościach (i dlatego powinna być akceptowaną odpowiedzią na to pytanie).Wciąż jest sporo miejsca na ulepszenia, ponieważ rozszerzenie C działało prawie 4 razy szybciej niż czysty kod Pythona, co wskazuje, że może istnieć argument za dedykowaną funkcją biblioteczną Pythona.
źródło
Najprostszy sposób?
źródło
Prawdopodobnie zrobiłbym coś takiego, używając funkcji find, która przyjmuje parametr indeksu:
Wydaje mi się, że nie jest to specjalnie Pythonic, ale jest proste. Zamiast tego możesz to zrobić używając rekurencji:
To funkcjonalny sposób na rozwiązanie tego problemu, ale nie wiem, czy to czyni go bardziej Pythonowym.
źródło
for _ in xrange(n):
można użyć zamiastwhile n: ... n-=1
return find_nth(s, x, n - 1, i + 1)
powinno byćreturn find_nth(s, x, n - 1, i + len(x))
. Nie jest to wielka sprawa, ale oszczędza trochę czasu obliczeń.To da ci tablicę indeksów początkowych dla dopasowań do
yourstring
:Wtedy twój n-ty wpis wyglądałby tak:
Oczywiście musisz uważać na granice indeksu. Możesz uzyskać liczbę takich wystąpień
yourstring
:źródło
Oto inne podejście wykorzystujące re.finditer.
Różnica polega na tym, że zagląda to do stogu siana tylko wtedy, gdy jest to konieczne
źródło
Oto kolejna wersja
re
+,itertools
która powinna działać podczas wyszukiwania astr
lub aRegexpObject
. Przyznam, że jest to prawdopodobnie przesadzone, ale z jakiegoś powodu bawiło mnie to.źródło
Opierając się na odpowiedzi modle13 , ale bez
re
zależności od modułu.Chciałbym, żeby to była wbudowana metoda ciągów.
źródło
źródło
Dostarczenie innego „podstępnego” rozwiązania, które wykorzystuje
split
ijoin
.W Twoim przykładzie możemy użyć
źródło
źródło
find_nth('aaa', 'a', 0)
zwraca,1
podczas gdy powinien powrócić0
. Potrzebujesz czegoś takiego,i = s.find(substr, i) + 1
a potem wróći - 1
.Rozwiązanie bez używania pętli i rekurencji.
źródło
W szczególnym przypadku, w którym szukasz n-tego wystąpienia znaku (tj. Podłańcuch o długości 1), następująca funkcja działa poprzez zbudowanie listy wszystkich pozycji wystąpień danego znaku:
Jeśli będzie mniej niż
n
wystąpień danej postaci, to daIndexError: list index out of range
.To pochodzi od @ Zv_oDD za odpowiedź i uproszczone dla przypadku pojedynczego znaku.
źródło
Wymiana jednej wkładki jest świetna, ale działa tylko dlatego, że XX i kierownica mają tę samą długość
Dobra i ogólna def to:
źródło
Oto odpowiedź, której naprawdę chcesz:
źródło
Oto moje rozwiązanie do znalezienia
n
wystąpieniab
w łańcuchua
:Jest to czysty Python i iteracyjny. W przypadku 0 lub
n
zbyt dużej wartości zwraca -1. Jest jednoliniowy i można go używać bezpośrednio. Oto przykład:źródło
Def:
Używać:
Wynik:
źródło
Co powiesz na:
źródło