python - كيفية جعل قائمة مسطحة من قائمة القوائم؟




list multidimensional-array (20)

أتساءل عما إذا كان هناك اختصار لجعل قائمة بسيطة للخروج من قائمة القوائم في بايثون.

أستطيع أن أفعل ذلك في حلقة ، ولكن ربما هناك بعض "واحد" الخطوط الباردة؟ لقد حاولت ذلك مع تقليل ، لكنني حصلت على خطأ.

الشفرة

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

رسالة خطأ

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

إذا كنت تريد تسوية بنية البيانات حيث لا تعرف مدى عمق تداخلها ، يمكنك استخدام iteration_utilities.deepflatten 1

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

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

إنه مولد لذا تحتاج إلى إرسال النتيجة إلى list أو التكرار الواضح عليها.

لتسطيح مستوى واحد فقط وإذا كان كل من العناصر نفسها قابلة للتكرار ، فيمكنك أيضًا استخدام iteration_utilities.flatten وهو عبارة عن غلاف itertools.chain.from_iterable حول itertools.chain.from_iterable :

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

فقط لإضافة بعض التوقيتات (استنادًا إلى إجابة نيكو شلومر التي لم تتضمن الوظيفة المقدمة في هذه الإجابة):

انها مؤامرة سجل السجل لاستيعاب لمجموعة واسعة من القيم الممتدة. للتفكير النوعي: أقل أفضل.

تظهر النتائج أنه إذا احتوى التكرار على عدد قليل فقط من iteration_utilities.deepflatten الداخلية ، فسيكون sum أسرع ، ولكن بالنسبة itertools.chain.from_iterable ، فإن itertools.chain.from_iterable أو iteration_utilities.deepflatten أو الفهم المتداخل يكون ذو أداء معقول مع itertools.chain.from_iterable كونه الأسرع (كما لاحظت بالفعل من قبل نيكو شلومر).

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

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

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1 إخلاء المسؤولية: أنا مؤلف هذه المكتبة


إذا كنت على استعداد للتخلي عن كمية ضئيلة من السرعة للحصول على مظهر أنظف ، فيمكنك استخدام numpy.concatenate().tolist() أو numpy.concatenate().ravel().tolist() :

import numpy

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 99

%timeit numpy.concatenate(l).ravel().tolist()
1000 loops, best of 3: 313 µs per loop

%timeit numpy.concatenate(l).tolist()
1000 loops, best of 3: 312 µs per loop

%timeit [item for sublist in l for item in sublist]
1000 loops, best of 3: 31.5 µs per loop

يمكنك معرفة المزيد هنا في المستندات numpy.concatenate و numpy.ravel


الميزة السيئة لوظيفة Anil المذكورة أعلاه هي أنها تتطلب من المستخدم أن يقوم دائمًا بتحديد الوسيطة الثانية لتكون قائمة فارغة [] . هذا يجب أن يكون بدلا من الافتراضي. نظرًا للطريقة التي تعمل بها كائنات Python ، يجب تعيينها داخل الدالة وليس في الوسيطات.

وهنا وظيفة العمل:

def list_flatten(l, a=None):
    #check a
    if a is None:
        #initialize with empty list
        a = []

    for i in l:
        if isinstance(i, list):
            list_flatten(i, a)
        else:
            a.append(i)
    return a

اختبارات:

In [2]: lst = [1, 2, [3], [[4]],[5,[6]]]

In [3]: lst
Out[3]: [1, 2, [3], [[4]], [5, [6]]]

In [11]: list_flatten(lst)
Out[11]: [1, 2, 3, 4, 5, 6]

رمز بسيط للمروحة حزمة underscore.py

from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

يحل جميع مشاكل المسطح (لا يوجد عنصر قائمة أو تعشيق معقد)

from underscore import _
# 1 is none list item
# [2, [3]] is complex nesting
_.flatten([1, [2, [3]], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

يمكنك تثبيت underscore.py مع نقطة

pip install underscore.py

فكر في تثبيت حزمة more_itertools .

> pip install more_itertools

انها تأتي مع تنفيذ flatten ( source ، من وصفات itertools ):

import more_itertools


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

اعتبارا من الإصدار 2.4 ، يمكنك تسطيح iterables أكثر تعقيدا ، متداخلة مع more_itertools.collapse ( source ، ساهمت بها abarnet).

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.collapse(lst)) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

lst = [[1, 2, 3], [[4, 5, 6]], [[[7]]], 8, 9]              # complex nesting
list(more_itertools.collapse(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

في ما يلي مقاربة عامة تنطبق على الأرقام والسلاسل والقوائم المتداخلة والحاويات المختلطة .

الشفرة

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

ملاحظة: في بايثون 3 ، يمكن أن يستبدل for sub_x in flatten(x): yield sub_x

عرض

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']

مرجع

  • يتم تعديل هذا الحل من وصفة في بيزلي ، د. وباء جونز. Recipe 4.14، Python Cookbook 3rd Ed.، O'Reilly Media Inc. Sebastopol، CA: 2013.
  • العثور على آخر SO SO ، وربما العرض الأصلي.

لم تنجح الإجابة المقبولة عند التعامل مع القوائم النصية ذات الأطوال المتغيرة. هنا مقاربة بديلة عملت لي.

l = ['aaa', 'bb', 'cccccc', ['xx', 'yyyyyyy']]

الإجابة المقبولة التي لم تنجح:

flat_list = [item for sublist in l for item in sublist]
print(flat_list)
['a', 'a', 'a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'xx', 'yyyyyyy']

الحل المقترح الجديد الذي عمل بالنسبة لي:

flat_list = []
_ = [flat_list.extend(item) if isinstance(item, list) else flat_list.append(item) for item in l if item]
print(flat_list)
['aaa', 'bb', 'cccccc', 'xx', 'yyyyyyy']

لماذا تستخدم الامتداد؟

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

هذا يجب أن تعمل بشكل جيد.


يمكن للمرء أيضا استخدام flat NumPy:

import numpy as np
list(np.array(l).flat)

تحرير 11/02/2016: يعمل فقط عندما تكون للمسطحات الفرعية أبعاد متطابقة.


يمكنك استخدام itertools.chain() :

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

أو ، على Python> = 2.6 ، استخدم itertools.chain.from_iterable() الذي لا يتطلب تفريغ القائمة:

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

يمكن القول إن هذا النهج أكثر قابلية للقراءة من [item for sublist in l for item in sublist] ويبدو أسرع أيضًا:

[[email protected]]$ 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
[[email protected]]$ 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
[[email protected]]$ 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
[[email protected]]$ 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
[[email protected]]$ python --version
Python 2.7.3

matplotlib.cbook.flatten() مع القوائم المتداخلة حتى إذا كانت تتداخل بشكل أعمق من المثال.

import matplotlib
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
print(list(matplotlib.cbook.flatten(l)))
l2 = [[1, 2, 3], [4, 5, 6], [7], [8, [9, 10, [11, 12, [13]]]]]
print list(matplotlib.cbook.flatten(l2))

نتيجة:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

هذا هو 18x أسرع من تسطير أسفل السطر ._.

Average time over 1000 trials of matplotlib.cbook.flatten: 2.55e-05 sec
Average time over 1000 trials of underscore._.flatten: 4.63e-04 sec
(time for underscore._)/(time for matplotlib.cbook) = 18.1233394636

أنا أعود بياني مرة أخرى. المبلغ ليس هو الفائز. على الرغم من أنها أسرع عندما تكون القائمة صغيرة. لكن الأداء يتحلل بشكل كبير مع قوائم أكبر.

>>> 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

ما زال الإصدار الإجمالي يعمل لأكثر من دقيقة ولم يتم معالجته بعد!

للقوائم المتوسطة:

>>> 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

استخدام القوائم الصغيرة والوقت: number = 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

ملاحظة : تنطبق أدناه على Python 3.3 والإصدارات الأحدث لأنها تستخدم yield_from. sixهو أيضا حزمة طرف ثالث ، على الرغم من أنها مستقرة. بالتناوب ، يمكنك استخدامها sys.version.

في حالة obj = [[1, 2,], [3, 4], [5, 6]]، جميع الحلول هنا جيدة ، بما في ذلك فهم القائمة و itertools.chain.from_iterable.

ومع ذلك ، فكر في هذه الحالة الأكثر تعقيدًا بعض الشيء:

>>> obj = [[1, 2, 3], [4, 5], 6, 'abc', [7], [8, [9, 10]]]

هناك العديد من المشاكل هنا:

  • عنصر واحد ، 6، هو مجرد عددي. انها ليست متكررة ، لذلك سوف تفشل الطرق المذكورة أعلاه هنا.
  • عنصر واحد ، 'abc'، من الممكن تقنيًا (كل strs). ومع ذلك ، القراءة بين السطور قليلا ، لا تريد أن تعاملها على هذا النحو - تريد التعامل معها كعنصر واحد.
  • العنصر الأخير ، [8, [9, 10]]هو بحد ذاته متداخل متداخل. فهم القائمة الأساسية chain.from_iterableواستخراج فقط "1 مستوى أسفل."

يمكنك علاج هذا كما يلي:

>>> from collections import Iterable
>>> from six import string_types

>>> def flatten(obj):
...     for i in obj:
...         if isinstance(i, Iterable) and not isinstance(i, string_types):
...             yield from flatten(i)
...         else:
...             yield i


>>> list(flatten(obj))
[1, 2, 3, 4, 5, 6, 'abc', 7, 8, 9, 10]

هنا ، تتحقق من أن العنصر الفرعي (1) متوافق مع Iterable، ABC من itertools، ولكنك تريد أيضًا التأكد من أن (2) العنصر ليس "شبيهًا بالسلسلة".


الحل الأسرع لقد وجدت (لقائمة كبيرة على أي حال):

import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()

فعله! يمكنك بالطبع إعادته إلى قائمة من خلال قائمة التنفيذ (ل)


مقاربة أخرى غير معتادة تعمل من أجل قوائم غير متجانسة ومتجانسة من الأعداد الصحيحة:

from typing import List


def flatten(l: list) -> List[int]:
    """Flatten an arbitrary deep nested list of lists of integers.

    Examples:
        >>> flatten([1, 2, [1, [10]]])
        [1, 2, 1, 10]

    Args:
        l: Union[l, Union[int, List[int]]

    Returns:
        Flatted list of integer
    """
    return [int(i.strip('[ ]')) for i in str(l).split(',')]

يمكنك تجنب مكالمات متكررة إلى بنية تخزين العناصر باستخدام بنية بيانات مكدس الفعلية ببساطة.

alist = [1,[1,2],[1,2,[4,5,6],3, "33"]]
newlist = []

while len(alist) > 0 :
  templist = alist.pop()
  if type(templist) == type(list()) :
    while len(templist) > 0 :
      temp = templist.pop()
      if type(temp) == type(list()) :
        for x in temp :
          templist.append(x)
      else :
        newlist.append(temp)
  else :
    newlist.append(templist)
print(list(reversed(newlist)))

وهناك طريقة بسيطة باستخدام العودية reduceمن functoolsو addالمشغل على القوائم:

>>> from functools import reduce
>>> from operator import add
>>> flatten = lambda lst: [lst] if type(lst) is int else reduce(add, [flatten(ele) for ele in lst])
>>> flatten(l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

flattenتأخذ الدالة lstكمعلمة. ومن حلقات جميع عناصر lstحتى الأعداد الصحيحة الوصول (يمكن أيضا تغيير intل float، strوغيرها لأنواع البيانات الأخرى)، والتي تضاف إلى قيمة الإرجاع العودية الأبعد.

التكرار ، على عكس أساليب مثل forالحلقات و monads ، هو أنه حل عام لا يقتصر على عمق القائمة . على سبيل المثال ، يمكن تسوية قائمة بعمق 5 بنفس الطريقة l:

>>> l2 = [[3, [1, 2], [[[6], 5], 4, 0], 7, [[8]], [9, 10]]]
>>> flatten(l2)
[3, 1, 2, 6, 5, 4, 0, 7, 8, 9, 10]

def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])

flat_list = []
for i in list_of_list:
    flat_list+=i

تعمل هذه المدونة أيضًا بشكل جيد لأنها تمد القائمة على طول الطريق. على الرغم من أنها متشابهة إلى حد كبير ، إلا أنها تحتوي على حلقة واحدة فقط. لذلك يكون أقل تعقيدًا من إضافة 2 للحلقات.


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

مما يعني:

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

أسرع من الاختصارات المنشورة حتى الآن. ( l هي قائمة لتسطيح.)

هنا وظيفة المقابلة:

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

للحصول على الأدلة ، كما هو الحال دائمًا ، يمكنك استخدام وحدة timeit في المكتبة القياسية:

$ 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

Explanation: الاختصارات التي تعتمد على + (بما في ذلك الاستخدام الضمني في sum ) هي ، بالضرورة ، O(L**2) عندما تكون هناك قوائم فرعية L - حيث تستمر قائمة النتائج الوسيطة في الحصول على وقت أطول ، في كل خطوة تكون نتيجة وسيطة جديدة يتم تخصيص كائن القائمة ، ويجب نسخ جميع العناصر في النتيجة الوسيطة السابقة (بالإضافة إلى بعض العناصر الجديدة المضافة في النهاية). لذلك (من أجل البساطة وبدون خسارة عمومية فعلية) لنفترض أن لديك قوائم فرعية من العناصر I لكل منها: يتم نسخ عناصر I أولاً إلى الأمام والخلف L-1 ، والعناصر الثانية L-2 ، وهكذا ؛ العدد الإجمالي للنسخ هو I مجموع مجموع x لـ x من 1 إلى L مستثنى ، بمعنى I * (L**2)/2 .

يقوم فهم القائمة فقط بإنشاء قائمة واحدة ، مرة واحدة ، ونسخ كل عنصر (من مكان إقامته الأصلي إلى قائمة النتائج) مرة واحدة بالضبط.







flatten