it-swarm.com.de

Beste Möglichkeit, zwei oder mehr Listen in Python zu verschachteln?

Angenommen, ich habe eine Liste:

l=['a','b','c']

Und seine Suffixliste:

l2 = ['a_1', 'b_1', 'c_1']

Ich möchte, dass die gewünschte Ausgabe lautet:

out_l = ['a','a_1','b','b_2','c','c_3']

Das Ergebnis ist die verschachtelte Version der beiden obigen Listen.

Ich kann eine reguläre for-Schleife schreiben, um dies zu erledigen, aber ich frage mich, ob es einen mehr pythonischen Weg gibt (z. B. Listenverstehen oder Lambda), um dies zu erreichen.

Ich habe so etwas ausprobiert:

list(map(lambda x: x[1]+'_'+str(x[0]+1), enumerate(a)))
# this only returns ['a_1', 'b_2', 'c_3']

Welche Änderungen müssten außerdem für den allgemeinen Fall vorgenommen werden, d. H. Für 2 oder mehr Listen, bei denen l2 nicht notwendigerweise eine Ableitung von l ist.

26
user1330974

yield

Sie können einen Generator für eine elegante Lösung verwenden. Bei jeder Iteration ergeben sich zweimal— einmal mit dem ursprünglichen Element und einmal mit dem Element mit dem hinzugefügten Suffix.

Der Generator muss erschöpft sein; Dies kann durch Anlegen eines list-Aufrufs am Ende erreicht werden.

def transform(l):
    for i, x in enumerate(l, 1):
        yield x
        yield f'{x}_{i}'  # {}_{}'.format(x, i)

Sie können dies auch mit der yield from-Syntax für die Delegierung des Generators neu schreiben:

def transform(l):
    for i, x in enumerate(l, 1):
        yield from (x, f'{x}_{i}') # (x, {}_{}'.format(x, i))

out_l = list(transform(l))
print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Wenn Sie ältere Versionen als Python-3.6 verwenden, ersetzen Sie f'{x}_{i}' durch '{}_{}'.format(x, i).

Generalisieren
Stellen Sie sich ein allgemeines Szenario vor, in dem Sie N Listen des Formulars haben:

l1 = [v11, v12, ...]
l2 = [v21, v22, ...]
l3 = [v31, v32, ...]
...

Was Sie gerne verschachteln möchten. Diese Listen sind nicht notwendigerweise voneinander abgeleitet.

Um Interleaving-Operationen mit diesen N-Listen durchzuführen, müssen Sie Paare durchlaufen:

def transformN(*args):
    for vals in Zip(*args):
        yield from vals

out_l = transformN(l1, l2, l3, ...)

In Scheiben geschnitten list.__setitem__

Ich würde dies aus Sicht der Leistung empfehlen. Weisen Sie zunächst Platz für eine leere Liste zu, und weisen Sie dann den Listenpositionen die entsprechenden Positionen zu. l geht in gerade Indizes und l' (l modified) in ungerade Indizes. 

out_l = [None] * (len(l) * 2)
out_l[::2] = l
out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]  # [{}_{}'.format(x, i) ...]

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Dies ist konstant das schnellste von meinen Timings (unten).

Generalisieren
Um N-Listen zu handhaben, weisen Sie die Slices iterativ zu. 

list_of_lists = [l1, l2, ...]

out_l = [None] * len(list_of_lists[0]) * len(list_of_lists)
for i, l in enumerate(list_of_lists):
    out_l[i::2] = l

Zip + chain.from_iterable

Ein funktionaler Ansatz, ähnlich der Lösung von @chrisz. Konstruieren Sie Paare mit Zip und reduzieren Sie sie dann mit itertools.chain.

from itertools import chain
# [{}_{}'.format(x, i) ...]
out_l = list(chain.from_iterable(Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))) 

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

iterools.chain wird weithin als der Pythonic-Listen-Abflachungsansatz angesehen.

Generalisieren
Dies ist die am einfachsten zu verallgemeinernde Lösung, und ich vermute, dass die effizienteste für mehrere Listen ist, wenn N groß ist.

list_of_lists = [l1, l2, ...]
out_l = list(chain.from_iterable(Zip(*list_of_lists)))

Leistung

Schauen wir uns einige Perf-Tests für den einfachen Fall von zwei Listen an (eine Liste mit Suffix). Allgemeine Fälle werden nicht getestet, da die Ergebnisse stark von Daten abweichen.

from timeit import timeit

import pandas as pd
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['ajax1234', 'cs0', 'cs1', 'cs2', 'cs3', 'chrisz', 'sruthiV'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        l = ['a', 'b', 'c', 'd'] * c
        stmt = '{}(l)'.format(f)
        setp = 'from __main__ import l, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=50)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show()

 enter image description here

Funktionen

def ajax1234(l):
    return [
        i for b in [[a, '{}_{}'.format(a, i)] 
        for i, a in enumerate(l, start=1)] 
        for i in b
    ]

def cs0(l):
    # this is in Ajax1234's answer, but it is my suggestion
    return [j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]

def cs1(l):
    def _cs1(l):
        for i, x in enumerate(l, 1):
            yield x
            yield f'{x}_{i}'

    return list(_cs1(l))

def cs2(l):
    out_l = [None] * (len(l) * 2)
    out_l[::2] = l
    out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]

    return out_l

def cs3(l):
    return list(chain.from_iterable(
        Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))
    )

def chrisz(l):
    return [
        val 
        for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) 
        for val in pair
    ]

def sruthiV(l):
    return [ 
        l[int(i / 2)] + "_" + str(int(i / 2) + 1) if i % 2 != 0 else l[int(i/2)] 
        for i in range(0,2*len(l))
    ]

Software

System - Mac OS X High Sierra - 2,4 GHz Intel Core i7
Python - 3.6.0
IPython - 6.2.1 

57
coldspeed

Sie können ein Listenverständnis wie folgt verwenden:

l=['a','b','c']
new_l = [i for b in [[a, '{}_{}'.format(a, i)] for i, a in enumerate(l, start=1)] for i in b]

Ausgabe:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Optionale, kürzere Methode:

[j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]
6
Ajax1234

Sie könnten Zip verwenden:

[val for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) for val in pair]

Ausgabe:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
5
user3483203

Hier ist meine einfache Implementierung

l=['a','b','c']
# generate new list with the indices of the original list
new_list=l + ['{0}_{1}'.format(i, (l.index(i) + 1)) for i in l]
# sort the new list in ascending order
new_list.sort()
print new_list
# Should display ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
2
Isaac Boakye

Hier ist auch ein leichteres Listenverständnis für dieses Problem:

l = ['a', 'b', 'c']
print([ele for index, val in enumerate(l) for ele in (val, val + f'_{index + 1}')])

Ausgabe:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Beachten Sie, dass dies nur eine einfachere Lösung zum Verschachteln der beiden Listen ist. Dies ist keine Lösung für mehrere Listen. Der Grund, warum ich zwei for Schleifen verwende, ist, dass das Listenverständnis zum Zeitpunkt des Schreibens das Entpacken von Tupeln nicht unterstützt.

Wenn Sie [["a","a_1"],["b","b_2"],["c","c_3"]] zurückgeben möchten, können Sie schreiben 

new_l=[[x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)]

Dies ist nicht das, was Sie wollen, sondern ["a","a_1"]+["b","b_2"]+["c","c_3"]. Dies kann aus dem Ergebnis der obigen Operation mit sum() gemacht werden. Da Sie Listen summieren, müssen Sie die leere Liste als Argument hinzufügen, um einen Fehler zu vermeiden. Das gibt also

new_l=sum(([x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)),[])

Ich weiß nicht, wie das mit der Geschwindigkeit verglichen wird (wahrscheinlich nicht gut), aber ich finde es einfacher zu verstehen, was los ist als die anderen Antworten, die auf Listenverständnis basieren.

0
Especially Lime

Eine sehr einfache Lösung:

out_l=[]
for i,x in enumerate(l,1):
    out_l.extend([x,f"{x}_{i}"])
0
kantal