Comment trouver toutes les occurrences d'une sous-chaîne?

365

Python a string.find()et string.rfind()pour obtenir l'index d'une sous-chaîne dans une chaîne.

Je me demande s'il y a quelque chose comme string.find_all()qui peut retourner tous les index trouvés (pas seulement le premier depuis le début ou le premier depuis la fin).

Par exemple:

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
la source
11
qu'est-ce qui devrait 'ttt'.find_all('tt')revenir?
Santiago Alessandri
2
il doit retourner «0». Bien sûr, dans un monde parfait, il doit aussi y en avoir 'ttt'.rfind_all('tt'), qui devrait renvoyer '1'
nukl
2
Semble comme un doublon de ce stackoverflow.com/questions/3873361/…
nu everest

Réponses:

523

Il n'y a pas de fonction de chaîne intégrée simple qui fait ce que vous cherchez, mais vous pouvez utiliser les expressions régulières les plus puissantes :

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

Si vous voulez trouver des correspondances qui se chevauchent, lookahead le fera:

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

Si vous voulez une recherche inversée sans chevauchements, vous pouvez combiner l'anticipation positive et négative en une expression comme celle-ci:

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

re.finditerrenvoie un générateur , vous pouvez donc changer le []dans ce qui précède pour ()obtenir un générateur au lieu d'une liste qui sera plus efficace si vous parcourez les résultats une seule fois.

moinudin
la source
salut, à ce sujet [m.start() for m in re.finditer('test', 'test test test test')], comment pouvons-nous rechercher testou text? Cela devient-il beaucoup plus compliqué?
xpanta
7
Vous voulez examiner les expressions régulières en général: docs.python.org/2/howto/regex.html . La solution à votre question sera: [m.start () pour m dans re.finditer ('te [sx] t', 'text test text test')]
Yotam Vaknin
1
Quelle sera la complexité temporelle de l'utilisation de cette méthode?
Pranjal Mittal
1
@PranjalMittal. Limite supérieure ou inférieure? Meilleur, pire ou cas moyen?
Mad Physicist
@marcog que faire si la sous-chaîne contient des parenthèses ou d'autres caractères spéciaux?
Bananach
109
>>> help(str.find)
Help on method_descriptor:

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

Ainsi, nous pouvons le construire nous-mêmes:

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]

Aucune chaîne ou expression régulière temporaire n'est requise.

Karl Knechtel
la source
22
Pour obtenir des correspondances qui se chevauchent, il devrait suffire de les remplacer start += len(sub)par start += 1.
Karl Knechtel
4
Je crois que votre commentaire précédent devrait être un post-scriptum dans votre réponse.
tzot
1
Votre code ne fonctionne pas pour trouver des substr: "ATAT" dans "GATATATGCATATACTT"
Ashish Negi
2
Voir le commentaire que j'ai fait en plus. C'est un exemple de correspondance qui se chevauchent.
Karl Knechtel
4
Pour correspondre au comportement de re.findall, je recommande d'ajouter len(sub) or 1au lieu de len(sub), sinon ce générateur ne se terminera jamais sur une sous-chaîne vide.
WGH
45

Voici un moyen (très inefficace) d'obtenir toutes les correspondances (c'est-à-dire même les chevauchements):

>>> string = "test test test test"
>>> [i for i in range(len(string)) if string.startswith('test', i)]
[0, 5, 10, 15]
thkala
la source
25

Encore une fois, vieux fil, mais voici ma solution en utilisant un générateur et simple 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)

Exemple

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

Retour

[(2, 'na'), (4, 'na'), (6, 'na'), (14, 'na')]
AkiRoss
la source
3
c'est magnifique!
fabio.sang
21

Vous pouvez utiliser re.finditer()pour des correspondances qui ne se chevauchent pas.

>>> 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)]

mais ne fonctionnera pas pour:

In [1]: aString="ababa"

In [2]: print [(a.start(), a.end()) for a in list(re.finditer('aba', aString))]
Output: [(0, 3)]
Chinmay Kanchi
la source
12
Pourquoi faire une liste à partir d'un itérateur, cela ralentit simplement le processus.
pradyunsg
2
aString VS astring;)
NexD.
18

Venez, laissez-nous récurer ensemble.

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]

Pas besoin d'expressions régulières de cette façon.

Cody Piersall
la source
Je viens de commencer à me demander "existe-t-il un moyen sophistiqué de localiser une sous-chaîne à l'intérieur d'une chaîne en python" ... puis après 5 min de recherche sur Google, j'ai trouvé votre code. Merci d'avoir partagé!!!
Geparada
3
Ce code a plusieurs problèmes. Comme cela fonctionne tôt ou tard sur des données ouvertes, vous rencontrerez RecursionErrors'il y a suffisamment d'occurrences. Un autre est deux listes à jeter qu'il crée à chaque itération juste pour ajouter un élément, ce qui est très sous-optimal pour une fonction de recherche de chaîne, qui pourrait éventuellement être appelée beaucoup de fois. Bien que les fonctions récursives semblent parfois élégantes et claires, elles doivent être prises avec prudence.
Ivan Nikolaev
11

Si vous cherchez juste un seul personnage, cela fonctionnerait:

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

Aussi,

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

Mon intuition est qu'aucune de celles-ci (en particulier # 2) n'est terriblement performante.

jstaab
la source
solution gr8 .. je suis impressionné par l'utilisation de .. split ()
shantanu pathak
9

c'est un vieux fil mais je me suis intéressé et je voulais partager ma solution.

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

Il devrait renvoyer une liste de positions où la sous-chaîne a été trouvée. Veuillez commenter si vous voyez une erreur ou une marge d'amélioration.

Thurines
la source
6

Cela fait l'affaire pour moi en utilisant 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
la source
5

Ce fil est un peu vieux mais cela a fonctionné pour moi:

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
la source
5

Tu peux essayer :

>>> 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
la source
2

Quelles que soient les solutions fournies par d'autres, elles sont entièrement basées sur la méthode disponible find () ou sur toute méthode disponible.

Quel est l'algorithme de base de base pour trouver toutes les occurrences d'une sous-chaîne dans une chaîne?

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

Vous pouvez également hériter de la classe str dans une nouvelle classe et utiliser cette fonction ci-dessous.

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

Appel de la méthode

newstr.find_all ('Trouvez-vous cette réponse utile? alors votez pour ceci!', 'ceci')

naveen raja
la source
2

Cette fonction ne regarde pas toutes les positions à l'intérieur de la chaîne, elle ne gaspille pas les ressources de calcul. Mon essai:

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

pour l'utiliser, appelez-le comme ceci:

result=findAll('this word is a big word man how many words are there?','word')
Valentin Goikhman
la source
1

Lorsque vous recherchez une grande quantité de mots clés dans un document, utilisez le flashtext

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 s'exécute plus rapidement que regex sur une grande liste de mots de recherche.

Uri Goren
la source
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
la source
1
Bien que ce code puisse résoudre le problème de l'OP, il est préférable d'inclure une explication sur la façon dont votre code résout le problème de l'OP. De cette façon, les futurs visiteurs peuvent apprendre de votre message et l'appliquer à leur propre code. SO n'est pas un service de codage, mais une ressource de connaissances. En outre, les réponses complètes de haute qualité sont plus susceptibles d'être votées positivement. Ces fonctionnalités, ainsi que l'exigence que tous les messages soient autonomes, sont quelques-unes des forces de SO en tant que plate-forme, ce qui le différencie des forums. Vous pouvez modifier pour ajouter des informations supplémentaires et / ou pour compléter vos explications avec la documentation source
SherylHohman
0

C'est la solution d'une question similaire de hackerrank. J'espère que cela pourrait vous aider.

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))

Production:

aaadaa
aa
(0, 1)
(1, 2)
(4, 5)
Ruman Khan
la source
-1

En découpant, nous trouvons toutes les combinaisons possibles et les ajoutons dans une liste et trouvons le nombre de fois où cela se produit en utilisant la countfonction

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
la source
Quand s="test test test test"et f="test"votre code s'imprime 4, mais OP attendu[0,5,10,15]
barbsan
Avoir écrit pour un seul mot mettra à jour le code
BONTHA SREEVIDHYA
-2

veuillez regarder ci-dessous le code

#!/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 培训
la source
-2

La voie pythonique serait:

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
la source
3
1) Comment cela aide-t-il une question à laquelle il a été répondu il y a 7 ans? 2) L' utilisation de lambdacette manière n'est pas Pythonic et va à l'encontre de PEP8 . 3) Cela ne fournit pas la sortie correcte pour la situation des PO
Wondercricket
Pythonic ne signifie pas "Utilisez autant de fonctionnalités de python que vous pouvez penser"
klutt
-2

Vous pouvez facilement utiliser:

string.count('test')!

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

À votre santé!

RaySaraiva
la source
cela devrait être la réponse
Maxwell Chandler
8
La méthode string count () renvoie le nombre d'occurrences d'une sous-chaîne dans la chaîne donnée. Pas leur emplacement.
Astrid
5
cela ne satisfait pas tous les cas, s = 'banane', sub = 'ana'. Sub se produit deux fois dans cette situation mais faire ssub ('ana') reviendrait 1
Joey daniel darko