Suchen mehrerer Vorkommen einer Zeichenfolge in einer Zeichenfolge in Python

79

Wie finde ich mehrere Vorkommen einer Zeichenfolge in einer Zeichenfolge in Python? Bedenken Sie:

>>> text = "Allowed Hello Hollow"
>>> text.find("ll")
1
>>> 

Das erste Auftreten von llist also wie erwartet bei 1. Wie finde ich das nächste Auftreten davon?

Die gleiche Frage gilt für eine Liste. Erwägen:

>>> x = ['ll', 'ok', 'll']

Wie finde ich alle llmit ihren Indizes?

user225312
quelle
3
>>> text.count ("ll")
blackappy
2
@blackappy dies zählt Vorkommen, lokalisiert sie nicht
pcko1

Antworten:

120

Mit regulären Ausdrücken können Sie re.finditeralle (nicht überlappenden) Vorkommen finden:

>>> import re
>>> text = 'Allowed Hello Hollow'
>>> for m in re.finditer('ll', text):
         print('ll found', m.start(), m.end())

ll found 1 3
ll found 10 12
ll found 16 18

Wenn Sie den Overhead regulärer Ausdrücke nicht möchten, können Sie alternativ auch wiederholt str.findden nächsten Index abrufen:

>>> text = 'Allowed Hello Hollow'
>>> index = 0
>>> while index < len(text):
        index = text.find('ll', index)
        if index == -1:
            break
        print('ll found at', index)
        index += 2 # +2 because len('ll') == 2

ll found at  1
ll found at  10
ll found at  16

Dies funktioniert auch für Listen und andere Sequenzen.

Sack
quelle
1
Gibt es keine Möglichkeit, dies ohne reguläre Ausdrücke zu tun?
user225312
1
Nicht dass ich ein Problem hätte, sondern nur neugierig.
user225312
2
Listen haben nicht find. Aber es funktioniert mit index, Sie müssen nur except ValueErroranstatt auf -1 zu testen
aaronasterling
@ Aaron: Ich habe mich auf die Grundidee bezogen, natürlich muss man sie für Listen ein wenig ändern (zum Beispiel index += 1stattdessen).
stupsen
4
jetzt , dass Sie die ganze erwähnen index += 2Sache, wenn man dies auf den String ‚lllll‘ anwenden, wird es zwei von vier Vorkommen von ‚ll‘ verpassen. Am besten auch index += 1für Saiten.
Aaronasterling
30

Ich denke, was Sie suchen, ist string.count

"Allowed Hello Hollow".count('ll')
>>> 3

Hoffe, dies hilft
HINWEIS: Dies erfasst nur nicht überlappende Vorkommen

inspectorG4dget
quelle
wow Danke. Dies war eine sehr einfache Antwort
Derrick
25

Verwenden Sie für das Listenbeispiel ein Verständnis:

>>> l = ['ll', 'xx', 'll']
>>> print [n for (n, e) in enumerate(l) if e == 'll']
[0, 2]

Ähnliches gilt für Strings:

>>> text = "Allowed Hello Hollow"
>>> print [n for n in xrange(len(text)) if text.find('ll', n) == n]
[1, 10, 16]

Dadurch werden benachbarte Läufe von "ll" aufgelistet, die möglicherweise Ihren Wünschen entsprechen oder nicht:

>>> text = 'Alllowed Hello Holllow'
>>> print [n for n in xrange(len(text)) if text.find('ll', n) == n]
[1, 2, 11, 17, 18]
bstpierre
quelle
Wow ich mag das. Vielen Dank. Dies ist perfekt.
user225312
5
Dies ist äußerst ineffizient.
Clément
1
@ Clément
poste
@ Clément print [n für n in xrange (len (text)) wenn text [n-1: n] == 'll']
Stephen
Ich meinte: drucke [n für n in xrange (len (text)) wenn text [n: n + 2] == 'll']
Stephen
14

FWIW, hier sind ein paar Nicht-RE-Alternativen, die meiner Meinung nach besser sind als die Lösung von Poke .

Der erste verwendet str.indexund prüft auf ValueError:

def findall(sub, string):
    """
    >>> text = "Allowed Hello Hollow"
    >>> tuple(findall('ll', text))
    (1, 10, 16)
    """
    index = 0 - len(sub)
    try:
        while True:
            index = string.index(sub, index + len(sub))
            yield index
    except ValueError:
        pass

Der zweite Test verwendet str.findund prüft den Sentinel von, -1indem er Folgendes verwendet iter:

def findall_iter(sub, string):
    """
    >>> text = "Allowed Hello Hollow"
    >>> tuple(findall_iter('ll', text))
    (1, 10, 16)
    """
    def next_index(length):
        index = 0 - length
        while True:
            index = string.find(sub, index + length)
            yield index
    return iter(next_index(len(sub)).next, -1)

Um eine dieser Funktionen auf eine Liste, ein Tupel oder eine andere iterierbare Zeichenfolge anzuwenden, können Sie eine übergeordnete Funktion verwenden - eine, die eine Funktion als eines ihrer Argumente verwendet - wie diese:

def findall_each(findall, sub, strings):
    """
    >>> texts = ("fail", "dolly the llama", "Hello", "Hollow", "not ok")
    >>> list(findall_each(findall, 'll', texts))
    [(), (2, 10), (2,), (2,), ()]
    >>> texts = ("parallellized", "illegally", "dillydallying", "hillbillies")
    >>> list(findall_each(findall_iter, 'll', texts))
    [(4, 7), (1, 6), (2, 7), (2, 6)]
    """
    return (tuple(findall(sub, string)) for string in strings)
intuitiv
quelle
3

Für Ihr Listenbeispiel:

In [1]: x = ['ll','ok','ll']

In [2]: for idx, value in enumerate(x):
   ...:     if value == 'll':
   ...:         print idx, value       
0 ll
2 ll

Wenn Sie alle Elemente in einer Liste haben möchten, die 'll' enthält, können Sie dies auch tun.

In [3]: x = ['Allowed','Hello','World','Hollow']

In [4]: for idx, value in enumerate(x):
   ...:     if 'll' in value:
   ...:         print idx, value
   ...:         
   ...:         
0 Allowed
1 Hello
3 Hollow
chauncey
quelle
2
>>> for n,c in enumerate(text):
...   try:
...     if c+text[n+1] == "ll": print n
...   except: pass
...
1
10
16
Ghostdog74
quelle
1

Ganz neu in der Programmierung im Allgemeinen und in einem Online-Tutorial. Ich wurde gebeten, dies ebenfalls zu tun, aber nur mit den Methoden, die ich bisher gelernt hatte (im Grunde Strings und Loops). Ich bin mir nicht sicher, ob dies hier einen Mehrwert bringt, und ich weiß, dass Sie es nicht so machen würden, aber ich habe es dazu gebracht, damit zu arbeiten:

needle = input()
haystack = input()
counter = 0
n=-1
for i in range (n+1,len(haystack)+1):
   for j in range(n+1,len(haystack)+1):
      n=-1
      if needle != haystack[i:j]:
         n = n+1
         continue
      if needle == haystack[i:j]:
         counter = counter + 1
print (counter)
Aaron Semeniuk
quelle
1

Diese Version sollte eine lineare Länge der Zeichenfolge haben und in Ordnung sein, solange sich die Sequenzen nicht zu oft wiederholen (in diesem Fall können Sie die Rekursion durch eine while-Schleife ersetzen).

def find_all(st, substr, start_pos=0, accum=[]):
    ix = st.find(substr, start_pos)
    if ix == -1:
        return accum
    return find_all(st, substr, start_pos=ix + 1, accum=accum + [ix])

Das Listenverständnis von bstpierre ist eine gute Lösung für kurze Sequenzen, scheint jedoch quadratisch komplex zu sein und hat einen von mir verwendeten Langtext nie fertiggestellt.

findall_lc = lambda txt, substr: [n for n in xrange(len(txt))
                                   if txt.find(substr, n) == n]

Für eine zufällige Zeichenfolge mit nicht trivialer Länge ergeben die beiden Funktionen dasselbe Ergebnis:

import random, string; random.seed(0)
s = ''.join([random.choice(string.ascii_lowercase) for _ in range(100000)])

>>> find_all(s, 'th') == findall_lc(s, 'th')
True
>>> findall_lc(s, 'th')[:4]
[564, 818, 1872, 2470]

Die quadratische Version ist jedoch etwa 300-mal langsamer

%timeit find_all(s, 'th')
1000 loops, best of 3: 282 µs per loop

%timeit findall_lc(s, 'th')    
10 loops, best of 3: 92.3 ms per loop
Bart
quelle
0
#!/usr/local/bin python3
#-*- coding: utf-8 -*-

main_string = input()
sub_string = input()

count = counter = 0

for i in range(len(main_string)):
    if main_string[i] == sub_string[0]:
        k = i + 1
        for j in range(1, len(sub_string)):
            if k != len(main_string) and main_string[k] == sub_string[j]:
                count += 1
                k += 1
        if count == (len(sub_string) - 1):
            counter += 1
        count = 0

print(counter) 

Dieses Programm zählt die Anzahl aller Teilzeichenfolgen, auch wenn sie sich ohne Verwendung von Regex überlappen. Dies ist jedoch eine naive Implementierung. Um im schlimmsten Fall bessere Ergebnisse zu erzielen, wird empfohlen, entweder Suffix Tree, KMP oder andere String-Matching-Datenstrukturen und -Algorithmen zu verwenden.

pmsh.93
quelle
0

Hier ist meine Funktion zum Auffinden mehrerer Vorkommen. Im Gegensatz zu den anderen Lösungen hier werden die optionalen Start- und Endparameter für das Schneiden unterstützt, genau wie str.index:

def all_substring_indexes(string, substring, start=0, end=None):
    result = []
    new_start = start
    while True:
        try:
            index = string.index(substring, new_start, end)
        except ValueError:
            return result
        else:
            result.append(index)
            new_start = index + len(substring)
Elias Zamaria
quelle
0

Ein einfacher iterativer Code, der eine Liste von Indizes zurückgibt, in denen der Teilstring vorkommt.

        def allindices(string, sub):
           l=[]
           i = string.find(sub)
           while i >= 0:
              l.append(i)
              i = string.find(sub, i + 1)
           return l
FReeze FRancis
quelle
0

Sie können teilen, um relative Positionen zu erhalten, dann aufeinanderfolgende Zahlen in einer Liste summieren und gleichzeitig hinzufügen (Zeichenfolgenlänge * Vorkommensreihenfolge), um die gewünschten Zeichenfolgenindizes zu erhalten.

>>> key = 'll'
>>> text = "Allowed Hello Hollow"
>>> x = [len(i) for i in text.split(key)[:-1]]
>>> [sum(x[:i+1]) + i*len(key) for i in range(len(x))]
[1, 10, 16]
>>> 
Kenly
quelle
0

Vielleicht nicht so pythonisch, aber etwas selbsterklärender. Es gibt die Position des Wortes zurück, das in der ursprünglichen Zeichenfolge enthalten ist.

def retrieve_occurences(sequence, word, result, base_counter):
     indx = sequence.find(word)
     if indx == -1:
         return result
     result.append(indx + base_counter)
     base_counter += indx + len(word)
     return retrieve_occurences(sequence[indx + len(word):], word, result, base_counter)
Blasrodri
quelle
0

Ich denke, es ist nicht nötig, die Länge des Textes zu testen. finde einfach weiter, bis nichts mehr zu finden ist. So was:

    >>> text = 'Allowed Hello Hollow'
    >>> place = 0
    >>> while text.find('ll', place) != -1:
            print('ll found at', text.find('ll', place))
            place = text.find('ll', place) + 2


    ll found at 1
    ll found at 10
    ll found at 16
rdo
quelle
0

Sie können dies auch mit dem folgenden bedingten Listenverständnis tun:

string1= "Allowed Hello Hollow"
string2= "ll"
print [num for num in xrange(len(string1)-len(string2)+1) if string1[num:num+len(string2)]==string2]
# [1, 10, 16]
Stefan Gruenwald
quelle
0

Ich hatte diese Idee erst vor einiger Zeit zufällig bekommen. Die Verwendung einer While-Schleife mit String-Spleißung und String-Suche kann auch bei überlappenden Strings funktionieren.

findin = "algorithm alma mater alison alternation alpines"
search = "al"
inx = 0
num_str = 0

while True:
    inx = findin.find(search)
    if inx == -1: #breaks before adding 1 to number of string
        break
    inx = inx + 1
    findin = findin[inx:] #to splice the 'unsearched' part of the string
    num_str = num_str + 1 #counts no. of string

if num_str != 0:
    print("There are ",num_str," ",search," in your string.")
else:
    print("There are no ",search," in your string.")

Ich bin ein Amateur in Python-Programmierung (eigentlich Programmierung einer beliebigen Sprache) und bin mir nicht sicher, welche anderen Probleme es haben könnte, aber ich denke, es funktioniert gut?

Ich denke, lower () könnte bei Bedarf auch irgendwo darin verwendet werden.

Mystearica Primal Fende
quelle