Aus einer Liste Endelemente entfernen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ich wollte einmal fragen, ob es einen Python Befehl für so etwas gibt.
Ich habe eine Liste:

Code: Alles auswählen

my_list = [ (1,2,3),
            (4,5,6),
            (7,8,9),
            (0,0,0),
            (0,0,0),
            (0,0,0) ]
Und daraus möchte ich die (0,0,0) Elemente am Ende entfernen.
Gibt es für so etwas eine Python Funktion oder macht man das auf herkömmliche Art mit einer Schleife?

Meine Lösung sähe so aus:

Code: Alles auswählen

def remove_trailing_elements(some_list,trailing_value):
    for index in range(len(some_list)-1,-1,-1):
        if some_list[index] == trailing_value:
            some_list.pop(index)
Aber entspricht die der Python Philosophie, dass es nur genau eine richtige Lösung geben soll?
Aber welche ist da dann die Richtige?
karolus
User
Beiträge: 140
Registriert: Samstag 22. August 2009, 22:34

Code: Alles auswählen

from itertools import takewhile, filterfalse

list(takewhile(lambda x: x!=(0,0,0), my_list))
#oder
list(filterfalse(lambda x: x==(0,0,0), my_list))
takewhile nur falls die nicht gewünschten Elemente zusammen am Ende stehen.
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Code: Alles auswählen

>>> my_list = [
...     (1, 2, 3),
...     (4, 5, 6),
...     (7, 8, 9),
...     (0, 0, 0),
...     (0, 0, 0),
...     (0, 0, 0)
... ]
>>> my_list
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (0, 0, 0), (0, 0, 0), (0, 0, 0)]
>>> while my_list[-1] == (0, 0, 0):
...     my_list.pop()                                                           
... 
(0, 0, 0)
(0, 0, 0)
(0, 0, 0)
>>> my_list
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]
In specifications, Murphy's Law supersedes Ohm's.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@pillmuncher: ja das ist kurz und prägnant. Bei der for Schleife habe ich glatt noch etwas vergessen, nämlich else: break
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@pillmuncher: die Lösung ist geradlinig. Als Edgecase müsste noch ein IndexError abgefangen werden.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Code: Alles auswählen

def strip_end(sequence, value):
    try:
        stop = sequence.index(value)
    except ValueError:
        stop = None
    return sequence[:stop]

def main():
    my_list = [
        (1,2,3),
        (4,5,6),
        (7,8,9),
        (0,0,0),
        (0,0,0),
        (0,0,0)
    ]
    stripped = strip_end(my_list, (0, 0, 0))
    print(stripped)

if __name__ == '__main__':
    main()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@snafu: Deine Lösung funktioniert aber hier nicht:

Code: Alles auswählen

def strip_end(sequence, value):
    try:
        stop = sequence.index(value)
    except ValueError:
        stop = None
    return sequence[:stop]
 
def main():
    my_list = [
        (1,2,3),
        (4,5,6),
        (0,0,0),
        (7,8,9),
        (0,0,0),
        (0,0,0),
        (0,0,0)
    ]
    stripped = strip_end(my_list, (0, 0, 0))
    print(stripped)
 
if __name__ == '__main__':
    main()
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Richtig. Für den Fall, den du in Zeile 12 zeigst, funktioniert es nicht. Bisher war ja von Endelementen die Rede. Aber scheinbar ist die tatsächliche Anforderung anders als die zuvor beschriebene Anforderung. Du willst also nicht nur Elemente vom Ende der Liste entfernen, sondern alle Elemente, die dem angegebenen Objekt entsprechen...?

EDIT:
Im Übrigen funktionieren die anderen gezeigten Lösungen für diese neue Anforderung genau so wenig.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Eine Möglichkeit, alle Tuple mit Nullen loszuwerden:

Code: Alles auswählen

list(filter(all, my_list))
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Am schnellsten ist übrigens eine LC ohne Funktionsaufrufe:

Code: Alles auswählen

zero_tuple = (0, 0, 0)
[tup for tup in my_list if tup != zero_tuple]
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

snafu hat geschrieben:Richtig. Für den Fall, den du in Zeile 12 zeigst, funktioniert es nicht. Bisher war ja von Endelementen die Rede. Aber scheinbar ist die tatsächliche Anforderung anders als die zuvor beschriebene Anforderung. Du willst also nicht nur Elemente vom Ende der Liste entfernen, sondern alle Elemente, die dem angegebenen Objekt entsprechen...?
Nein es ist keine neue Anforderung aber deine Lösung funktioniert nicht. Die Lösung soll trailing Elemente am Ende entfernen, vergleichber mit Leerzeichen am Stringende. Deine Lösung kappt aber alles ab dem ersten 'Leerzeichen', das soll sie natürlich nicht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@pillmuncher: ich lande bei Berücksichtigung von Randbedingungen und unter Berücksichtigung dass man auch pop nicht braucht, doch bei der for Schleife:

Code: Alles auswählen

def strip_end(sequence, value):
    for index in range(len(sequence)-1,-1,-1):
        if sequence[index] != value:
            return sequence[0:index+1]
    return []


def main():
    my_list = [
        (1,2,3),
        (4,5,6),
        (0,0,0),
        (7,8,9),
        (0,0,0),
        (0,0,0),
        (0,0,0)    ]

    stripped = strip_end(my_list,(0,0,0))
    print(stripped)
    stripped = strip_end([(0,0,0)],(0,0,0))
    print(stripped)
    stripped = strip_end([],(0,0,0))
    print(stripped)

if __name__ == '__main__':
    main()
Zuletzt geändert von Alfons Mittelmeyer am Sonntag 21. Mai 2017, 09:33, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Alfons:
Sorry, hab meinen Fehler jetzt erkannt. :oops:
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@snafu: und nach was suchst Du mit: stop = sequence.index(value)

Nach dem ersten Vorkommen des Elementes das aber hinten als trailing zu entfernen ist. Deine Lösung funktioniert nicht.
Python wird es ja wohl wissen und zeigt: [(1, 2, 3), (4, 5, 6)]

Das war eine Überschneidung mit Deinem letzten Post. Meiner war vor deinem letzten Post gedacht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Unter Berücksichtigung, dass die ursprüngliche Liste abzuschneiden ist und dass man nur in einem Spezialfall solche trailing Elemente hat, ist wohl doch am Besten:

Code: Alles auswählen

def remove_trailing(sequence, value):
    for index in range(len(sequence)-1,-1,-1):
        if sequence[index] != value:
            break
        else:
            sequence.pop()
 
def main():
    my_list = [
        (1,2,3),
        (4,5,6),
        (0,0,0),
        (7,8,9),
        (0,0,0),
        (0,0,0),
        (0,0,0)    ]

    remove_trailing(my_list,(0,0,0))
    print(my_list)
 
if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Am besten schreibt man solche Funktionen so, dass man jedes iterierbare Objekt übergeben kann.

Code: Alles auswählen

def strip_trailing(sequenze, value):
    result = []
    saved = []
    for element in sequenze:
        saved.append(element)
        if element != value:
            result.extend(saved)
            saved = []
    return result
    
def istrip_trailing(sequenze, value):
    saved = []
    for element in sequenze:
        saved.append(element)
        if element != value:
            for el in saved:
                yield el
            saved = []
So fumktioniert das ganze auch, wenn man Leerzeilen am Ende einer Datei ignorieren will:

Code: Alles auswählen

with open("some_file.txt") as lines:
    for line in istrip_trailing(lines, "\n"):
        print line
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ohne explizite Schleife ist die Aufgabe mit dem Mitteln von Python wohl nicht sinnvoll zu lösen.

Daher hier als Schleife mit reversed(), die zumindest das unpythonische Holen einzelner Elemente über den Index vermeidet:

Code: Alles auswählen

def strip_end(sequence, value):
    for i, item in enumerate(reversed(sequence)):
        if item != value:
            # `i` might be 0, so don't use negative index
            stop = len(sequence) - i
            return sequence[:stop]
    return sequence
 
def main():
    my_list = [
        (1,2,3),
        (4,5,6),
        (0,0,0),
        (7,8,9),
        (0,0,0),
        (0,0,0),
        (0,0,0)
    ]
    stripped = strip_end(my_list, (0, 0, 0))
    print(stripped)
 
if __name__ == '__main__':
    main()
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Hier pillmunchers stabilisierte Variante, ich im Sinne der originalen Frage als die pythonischste sehe (und die zudem ziemlich schnell ist):

Code: Alles auswählen

def strip_end(sequence, value):
    try:
        while sequence[-1] != value:
            sequence.pop()
    except IndexError:
        pass
    return sequence
Alternativ ist 'del sequence[-1]' anstelle von .pop() noch marginal flotter.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@kbr:
Das löst aber leider das Problem nicht. Schau dir mal genauer an, was in Zeile 3 bei dir passiert.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ganze noch etwas schöner, mit groupby

Code: Alles auswählen

from itertools import groupby, chain

def istrip_trailing(sequenze, value):
    saved = []
    for is_value, elements in groupby(sequenze, value.__eq__):
        if is_value:
            saved = list(elements)
        else:
            for element in chain(saved, elements):
                yield element
@kbr: Funktionen, die Listen verändern, sollten diese nicht auch noch als Rückgabewert haben (siehe list.sort vs. sorted)
Antworten