Liste transformieren

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.
Antworten
nezzcarth
User
Beiträge: 1635
Registriert: Samstag 16. April 2011, 12:47

Hallo :)

Der Titel ist leider nicht besonders aussagekräftig, aber mir ist nichts passendes eingefallen.
Jedenfalls:

Ich möchte gerne eine Liste folgendermaßen transformieren:

Code: Alles auswählen

[1, 2, 3, 4, 5, 6]
=>
[6, 1, 5, 2, 4, 3]

[1, 2, 3, 4, 5, 6, 7]
=>
[7, 1, 6, 2, 5, 3, 4]
Im Prinzip wird also immer abwechselnd ein Element von hinten und eines von vorne genommen (oder Alternativ die Liste in der Mitte geteilt, die zweite Hälfte
umgedreht und mit dieser beginnend dann mit der ersten verzahnt; bei ungeraden Liste sollte die erste länger das "Mittelelement enthalten).

Ich habe nun zwei Varianten, die grob die beiden Algoritmen umsetzen:

(Python3)

Code: Alles auswählen

from itertools import cycle, zip_longest
from collections import deque

def iter_sestina_1(l):
    yield l
    while True:
        l = list(item for pair in zip_longest(l[:(len(l)//2)-1:-1], l[:len(l)//2]) for item in pair if item)
        yield l

def iter_sestina_2(l):
    tmp = deque(l)
    yield l
    while True:
        result = []
        try:
            for f in cycle((tmp.pop, tmp.popleft)):
                result.append(f())
        except IndexError:
            yield result
        tmp = deque(result)
Allerdings kommt mir beides recht umständlich vor; gibt es evtl. eine einfachere Lösung?

(Inspiration für die Fragestellung: http://en.wikipedia.org/wiki/Sestina#Form )

Vielen Dank schon mal :)
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

So geht's ohne Schleife und Funktionsdefinition:

Code: Alles auswählen

a = range(1,7)
b = range(1,7)
b[1::2] = a[:len(a)/2:]
b[::2]  = a[:len(a)/2-1:-1]
print(b)

a = range(1,8)
b = range(1,8)
b[1::2] = a[:len(a)/2:]
b[::2]  = a[:len(a)/2-1:-1]
print(b)
Die Erzeugung von b is ein bisschen unschön, in Numpy würde ich

Code: Alles auswählen

b = np.empty((7,), dtype=np.int32)
machen.
a fool with a tool is still a fool, www.magben.de, YouTube
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Es geht noch kürzer (ohne b):

Code: Alles auswählen

a = range(1,7)
a[1::2], a[::2] = a[:len(a)/2:], a[:len(a)/2-1:-1]
print(a)

a = range(1,8)
a[1::2], a[::2] = a[:len(a)/2:], a[:len(a)/2-1:-1]
print(a)
a fool with a tool is still a fool, www.magben.de, YouTube
karolus
User
Beiträge: 141
Registriert: Samstag 22. August 2009, 22:34

Code: Alles auswählen

from itertools import chain, zip_longest
flat = chain.from_iterable
liste = [1,2,3,4,5,6,7]
mitte, r = divmod(len(liste), 2)
first, second = liste[:mitte+r], liste[:mitte-1+r:-1]

out = [elem for elem in flat(zip_longest(second, first ))
       if not elem == None]
Funktioniert für Listen mit gerader und ungerader Länge

Karolus
nezzcarth
User
Beiträge: 1635
Registriert: Samstag 16. April 2011, 12:47

Danke erstmal für die Antworten :)

@MagBen:
In diese Richtung ging mein erster Versuch, allerdings bin ich nicht auf die Ideen gekommen, dass man Slices ja auch etwas zuweisen kann. Ich glaube, da muss ich mich erstmal dran gewöhnen ;)

Um's unter Python 3 zum laufen zu bringen (der Hinweis war evtl. etwas versteckt), musste ich deinen Code außerdem etwas anpassen.

@karolus:
Dein Vorschlag ist denke nah an meinem ersten; chain.from_iterable könnte ich tatsächlich verwenden, um die verschachtelte List-Comprehension loszuwerden. Die Benennung deiner Bezeichner gefällt mir allerdings nicht so gut, das Binden von chain an einen Namen und die explizite Prüfung auf None.

Das sähe dann evt. so aus:
(python3)

Code: Alles auswählen

def iter_sestina_1(l):
    yield l
    while True:
        l = [item for item in chain.from_iterable(zip_longest(l[:(len(l)//2)-1:-1], l[:len(l)//2])) if item]
        yield l
karolus
User
Beiträge: 141
Registriert: Samstag 22. August 2009, 22:34

Hallo
Ich weiss ja nicht was in deinen Listen drinsteckt, deshalb die explizite Prüfung -- deine Lösung würde 0-werte rausfiltern.

Karolus
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@karolus, @nezzcarth: Ihr habt beide einen Sonderfall übersehen: Eure Ansätze funktionieren nicht, wenn sich in der Liste ein ``None`` befindet. bei nezzcarth ist es sogar noch schlimmer. Es werden alle Elemente entfernt, welche nicht zu True ausgewertet werden. Also None, 0, 0.0, eine leere Liste...

Ein Test auf None sollte übrigens nicht mittels == durchgeführt werden sondern mittels ``is None`` bzw. ``is not None``.

@nezzcarth: Was ist das für eine seltsame Funktion in deinem letzten Beitrag? Du wirfst erst die Originalliste raus und dann unendlich oft die veränderte? Das sieht sehr komisch aus.
Das Leben ist wie ein Tennisball.
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Mir fiel ja spontan so etwas ein (Python 3).

Code: Alles auswählen

import itertools


def list_changer(data):
    index = itertools.cycle((-1, 0))
    result = []
    while data:
        result.append(data.pop(next(index)))
    return result

def main():
    data = list(range(1, 7))
    print(list_changer(data))
    data = list(range(1, 8))
    print(list_changer(data))

if __name__ == '__main__':
    main()
BlackJack

@/me: Das `pop()` vom Listenanfang ist von der Laufzeit her sehr ungünstig.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das Verhalten von "list_changer" ist auch etwas seltsam. Klar, der Name sagt, dass die Liste geändert wird, aber am Ende stecken jedes Mal 0 Elemente drin und es wird eine neue Liste zurückgegeben. Etwas unerwartet ;-)
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Mal mit Schleife über den Index:

Code: Alles auswählen

liste = [1, 2, 3, 4, 5, 6]
neu = [liste[(-1)**(i&1)*((i+1)//2)] for i in range(len(liste))]
BlackJack

Oldschool:

Code: Alles auswählen

def funny_iter(sequence):
    i = 0
    j = len(sequence) - 1
    while i <= j:
        yield sequence[j]
        if i == j:
            break
        yield sequence[i]
        i += 1
        j -= 1
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Sirius3 hat geschrieben:

Code: Alles auswählen

neu = [liste[(-1)**(i&1)*((i+1)//2)] for i in range(len(liste))]
Das hatte ich dann so ähnlich.

Code: Alles auswählen

def yield_sestina(data):
    direction = 1
    for i in range(len(data)):
        yield data[(i // 2) * (1, -1)[direction] - direction]
        direction = 1 - direction
Das i & 1 ist natürlich hübsch.
nezzcarth
User
Beiträge: 1635
Registriert: Samstag 16. April 2011, 12:47

Oh je, ich fürchte nun habe ich mich dem Spott ausgesetzt... ;)
EyDu hat geschrieben:@karolus, @nezzcarth: Ihr habt beide einen Sonderfall übersehen: Eure Ansätze funktionieren nicht, wenn sich in der Liste ein ``None`` befindet. bei nezzcarth ist es sogar noch schlimmer. Es werden alle Elemente entfernt, welche nicht zu True ausgewertet werden. Also None, 0, 0.0, eine leere Liste...

Ein Test auf None sollte übrigens nicht mittels == durchgeführt werden sondern mittels ``is None`` bzw. ``is not None``.

@nezzcarth: Was ist das für eine seltsame Funktion in deinem letzten Beitrag? Du wirfst erst die Originalliste raus und dann unendlich oft die veränderte? Das sieht sehr komisch aus.
Ja, das stimmt, die ist sehr merkwürdig. Das kommt daher, dass ich sie von Außen in einer Schleife stets per next() aufgerufen und somit die Terminierung darüber gesteuert habe; das ist natürlich nicht sehr sinnvoll, zugegeben. Da kam dann noch die Überlegung hinzu, dass diese Vertauschung ja inhärent unendlich weiter gehen kann, was ich abbilden wollte.

Bzgl. der Listenelemente sind die genannten Randbedingungen zumindest für die Ausgangsfragestellung, von der ich den Algorithmus habe, egal.
Ursprünglich ging's wie gesagt darum, die Wortabfolgen in Sestinen abzubilden. Die bestehen im Kernteil aus 6 Strophen mit je 6 Versen, wobei die
die jeweils letzten Wörter eines Verses von Strophe zu Strophe nach dem beschriebenen Muster alternieren. Siehe z.B.: http://nddg.de/gedicht/23933-Sestine-R%C3%BCckert.html
An sich finde ich es aber unsauber, wenn es solche Lücken gibt, von daher ist dieser Ansatz wohl eher nicht geeignet.

Nun allerdings noch unentschieden, welcher Ansatz am geeignetesten wäre; mein Versuch oben mit Deque ist vielleicht auch eher unsinnig, auch wenn das Ergebniss stimmt...
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Code: Alles auswählen

from itertools import islice, izip

def iter_switched(sequence):
    pairs = izip(reversed(sequence), sequence)
    for pair in islice(pairs, len(sequence) / 2):
        yield pair[0]
        yield pair[1]
    if len(sequence) % 2 != 0:
        yield next(pairs)[0]
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

/me hat geschrieben:Das i & 1 ist natürlich hübsch.
Das ist nichts anderes als i%2 ;-)

Und natürlich noch die offensichtlichen Lösungen:

Code: Alles auswählen

zip(*reduce(lambda x, _: x+[x[-1][1:][::-1]], data, [data[::-1]])[:-1])[0]

Code: Alles auswählen

(lambda l: (lambda f, l: f(f, l))(lambda f, (l, c): f(f, (l[:-1][::-1], c+[l[-1]])) if l else c, (l, [])) )(data)
Das Leben ist wie ein Tennisball.
Antworten