Jak znaleźć wszystkie wystąpienia podciągów?

365

Python ma string.find()i string.rfind()pobiera indeks podłańcucha w ciągu.

Zastanawiam się, czy istnieje coś takiego, string.find_all()co może zwrócić wszystkie znalezione indeksy (nie tylko pierwszy od początku, czy pierwszy od końca).

Na przykład:

string = "test test test test"

print string.find('test') # 0
print string.rfind('test') # 15

#this is the goal
print string.find_all('test') # [0,5,10,15]
nukl
źródło
11
co powinien 'ttt'.find_all('tt')zwrócić
Santiago Alessandri
2
powinien zwrócić „0”. Oczywiście, w idealnym świecie musi być także i to 'ttt'.rfind_all('tt'), co powinno zwrócić „1”
nukl
2
Wygląda na to duplikat tej stackoverflow.com/questions/3873361/...
nu Everest

Odpowiedzi:

523

Nie ma prostej wbudowanej funkcji łańcucha, która robi to, czego szukasz, ale możesz użyć bardziej wydajnych wyrażeń regularnych :

import re
[m.start() for m in re.finditer('test', 'test test test test')]
#[0, 5, 10, 15]

Jeśli chcesz znaleźć nakładające się mecze, lookahead zrobi to:

[m.start() for m in re.finditer('(?=tt)', 'ttt')]
#[0, 1]

Jeśli chcesz znaleźć wszystko wstecz bez nakładania się, możesz połączyć pozytywne i negatywne spojrzenie w przyszłość w takie wyrażenie:

search = 'tt'
[m.start() for m in re.finditer('(?=%s)(?!.{1,%d}%s)' % (search, len(search)-1, search), 'ttt')]
#[1]

re.finditerzwraca generator , więc możesz zmienić []powyższe, aby ()uzyskać generator zamiast listy, co będzie bardziej wydajne, jeśli będziesz powtarzał wyniki tylko raz.

moinudin
źródło
cześć, w związku z tym [m.start() for m in re.finditer('test', 'test test test test')], jak możemy szukać testlub text? Czy staje się to znacznie bardziej skomplikowane?
Xpanta
7
Ogólnie chcesz przejrzeć wyrażenia regularne: docs.python.org/2/howto/regex.html . Rozwiązaniem Twojego pytania będzie: [m.start () for m in re.finditer ('te [sx] t', 'test tekstowy test tekstowy')]
Yotam Vaknin
1
Jaka będzie złożoność czasowa korzystania z tej metody?
Pranjal Mittal
1
@PranjalMittal. Górna czy dolna granica? Najlepszy, najgorszy czy przeciętny przypadek?
Szalony fizyk,
@marcog co jeśli podciąg zawiera nawiasy lub inne znaki specjalne?
Bananach,
109
>>> help(str.find)
Help on method_descriptor:

find(...)
    S.find(sub [,start [,end]]) -> int

W ten sposób możemy zbudować go sami:

def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1: return
        yield start
        start += len(sub) # use start += 1 to find overlapping matches

list(find_all('spam spam spam spam', 'spam')) # [0, 5, 10, 15]

Nie są wymagane tymczasowe łańcuchy ani wyrażenia regularne.

Karl Knechtel
źródło
22
Aby uzyskać nakładających mecze, powinno wystarczyć, aby wymienić start += len(sub)z start += 1.
Karl Knechtel
4
Uważam, że twój poprzedni komentarz powinien być postscriptum w twojej odpowiedzi.
tzot
1
Twój kod nie działa w celu znalezienia substr: „ATAT” w „GATATATGCATATACTT”
Ashish Negi
2
Zobacz dodatkowo komentarz, który napisałem. To jest przykład nakładającego się dopasowania.
Karl Knechtel
4
Aby dopasować zachowanie re.findall, polecam dodanie len(sub) or 1zamiast len(sub), w przeciwnym razie ten generator nigdy nie zakończy działania na pustym podciągu.
WGH
45

Oto (bardzo nieefektywny) sposób uzyskania wszystkich (tzn. Nawet nakładających się) dopasowań:

>>> string = "test test test test"
>>> [i for i in range(len(string)) if string.startswith('test', i)]
[0, 5, 10, 15]
thkala
źródło
25

Znowu stary wątek, ale oto moje rozwiązanie z wykorzystaniem generatora i zwykłego str.find.

def findall(p, s):
    '''Yields all the positions of
    the pattern p in the string s.'''
    i = s.find(p)
    while i != -1:
        yield i
        i = s.find(p, i+1)

Przykład

x = 'banananassantana'
[(i, x[i:i+2]) for i in findall('na', x)]

zwroty

[(2, 'na'), (4, 'na'), (6, 'na'), (14, 'na')]
AkiRoss
źródło
3
to wygląda pięknie!
fabio.sang
21

Możesz użyć re.finditer()do nie nakładających się meczów.

>>> import re
>>> aString = 'this is a string where the substring "is" is repeated several times'
>>> print [(a.start(), a.end()) for a in list(re.finditer('is', aString))]
[(2, 4), (5, 7), (38, 40), (42, 44)]

ale nie będzie działać dla:

In [1]: aString="ababa"

In [2]: print [(a.start(), a.end()) for a in list(re.finditer('aba', aString))]
Output: [(0, 3)]
Chinmay Kanchi
źródło
12
Po co tworzyć listę z iteratora, to po prostu spowalnia proces.
pradyunsg
2
aString VS astring;)
NexD.
18

Chodź, połączmy się ponownie.

def locations_of_substring(string, substring):
    """Return a list of locations of a substring."""

    substring_length = len(substring)    
    def recurse(locations_found, start):
        location = string.find(substring, start)
        if location != -1:
            return recurse(locations_found + [location], location+substring_length)
        else:
            return locations_found

    return recurse([], 0)

print(locations_of_substring('this is a test for finding this and this', 'this'))
# prints [0, 27, 36]

W ten sposób nie potrzeba wyrażeń regularnych.

Cody Piersall
źródło
Właśnie zacząłem się zastanawiać: „czy istnieje fantazyjny sposób zlokalizowania podłańcucha w łańcuchu znaków w pythonie” ... a potem po 5 minutach googlingu znalazłem twój kod. Dzięki za udostępnienie !!!
Geparada,
3
Ten kod ma kilka problemów. Ponieważ prędzej czy później działa na danych otwartych, wpadniesz na RecursionErrornie, jeśli wystąpi wystarczająco dużo zdarzeń. Kolejną są dwie listy „wyrzucanych”, które tworzy na każdej iteracji tylko w celu dodania jednego elementu, co jest bardzo nieoptymalne dla funkcji wyszukiwania ciągów, które prawdopodobnie można nazwać wiele razy. Chociaż czasem funkcje rekurencyjne wydają się eleganckie i przejrzyste, należy o nie podchodzić ostrożnie.
Ivan Nikolaev
11

Jeśli szukasz tylko jednej postaci, to zadziała:

string = "dooobiedoobiedoobie"
match = 'o'
reduce(lambda count, char: count + 1 if char == match else count, string, 0)
# produces 7

Również,

string = "test test test test"
match = "test"
len(string.split(match)) - 1
# produces 4

Mam przeczucie, że żadne z nich (szczególnie # 2) nie jest strasznie wydajne.

jstaab
źródło
rozwiązanie gr8 .. Jestem pod wrażeniem użycia .. split ()
shantanu pathak
9

to jest stary wątek, ale zainteresowałem się i chciałem podzielić się moim rozwiązaniem.

def find_all(a_string, sub):
    result = []
    k = 0
    while k < len(a_string):
        k = a_string.find(sub, k)
        if k == -1:
            return result
        else:
            result.append(k)
            k += 1 #change to k += len(sub) to not search overlapping results
    return result

Powinien zwrócić listę pozycji, w których znaleziono podciąg. Skomentuj, jeśli zobaczysz błąd lub miejsce na ulepszenie.

Turyny
źródło
6

To załatwia sprawę za pomocą re.finditer

import re

text = 'This is sample text to test if this pythonic '\
       'program can serve as an indexing platform for '\
       'finding words in a paragraph. It can give '\
       'values as to where the word is located with the '\
       'different examples as stated'

#  find all occurances of the word 'as' in the above text

find_the_word = re.finditer('as', text)

for match in find_the_word:
    print('start {}, end {}, search string \'{}\''.
          format(match.start(), match.end(), match.group()))
Bruno Vermeulen
źródło
5

Ten wątek jest trochę stary, ale działał dla mnie:

numberString = "onetwothreefourfivesixseveneightninefiveten"
testString = "five"

marker = 0
while marker < len(numberString):
    try:
        print(numberString.index("five",marker))
        marker = numberString.index("five", marker) + 1
    except ValueError:
        print("String not found")
        marker = len(numberString)
Andrew H
źródło
5

Możesz spróbować :

>>> string = "test test test test"
>>> for index,value in enumerate(string):
    if string[index:index+(len("test"))] == "test":
        print index

0
5
10
15
Harsha Biyani
źródło
2

Niezależnie od rozwiązań dostarczonych przez innych, są one całkowicie oparte na dostępnej metodzie find () lub dowolnych dostępnych metodach.

Jaki jest podstawowy podstawowy algorytm znajdowania wszystkich wystąpień podłańcucha w ciągu?

def find_all(string,substring):
    """
    Function: Returning all the index of substring in a string
    Arguments: String and the search string
    Return:Returning a list
    """
    length = len(substring)
    c=0
    indexes = []
    while c < len(string):
        if string[c:c+length] == substring:
            indexes.append(c)
        c=c+1
    return indexes

Możesz także odziedziczyć klasę str do nowej klasy i możesz użyć tej funkcji poniżej.

class newstr(str):
def find_all(string,substring):
    """
    Function: Returning all the index of substring in a string
    Arguments: String and the search string
    Return:Returning a list
    """
    length = len(substring)
    c=0
    indexes = []
    while c < len(string):
        if string[c:c+length] == substring:
            indexes.append(c)
        c=c+1
    return indexes

Wywołanie metody

newstr.find_all („Czy uważasz, że ta odpowiedź jest pomocna? a następnie głosuj na to!”, „to”)

naveen raja
źródło
2

Ta funkcja nie patrzy na wszystkie pozycje w ciągu, nie marnuje zasobów obliczeniowych. Moja próba:

def findAll(string,word):
    all_positions=[]
    next_pos=-1
    while True:
        next_pos=string.find(word,next_pos+1)
        if(next_pos<0):
            break
        all_positions.append(next_pos)
    return all_positions

aby go użyć, nazwij to tak:

result=findAll('this word is a big word man how many words are there?','word')
Valentin Goikhman
źródło
1

Szukając dużej liczby słów kluczowych w dokumencie, użyj tekstu błyskawicznego

from flashtext import KeywordProcessor
words = ['test', 'exam', 'quiz']
txt = 'this is a test'
kwp = KeywordProcessor()
kwp.add_keywords_from_list(words)
result = kwp.extract_keywords(txt, span_info=True)

Flashtext działa szybciej niż wyrażenie regularne na dużej liście wyszukiwanych słów.

Uri Goren
źródło
0
src = input() # we will find substring in this string
sub = input() # substring

res = []
pos = src.find(sub)
while pos != -1:
    res.append(pos)
    pos = src.find(sub, pos + 1)
mascai
źródło
1
Chociaż ten kod może rozwiązać problem PO, najlepiej dołączyć wyjaśnienie, w jaki sposób Twój kod rozwiązuje problem PO. W ten sposób przyszli użytkownicy mogą uczyć się z Twojego postu i stosować go do własnego kodu. SO nie jest usługą kodowania, ale zasobem wiedzy. Ponadto, wysokiej jakości, pełne odpowiedzi będą częściej oceniane. Funkcje te, wraz z wymogiem, że wszystkie posty są samodzielne, stanowią niektóre z zalet SO jako platformy, która odróżnia ją od forum. Możesz edytować, aby dodać dodatkowe informacje i / lub uzupełnić swoje objaśnienia dokumentacją źródłową
SherylHohman
0

To jest rozwiązanie podobnego pytania od hackera. Mam nadzieję, że to może ci pomóc.

import re
a = input()
b = input()
if b not in a:
    print((-1,-1))
else:
    #create two list as
    start_indc = [m.start() for m in re.finditer('(?=' + b + ')', a)]
    for i in range(len(start_indc)):
        print((start_indc[i], start_indc[i]+len(b)-1))

Wynik:

aaadaa
aa
(0, 1)
(1, 2)
(4, 5)
Ruman Khan
źródło
-1

Krojąc, znajdujemy wszystkie możliwe kombinacje i dołączamy je do listy oraz określamy, ile razy występuje przy użyciu countfunkcji

s=input()
n=len(s)
l=[]
f=input()
print(s[0])
for i in range(0,n):
    for j in range(1,n+1):
        l.append(s[i:j])
if f in l:
    print(l.count(f))
BONTHA SREEVIDHYA
źródło
Kiedy s="test test test test"i f="test"kod zostanie wydrukowany 4, ale oczekiwany OP[0,5,10,15]
barbsan
Napisałem
-2

spójrz na poniższy kod

#!/usr/bin/env python
# coding:utf-8
'''黄哥Python'''


def get_substring_indices(text, s):
    result = [i for i in range(len(text)) if text.startswith(s, i)]
    return result


if __name__ == '__main__':
    text = "How much wood would a wood chuck chuck if a wood chuck could chuck wood?"
    s = 'wood'
    print get_substring_indices(text, s)
黄 哥 Python 培训
źródło
-2

Pythonicznym sposobem byłoby:

mystring = 'Hello World, this should work!'
find_all = lambda c,s: [x for x in range(c.find(s), len(c)) if c[x] == s]

# s represents the search string
# c represents the character string

find_all(mystring,'o')    # will return all positions of 'o'

[4, 7, 20, 26] 
>>> 
Harvey
źródło
3
1) Jak to pomaga na pytanie, na które udzielono odpowiedzi 7 lat temu? 2) Używanie w lambdaten sposób nie jest Pythonic i jest sprzeczne z PEP8 . 3) Nie zapewnia to prawidłowego wyniku dla sytuacji PO
Wondercricket
Pythonic nie oznacza „Używaj tylu funkcji Pythona, ile tylko możesz sobie wyobrazić”
klutt
-2

Możesz łatwo użyć:

string.count('test')!

https://www.programiz.com/python-programming/methods/string/count

Twoje zdrowie!

RaySaraiva
źródło
taka powinna być odpowiedź
Maxwell Chandler,
8
Metoda string count () zwraca liczbę wystąpień podłańcucha w danym ciągu. Nie ich lokalizacja.
Astrid,
5
to nie spełnia wszystkich przypadków, s = „banan”, sub = „ana”. Sub występuje w tej sytuacji dwa razy, ale wykonanie s.sub ('ana')
zwróciłoby