Vraag Een platte lijst maken uit de lijst met lijsten in Python


Ik vraag me af of er een snelkoppeling is om een ​​eenvoudige lijst uit de lijst met lijsten in Python te maken.

Ik kan dat doen in een for-lus, maar misschien is er een cool 'one-liner'? Ik heb het geprobeerd verminderen, maar ik krijg een foutmelding.

Code

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)

Foutmelding

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

2069
2018-06-04 20:30


oorsprong


antwoorden:


flat_list = [item for sublist in l for item in sublist]

wat betekent:

for sublist in l:
    for item in sublist:
        flat_list.append(item)

is sneller dan de snelkoppelingen die tot nu toe zijn gepost. (l is de lijst die moet worden afgevlakt.)

Hier is een bijbehorende functie:

flatten = lambda l: [item for sublist in l for item in sublist]

Voor bewijs, zoals altijd, kunt u de timeit module in de standaardbibliotheek:

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop

Uitleg: de snelkoppelingen op basis van + (inclusief het impliciete gebruik in sum) zijn noodzakelijkerwijs O(L**2) wanneer er L-sublijsten zijn - omdat de tussenresultaatlijst steeds langer wordt, wordt bij elke stap een nieuw tussenresultaatlijstobject toegewezen en moeten alle items in het vorige tussenresultaat worden gekopieerd (evenals enkele nieuwe toegevoegd) aan het einde). Dus (voor eenvoud en zonder feitelijk verlies van algemeenheid) zeg je dat je L sublijsten van I-items hebt: de eerste I-items worden L-1 keer heen en weer gekopieerd, de tweede I-items L-2 keer, enzovoort; totaal aantal kopieën is I maal de som van x voor x van 1 tot L uitgesloten, d.w.z. I * (L**2)/2.

Het begrip van de lijst genereert één lijst, eenmaal, en kopieert elk item (van zijn oorspronkelijke woonplaats naar de resultatenlijst) ook precies één keer.


2984
2018-06-04 20:37



Je kunt gebruiken itertools.chain():

>>> import itertools
>>> list2d = [[1,2,3],[4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))

of, op Python> = 2.6, gebruiken itertools.chain.from_iterable() waarvoor de lijst niet hoeft te worden uitgepakt:

>>> import itertools
>>> list2d = [[1,2,3],[4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain.from_iterable(list2d))

Deze benadering is aantoonbaar beter leesbaar dan [item for sublist in l for item in sublist] en lijkt ook sneller te zijn:

[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
10000 loops, best of 3: 24.2 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 45.2 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 488 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 522 usec per loop
[me@home]$ python --version
Python 2.7.3

1079
2018-06-04 21:06



Opmerking van de auteur: Dit is inefficiënt. Maar leuk, want monaden zijn geweldig. Het is niet geschikt voor productie Python-code.

>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Dit somt alleen de elementen van iterable die in het eerste argument zijn doorgegeven, waarbij het tweede argument wordt behandeld als de beginwaarde van de som (indien niet gegeven, 0 wordt gebruikt en deze case geeft een foutmelding).

Omdat u geneste lijsten optelt, wordt u feitelijk [1,3]+[2,4] als gevolg van sum([[1,3],[2,4]],[]), wat gelijk is aan [1,3,2,4].

Let op: werkt alleen in lijsten met lijsten. Voor lijsten met lijsten met lijsten heeft u een andere oplossing nodig.


636
2018-06-04 20:35



Ik heb de meeste voorgestelde oplossingen getest met perfplot (een lievelingsproject van mij, in wezen een wikkel rond timeit), en gevonden

list(itertools.chain.from_iterable(a))

om de snelste oplossing te zijn (als er meer dan 10 lijsten aaneengeschakeld zijn).

enter image description here


Code om de plot te reproduceren:

import functools
import itertools
import numpy
import operator
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def numpy_concatenate(a):
    return list(numpy.concatenate(a))


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    kernels=[
        forfor, sum_brackets, functools_reduce, itertools_chain, numpy_flat,
        numpy_concatenate
        ],
    n_range=[2**k for k in range(16)],
    logx=True,
    logy=True,
    xlabel='num lists'
    )

129
2017-07-26 09:38



from functools import reduce #python 3

>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(lambda x,y: x+y,l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

De extend() methode in uw voorbeeld wijzigt x in plaats van een bruikbare waarde terug te geven (welke reduce() verwacht).

Een snellere manier om het te doen reduce versie zou zijn

>>> import operator
>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

99
2018-06-04 20:35



Hier is een algemene benadering die van toepassing is getallen, strings, genesteld lijsten en gemengd containers.

Code

from collections import Iterable


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

Opmerking: in Python 3, yield from flatten(x) kan vervangen for sub_x in flatten(x): yield sub_x

demonstratie

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(lst))                                         # nested lists
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

mixed = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(mixed))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

Referentie

  • Deze oplossing is aangepast van een recept in Beazley, D. en B. Jones. Recept 4.14, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Eerder gevonden ZO bericht, mogelijk de originele demonstratie.

54
2017-11-29 04:14



Ik neem mijn verklaring terug. som is niet de winnaar. Hoewel het sneller is wanneer de lijst klein is. Maar de prestaties nemen aanzienlijk af bij grotere lijsten. 

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
    ).timeit(100)
2.0440959930419922

De sum-versie draait nog steeds langer dan een minuut en het heeft nog geen verwerking voltooid!

Voor middelgrote lijsten:

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
20.126545906066895
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
22.242258071899414
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
16.449732065200806

Gebruik van kleine lijsten en tijd: nummer = 1000000

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
2.4598159790039062
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.5289170742034912
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.0598428249359131

31
2018-06-04 20:46



Waarom gebruik je verlengen?

reduce(lambda x, y: x+y, l)

Dit zou goed moeten werken.


25
2018-06-04 20:38