Seite 1 von 1
´in´ für Teil-Listen (wie beim String)
Verfasst: Montag 14. Dezember 2015, 14:44
von Üpsilon
Hallo ihr Lieben,
Code: Alles auswählen
>>> "baum" in "baumhaus"
True
>>> [3,4] in [1,2,3,4,5]
False
>>>
Das fand ich überraschend. Gibt es etwas Eingebautes, um zu gucken, ob eine Liste eine Teil-Liste einer anderen Liste ist? Danke.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Montag 14. Dezember 2015, 15:40
von DasIch
Gibt es nicht. Allerdings gibt es sowas für sets:
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Montag 14. Dezember 2015, 16:50
von Üpsilon
Danke, aber das ist für mich leider keine Lösung, weil bei mir die Reihenfolge wichtig ist. Hab mir dann selbst was gebraut.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Montag 14. Dezember 2015, 17:45
von eckhard
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Montag 14. Dezember 2015, 20:19
von BlackJack
@eckhard: Soweit ich sehe steht da nur eine Lösung für das vorliegende Problem, die von Steven D'Aprano. Beide Listen vorher in Zeichenketten umwandeln und dann damit den ``in``-Test zu machen sehe ich jetzt mal nicht als Lösung an, das ist eher ein Hack der nur für Listen funktioniert bei denen das zufällig funktioniert, aber halt nicht im allgemeinen Fall.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 12:59
von snafu
Wie wäre es damit?
Code: Alles auswählen
from collections import Sequence
def contains_sequence(seq, subseq):
if not isinstance(seq, Sequence) or not isinstance(subseq, Sequence):
raise TypeError('seq and subseq must be sequences')
if not subseq:
return True
try:
i = seq.index(subseq[0])
except ValueError:
# subseq[0] not in seq
return False
stop = i + len(subseq)
if not isinstance(subseq, type(seq)):
# Different types are never equal
subseq = type(seq)(subseq)
return seq[i:stop] == subseq
EDIT: Dieser Ansatz ist Mist, merke ich gerade. Er basiert auf dem ersten Vorkommen von ``subseq[0]`` und schaut sich nur die Elemente direkt nach diesem Vorkommen an. Es kann aber sein, dass erst ein späteres Vorkommen die gesuchte Untersequenz enthält.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 13:36
von snafu
Hier die verbesserte Version:
Code: Alles auswählen
from collections import Sequence
def contains_sequence(seq, subseq):
if not isinstance(seq, Sequence) or not isinstance(subseq, Sequence):
raise TypeError('seq and subseq must be sequences')
if not subseq:
return True
if not isinstance(subseq, type(seq)):
subseq = type(seq)(subseq)
start = 0
while True:
try:
start = seq.index(subseq[0], start)
except ValueError:
return False
stop = start + len(subseq)
if seq[start:stop] == subseq:
return True
start += 1
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 13:44
von DasIch
Das kann man richtig sogar in wesentlich kürzer lösen, sofern man sich die paranoiden Typprüfungen spart.
Code: Alles auswählen
def startswith_sequence(sequence, subsequence):
return sequence[:len(subsequence)] == subsequence
def contains_sequence(sequence, subsequence):
return any(
startswith_sequence(sequence[i:], subsequence)
for i in range(len(sequence) - len(subsequence) + 1)
)
EDIT: Leicht optimiert.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 14:12
von snafu
@DasIch
Deine Variante beinhaltet sicherlich weniger Quelltext. Falls die Ausführungszeit eine Rolle spielt, würde ich diese Variante allerdings nicht benutzen wollen, da sie eine quadratische Laufzeit hat. Aus diesem Grund hatte ich `.index()` benutzt und finde dessen Einsatz auch nach wie vor sinnvoll.
Hier nochmal eine Version basierend auf `any()` und `.index()` (ohne "paranoide Typprüfungen"):
Code: Alles auswählen
def iter_candidates(seq, subseq):
sublen = len(subseq)
start = 0
while True:
try:
start = seq.index(subseq[0], start)
except ValueError:
return
yield (start, start + sublen)
start += 1
def contains_sequence(seq, subseq):
if not subseq:
return True
return any(
seq[start:stop] == subseq
for start, stop in iter_candidates(seq, subseq)
)
EDIT: Genau genommen ist die Laufzeit bei mir auch quadratisch. Aber der Worst Case ist eben unwahrscheinlicher. Mit anderen Worten: Es werden potenziell weniger Teillisten erzeugt und überprüft als ohne den Einsatz von `.index()`.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 14:19
von BlackJack
Wobei in beiden Lösungen potentiell viele Listenkopien durch slicing erstellt werden. Das ist IMHO einer der Fälle wo man in Python Code schreiben muss/sollte der eher nach C oder Java aussieht, so mit Zählschleifen über Indexwerte. Eben das was Steven D'Aprano in der weiter oben verlinkten Mailingliste gemacht hat.
In der Programmiersprache Io haben Listen eine `containsAll()`-Methode — sowas fehlt in Python irgendwie.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 14:41
von snafu
Unter Einbeziehung der Ideen aus dem verlinkten Thread:
Code: Alles auswählen
def iter_candidates(seq, subseq):
for i, item in enumerate(seq):
if item == subseq[0]:
yield i
def contains_sequence(seq, subseq):
if not subseq:
return True
if len(subseq) > len(seq):
return False
for start in iter_candidates(seq, subseq):
for i, subitem in enumerate(subseq):
if seq[start + i] != subitem:
break
else:
return True
return False
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 15:16
von snafu
Hier nochmal etwas gekürzt mittels `all()`:
Code: Alles auswählen
def contains_sequence(seq, subseq, start=0):
if not subseq:
return True
if len(subseq) > len(seq):
return False
while True:
try:
start = seq.index(subseq[0], start)
except ValueError:
return False
if all(seq[start + i] == subitem for i, subitem in enumerate(subseq)):
return True
start += 1
EDIT: Übrigens deutlich langsamer als mein Slicing-basierter Ansatz.
Wenn ich den Code ab der Stelle mit `all()` wie folgt ersetze:
Code: Alles auswählen
stop = start + len(subseq)
if seq[start:stop] == subseq:
return True
start += 1
...dann spuckt mir ein ``timeit contains_sequence([1,2,5,1,2,6,1,2,3,4,5], [1,2,3,4,5])`` Laufzeiten aus, die nur halb so groß sind wie mit dem `enumerate()`-basierten Loop.

Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 16:30
von Sirius3
@snafu: das Kopieren von relativ kurzen Listslices geht ja auch viel schneller als eine enumerate-Schleife in Python. Und wenn Du bei all sowieso bis zum Schluß testen mußt, gibt es wohl keinen Fall, wo die all-Methode schneller wäre, es sei denn, der Speicher wird knapp. Als Kompromiss könnte man ja Junks von 100-Elementen vergleichen, dann hat man den nativen Geschwindigkeitsvorteil und keine große Speicherverschwendung.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 15. Dezember 2015, 16:35
von BlackJack
Das ist einfach etwas was in C programmiert sein sollte wenn's effizient sein soll und IMHO eine Methode auf `list` sein sollte. Wenn ich Python 3 verwenden würde, dann würde ich hier glatt mal in den Archiven stöbern ob das schon mal diskutiert wurde und ggf. eine Implementierung schreiben.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Mittwoch 16. Dezember 2015, 12:32
von snafu
BlackJack hat geschrieben:Das ist einfach etwas was in C programmiert sein sollte wenn's effizient sein soll und IMHO eine Methode auf `list` sein sollte.
Sowohl auf `list` als auch auf `tuple`, finde ich. Da kann man jetzt entweder an beiden APIs Änderungen vornehmen oder `containsAll()` als allgemeine Funktion schreiben und diese z.B. über das `operator`-Modul anbieten. Ich habe mal zum Spaß etwas auf Basis des `PySequence`-Protokolls geschrieben. Ich werde das noch ein bißchen ausarbeiten und stelle es vielleicht dann in ein paar Tagen als Verbesserungsvorschlag vor.
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Freitag 18. Dezember 2015, 18:16
von snafu
Ich habe mal eine bespielhafte Implementierung in C auf den Python-Bugtracker gesetzt. Es kamen auch schon ein paar Rückmeldungen. Der Tenor ist anscheinend, dass man die Idee grundsätzlich nicht schlecht findet, aber sich nicht sicher ist, ob dies Teil der Standardbibliothek werden soll. Eine endgültige Entscheidung steht aber wohl noch aus. Für Interessierte:
http://bugs.python.org/issue25898
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Freitag 18. Dezember 2015, 19:40
von Üpsilon
boah, da hab ich ja was losgetreten :O Danke!
Meine Lösung sieht i.wie so aus (hab es grade nicht dabei):
Code: Alles auswählen
def ist_teilliste(kleine_liste, grosse_liste):
if len(kleine_liste) > len(grosse_liste): return False
if grosse_liste[:len(kleine_liste)] == kleine_liste: return True
return ist_teilliste(kleine_liste[1:], grosse_liste)
... Unglaubliche Effizienz durch Rekursion ...
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Dienstag 29. Dezember 2015, 14:52
von snafu
Hier mal aus Spaß an der Freude eine Iterator-basierte Umsetzung:
Code: Alles auswählen
#!/usr/bin/env python3
from collections import deque
from itertools import chain, islice
def dropuntil(iterable, needle):
"""
Advance `iterable` until a sequence with all items of `needle` (with
respect to their order) is found. Then yield all remaining items
starting with the first item of `needle`. Yield nothing if `iterable`
does not contain the given needle.
"""
iterator = iter(iterable)
window = deque(maxlen=len(needle))
window.extend(islice(iterator, len(needle)))
if len(window) == len(needle):
while any(a != b for a, b in zip(window, needle)):
try:
window.append(next(iterator))
except StopIteration:
return
yield from chain(window, iterator)
def main():
print(list(dropuntil([1,2,9,3,4,3,5,2,3,4,2,3], [2,3])))
print(list(dropuntil([1,2,9,3,4,3,5,2,3,4,2,3], [8,9])))
print(''.join(dropuntil('xxxhamyyyspamzzzeggs', 'spam')))
if __name__ == '__main__':
main()
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Mittwoch 30. Dezember 2015, 12:09
von snafu
Hier nochmal mit `map` anstelle der Generator Comprehension zur Performancesteigerung:
http://pastebin.com/LUYUi5Zk
Re: ´in´ für Teil-Listen (wie beim String)
Verfasst: Mittwoch 30. Dezember 2015, 12:26
von BlackJack
Ich würde es ja als `dropwhile()` implementieren. Die `needle` hat der Aufrufer ja schon, wenn er die vor dem Ergebnis haben möchte, kann er den `chain()`-Aufruf selber machen. Wenn er die `needle`-Daten dagegen nicht braucht, dann macht die Funktion erst ein `chain()` was der Aufrufer mit einem `islice()` wieder ”rückgängig” machen muss, also zwei Iteratorindirektionen zusätzlich.