Reduzieren einer flachen Liste in Python [duplicate]
Lesezeit: 12 Minuten
Gibt es eine einfache Möglichkeit, eine Liste von Iterables mit einem Listenverständnis zu glätten, oder was würden Sie alle als den besten Weg betrachten, um eine flache Liste wie diese zu glätten und Leistung und Lesbarkeit in Einklang zu bringen?
Ich habe versucht, eine solche Liste mit einem verschachtelten Listenverständnis wie folgt zu glätten:
[image for image in menuitem for menuitem in list_of_menuitems]
Aber ich bekomme Ärger von der NameError Abwechslung da, denn die name 'menuitem' is not defined. Nachdem ich gegoogelt und mich auf Stack Overflow umgesehen hatte, bekam ich die gewünschten Ergebnisse mit a reduce Aussage:
Aber diese Methode ist ziemlich unlesbar, weil ich das brauche list(x) rufen Sie dort auf, weil x ein Django ist QuerySet Objekt.
Fazit:
Danke an alle, die zu dieser Frage beigetragen haben. Hier ist eine Zusammenfassung dessen, was ich gelernt habe. Ich mache dies auch zu einem Community-Wiki, falls andere diese Beobachtungen ergänzen oder korrigieren möchten.
Meine ursprüngliche Anweisung zum Reduzieren ist überflüssig und wird besser so geschrieben:
>>> reduce(list.__add__, (list(mi) for mi in list_of_menuitems))
Dies ist die korrekte Syntax für ein verschachteltes Listenverständnis (Brillante Zusammenfassung dF!):
>>> [image for mi in list_of_menuitems for image in mi]
Aber keine dieser Methoden ist so effizient wie die Verwendung itertools.chain:
>>> from itertools import chain
>>> list(chain(*list_of_menuitems))
Und wie @cdleary anmerkt, ist es wahrscheinlich besser, *-Operatormagie durch Verwendung zu vermeiden chain.from_iterable so:
Ich verstehe nicht, warum jeder map(lambda x: list(x), other) verwendet – ist das nicht gleichbedeutend mit map(list, other)? Die eingebaute Liste ist aufrufbar …
– klar
2. Januar 2009 um 19:14 Uhr
Es ist gleichwertig. Glücklicherweise erkannte Prairie Dogg, dass dieser Code hässlich ist. 🙂
– rekursiv
2. Januar 2009 um 19:33 Uhr
@recursive: Ja, ich bin definitiv rot geworden, nachdem du darauf hingewiesen hast, wie viele Dinge an meiner Reduce-Anweisung überflüssig sind. Ich habe definitiv viel aus dieser Frage gelernt, also vielen Dank an alle!
– Präriedogg
2. Januar 2009 um 23:29 Uhr
Reduce(list.__add__, (list(mi.image_set.all()) for mi in list_of_menuitems)) ist nicht korrekt für den Fall, dass alle Listen leer sind. Es sollte Reduce(list.__add__, (list(mi.image_set.all()) for mi in list_of_menuitems) sein, [])
– Daira Hopwood
10. Juni 2012 um 11:59 Uhr
Diese Frage hat stackoverflow.com/q/952914/1206998 als doppelt geschlossen geschlossen. Allerdings ist es aufgrund all der irrelevanten Django-Sachen viel weniger klar. Soll es neu geschrieben werden?
– Juh_
17. Januar 2014 um 13:55 Uhr
klar
Wenn Sie nur über eine abgeflachte Version der Datenstruktur iterieren möchten und keine indizierbare Sequenz benötigen, sollten Sie dies in Betracht ziehen itertools.chain und Unternehmen.
Es funktioniert mit allem, was iterierbar ist, einschließlich Djangos iterable QuerySets, die Sie anscheinend in der Frage verwenden.
Bearbeiten: Dies ist wahrscheinlich sowieso so gut wie ein Reduzieren, da Reduzieren den gleichen Overhead haben wird, wenn die Elemente in die Liste kopiert werden, die erweitert wird. chain wird nur diesen (gleichen) Overhead verursachen, wenn Sie laufen list(chain) Am Ende.
Meta-Edit: Tatsächlich ist es weniger Overhead als die vorgeschlagene Lösung der Frage, weil Sie die temporären Listen wegwerfen, die Sie erstellen, wenn Sie das Original mit dem temporären erweitern.
Bearbeiten: Wie JF Sebastian sagt itertools.chain.from_iterable vermeidet das Auspacken und das sollten Sie vermeiden * Magic, aber die Timeit-App zeigt einen vernachlässigbaren Leistungsunterschied.
Eine explizite Schleife, die verwendet .extend Methode ist nach diesem Benchmark die schnellste Lösung
– jfs
30. April 2014 um 2:02 Uhr
hatte nichts von from_iterable gehört. es ist hübscher als das *, wenn auch weniger pythonisch
– Jules GM
29. März 2016 um 22:04 Uhr
Es lohnt sich auch, das hervorzuheben, weil from_iterable Entpacken vermeidet, kann es Probleme vermeiden, bei denen Sie viele (potenziell unbegrenzte) Elemente in der Iterable haben. Wenn das Iterable lang genug ist, wird Ihnen der Arbeitsspeicher ausgehen.
for inner_list in outer_list:
for item in inner_list:
...
entspricht
[... for inner_list in outer_list for item in inner_list]
Also du möchtest
[image for menuitem in list_of_menuitems for image in menuitem]
+1, ich habe das so oft nachgeschlagen und dies ist die einzige Antwort, die ich gesehen habe, die die Bestellung explizit gemacht hat … Vielleicht kann ich mich jetzt daran erinnern!
– Iskata
20. März 2012 um 15:47 Uhr
Ich wünschte, ich könnte dies noch einmal positiv bewerten, da diese Denkweise das Verständnis von verschachtelten Listen viel einfacher macht.
– Derek Litz
17. Juni 2013 um 21:36 Uhr
wohingegen [… for item in inner_list for inner_list in outer_list] ist ein Python-Gotcha: es wiederholt sich nur [... for item in inner_list] auf dem letzten Wert von inner_list und so oft wie len(outer_list). Nicht zu gebrauchen.
– smci
14. September 2013 um 4:47 Uhr
Diese Reihenfolge ist Ja wirklich seltsam. Wenn Sie sich ändern for i in list: ... zu ... for i in listwarum würdest du dann nicht auch die Reihenfolge der for-Schleifen ändern?
– nichts101
26. November 2013 um 6:38 Uhr
Ha! Ich habe es wieder vergessen. Ich schätze, Guidos Gehirn und meins sind sich einfach nicht einig darüber, was intuitiv ist.
– klack
27. Juni 2016 um 13:33 Uhr
@S.Lott: Du hast mich dazu inspiriert, eine timeit-App zu schreiben.
Ich dachte, es würde auch basierend auf der Anzahl der Partitionen (Anzahl der Iteratoren innerhalb der Containerliste) variieren – Ihr Kommentar erwähnte nicht, wie viele Partitionen es von den dreißig Elementen gab. Dieser Plot flacht in jedem Durchlauf tausend Elemente mit unterschiedlicher Anzahl von Partitionen ab. Die Artikel werden gleichmäßig auf die Partitionen verteilt.
Code (Python 2.6):
#!/usr/bin/env python2.6
"""Usage: %prog item_count"""
from __future__ import print_function
import collections
import itertools
import operator
from timeit import Timer
import sys
import matplotlib.pyplot as pyplot
def itertools_flatten(iter_lst):
return list(itertools.chain(*iter_lst))
def itertools_iterable_flatten(iter_iter):
return list(itertools.chain.from_iterable(iter_iter))
def reduce_flatten(iter_lst):
return reduce(operator.add, map(list, iter_lst))
def reduce_lambda_flatten(iter_lst):
return reduce(operator.add, map(lambda x: list(x), [i for i in iter_lst]))
def comprehension_flatten(iter_lst):
return list(item for iter_ in iter_lst for item in iter_)
METHODS = ['itertools', 'itertools_iterable', 'reduce', 'reduce_lambda',
'comprehension']
def _time_test_assert(iter_lst):
"""Make sure all methods produce an equivalent value.
:raise AssertionError: On any non-equivalent value."""
callables = (globals()[method + '_flatten'] for method in METHODS)
results = [callable(iter_lst) for callable in callables]
if not all(result == results[0] for result in results[1:]):
raise AssertionError
def time_test(partition_count, item_count_per_partition, test_count=10000):
"""Run flatten methods on a list of :param:`partition_count` iterables.
Normalize results over :param:`test_count` runs.
:return: Mapping from method to (normalized) microseconds per pass.
"""
iter_lst = [[dict()] * item_count_per_partition] * partition_count
print('Partition count: ', partition_count)
print('Items per partition:', item_count_per_partition)
_time_test_assert(iter_lst)
test_str="flatten(%r)" % iter_lst
result_by_method = {}
for method in METHODS:
setup_str="from test import %s_flatten as flatten" % method
t = Timer(test_str, setup_str)
per_pass = test_count * t.timeit(number=test_count) / test_count
print('%20s: %.2f usec/pass' % (method, per_pass))
result_by_method[method] = per_pass
return result_by_method
if __name__ == '__main__':
if len(sys.argv) != 2:
raise ValueError('Need a number of items to flatten')
item_count = int(sys.argv[1])
partition_counts = []
pass_times_by_method = collections.defaultdict(list)
for partition_count in xrange(1, item_count):
if item_count % partition_count != 0:
continue
items_per_partition = item_count / partition_count
result_by_method = time_test(partition_count, items_per_partition)
partition_counts.append(partition_count)
for method, result in result_by_method.iteritems():
pass_times_by_method[method].append(result)
for method, pass_times in pass_times_by_method.iteritems():
pyplot.plot(partition_counts, pass_times, label=method)
pyplot.legend()
pyplot.title('Flattening Comparison for %d Items' % item_count)
pyplot.xlabel('Number of Partitions')
pyplot.ylabel('Microseconds')
pyplot.show()
Bearbeiten: Beschlossen, daraus ein Community-Wiki zu machen.
Notiz:METHODS sollte wahrscheinlich mit einem Dekorateur gesammelt werden, aber ich denke, es wäre einfacher für die Leute, auf diese Weise zu lesen.
Ich weiß, dass dies ein alter Thread ist, aber ich habe eine Methode hinzugefügt, die ich von hier erhalten habe und die list.extend verwendet, die sich als die schnellste auf ganzer Linie erwiesen hat. Graphaktualisierter Kern
– Mike S
5. April 2013 um 2:04 Uhr
sum(list_of_lists, []) würde es platt machen.
l = [['image00', 'image01'], ['image10'], []]
print sum(l,[]) # prints ['image00', 'image01', 'image10']
James Brady
Diese Lösung funktioniert für beliebige Verschachtelungstiefen – nicht nur für die Tiefe der “Liste der Listen”, auf die einige (alle?) der anderen Lösungen beschränkt sind:
def flatten(x):
result = []
for el in x:
if hasattr(el, "__iter__") and not isinstance(el, basestring):
result.extend(flatten(el))
else:
result.append(el)
return result
Es ist die Rekursion, die eine beliebige Tiefenverschachtelung ermöglicht – natürlich bis Sie die maximale Rekursionstiefe erreichen …
Es könnte sich lohnen, hinzuzufügen hasattr(el, '__getitem__') für Kompatibilität mit iter() Funktion und eingebaute for-in-Schleife (obwohl alle Python-Sequenzen (Objekte mit __getitem__) sind auch iterierbar (Objekt mit __iter__)).
– jfs
14. März 2009 um 10:56 Uhr
Ich hatte so etwas bereits in itertools erwartet. gibt es ähnliche lösungen mit verständnissen?
– Josep Valls
18. Juni 2012 um 14:07 Uhr
Dies war für mich am nützlichsten, da es keine Saiten trennt.
@JosepValls, könnten Sie auch sagen, warum eine ähnliche Methode wie Ihre eine ergibt RECURSION ERROR ON Eingang A = ['str1', [[[['str2']]]], [['str3'], 'str4'], 'str5'] and input A = [1.0, 2, ‘a’, (4,), ((6,), (8,)), (((8,),(9,)), ((12,),(10)))]`, aber funktioniert gut mit Ihrer Lösung!
>>> from itertools import chain
>>> list(chain.from_iterable(mi.image_set.all() for mi in h.get_image_menu()))
Es vermeidet die Erstellung einer Zwischenliste.
Es könnte sich lohnen, hinzuzufügen hasattr(el, '__getitem__') für Kompatibilität mit iter() Funktion und eingebaute for-in-Schleife (obwohl alle Python-Sequenzen (Objekte mit __getitem__) sind auch iterierbar (Objekt mit __iter__)).
– jfs
14. März 2009 um 10:56 Uhr
Ich hatte so etwas bereits in itertools erwartet. gibt es ähnliche lösungen mit verständnissen?
– Josep Valls
18. Juni 2012 um 14:07 Uhr
Dies war für mich am nützlichsten, da es keine Saiten trennt.
@JosepValls, könnten Sie auch sagen, warum eine ähnliche Methode wie Ihre eine ergibt RECURSION ERROR ON Eingang A = ['str1', [[[['str2']]]], [['str3'], 'str4'], 'str5'] and input A = [1.0, 2, ‘a’, (4,), ((6,), (8,)), (((8,),(9,)), ((12,),(10)))]`, aber funktioniert gut mit Ihrer Lösung!
– Anu
12. Januar 2019 um 19:51 Uhr
Leistungsergebnisse. Überarbeitet.
import itertools
def itertools_flatten( aList ):
return list( itertools.chain(*aList) )
from operator import add
def reduce_flatten1( aList ):
return reduce(add, map(lambda x: list(x), [mi for mi in aList]))
def reduce_flatten2( aList ):
return reduce(list.__add__, map(list, aList))
def comprehension_flatten( aList ):
return list(y for x in aList for y in x)
Ich habe eine zweistufige Liste mit 30 Elementen 1000 Mal reduziert
Ich verstehe nicht, warum jeder map(lambda x: list(x), other) verwendet – ist das nicht gleichbedeutend mit map(list, other)? Die eingebaute Liste ist aufrufbar …
– klar
2. Januar 2009 um 19:14 Uhr
Es ist gleichwertig. Glücklicherweise erkannte Prairie Dogg, dass dieser Code hässlich ist. 🙂
– rekursiv
2. Januar 2009 um 19:33 Uhr
@recursive: Ja, ich bin definitiv rot geworden, nachdem du darauf hingewiesen hast, wie viele Dinge an meiner Reduce-Anweisung überflüssig sind. Ich habe definitiv viel aus dieser Frage gelernt, also vielen Dank an alle!
– Präriedogg
2. Januar 2009 um 23:29 Uhr
Reduce(list.__add__, (list(mi.image_set.all()) for mi in list_of_menuitems)) ist nicht korrekt für den Fall, dass alle Listen leer sind. Es sollte Reduce(list.__add__, (list(mi.image_set.all()) for mi in list_of_menuitems) sein, [])
– Daira Hopwood
10. Juni 2012 um 11:59 Uhr
Diese Frage hat stackoverflow.com/q/952914/1206998 als doppelt geschlossen geschlossen. Allerdings ist es aufgrund all der irrelevanten Django-Sachen viel weniger klar. Soll es neu geschrieben werden?
– Juh_
17. Januar 2014 um 13:55 Uhr