Сглаживание мелкого списка в Python

Есть ли простой способ сгладить список итераций с помощью понимания списка, или, если это не удается, что вы все считаете лучшим способом сгладить такой неглубокий список, уравновешивая производительность и удобочитаемость?

Я попытался сгладить такой список с помощью вложенного списка, например:

[image for image in menuitem for menuitem in list_of_menuitems]

Но у меня там проблемы с разновидностью NameError, потому что name 'menuitem' is not defined. После поиска в Google и просмотра Stack Overflow я получил желаемые результаты с помощью оператора reduce:

reduce(list.__add__, map(lambda x: list(x), list_of_menuitems))

Но этот метод практически не читается, потому что мне нужен этот вызов list(x), потому что x - это объект Django QuerySet.

Заключение:

Спасибо всем, кто внес свой вклад в этот вопрос. Вот краткое изложение того, что я узнал. Я также делаю это вики сообщества на случай, если другие захотят дополнить или исправить эти наблюдения.

Моя первоначальная инструкция сокращения избыточна и лучше написана так:

>>> reduce(list.__add__, (list(mi) for mi in list_of_menuitems))

Это правильный синтаксис для понимания вложенного списка (блестящее резюме dF!):

>>> [image for mi in list_of_menuitems for image in mi]

Но ни один из этих методов не так эффективен, как использование itertools.chain:

>>> from itertools import chain
>>> list(chain(*list_of_menuitems))

И, как отмечает @cdleary, вероятно, лучше избегать магии оператора *, используя chain.from_iterable следующим образом:

>>> chain = itertools.chain.from_iterable([[1,2],[3],[5,89],[],[6]])
>>> print(list(chain))
>>> [1, 2, 3, 5, 89, 6]

Я не понимаю, почему все используют map (lambda x: list (x), other) - разве это не эквивалентно map (list, other)? Встроенный список вызывается ...

cdleary 02.01.2009 22:14

Это эквивалентно. К счастью, Prairie Dogg осознал, что этот код уродлив. :)

recursive 02.01.2009 22:33

О да, я вижу, что вы указали на это в своем ответе @recursive. Извините за избыточность! :-)

cdleary 02.01.2009 23:56

@recursive: Да, я определенно покраснел после того, как вы указали, сколько вещей в моем операторе сокращения было лишним. Я определенно многому научился из этого вопроса, большое спасибо всем!

prairiedogg 03.01.2009 02:29

Я уже некоторое время изучаю Ruby и только что имел возможность использовать идиому Ruby для аналогичной проблемы. FWIW: [[1,2], [3], [5,89], [], [6]]. Flatten -> [1, 2, 3, 5, 89, 6]

prairiedogg 20.11.2010 19:40

reduce (list .__ add__, (list (mi.image_set.all ()) для mi в list_of_menuitems)) неверно для случая, когда все списки пусты. Его следует уменьшить (list .__ add__, (list (mi.image_set.all ()) для mi в list_of_menuitems), [])

Daira Hopwood 10.06.2012 15:59

Этот вопрос закрыл stackoverflow.com/q/952914/1206998 как дублированный. Однако это гораздо менее понятно из-за всего, что не имеет отношения к django. Следует ли его переписать?

Juh_ 17.01.2014 17:55
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
400
7
186 194
23
Перейти к ответу Данный вопрос помечен как решенный

Ответы 23

Вне всяких сомнений, вы можете исключить лямбду:

reduce(list.__add__, map(list, [mi.image_set.all() for mi in list_of_menuitems]))

Или даже удалите карту, так как у вас уже есть список:

reduce(list.__add__, [list(mi.image_set.all()) for mi in list_of_menuitems])

Вы также можете просто выразить это как сумму списков:

sum([list(mi.image_set.all()) for mi in list_of_menuitems], [])

Вы можете просто использовать add, и я считаю, что второй аргумент суммы излишен.

daniel 02.01.2009 09:30

Это не лишнее. По умолчанию значение равно нулю, что приводит к TypeError: неподдерживаемый тип (ы) операндов для +: 'int' и 'list'. IMO sum () более прямая, чем reduce (add, ...)

recursive 02.01.2009 09:52

Как насчет:

from operator import add
reduce(add, map(lambda x: list(x.image_set.all()), [mi for mi in list_of_menuitems]))

Но Гвидо не рекомендует выполнять слишком много работы в одной строке кода, поскольку это снижает удобочитаемость. Прирост производительности минимальный, если он вообще есть, за счет выполнения желаемого в одной строке по сравнению с несколькими строками.

Это невероятно приятно выполнять какой-то сумасшедший объем работы в одной строке ... но на самом деле это просто синтаксический suger

daniel 02.01.2009 09:37

Если я правильно помню, Гвидо на самом деле также не рекомендует использовать сокращения и понимание списков ... Я не согласен, хотя они невероятно полезны.

daniel 02.01.2009 09:39

Сравните производительность этого маленького самородка с многострочной функцией. Думаю, вы обнаружите, что эта строчка - настоящая собака.

S.Lott 02.01.2009 14:21

возможно, отображение с лямбдами - это ужасно. накладные расходы, возникающие при каждом вызове функции, высасывают жизнь из вашего кода. Я никогда не говорил, что эта конкретная строка была такой же быстрой, как многострочное решение ...;)

daniel 03.01.2009 06:34
Ответ принят как подходящий

Если вы просто хотите перебрать плоскую версию структуры данных и вам не нужна индексируемая последовательность, рассмотрите itertools.chain и компания.

>>> list_of_menuitems = [['image00', 'image01'], ['image10'], []]
>>> import itertools
>>> chain = itertools.chain(*list_of_menuitems)
>>> print(list(chain))
['image00', 'image01', 'image10']

Он будет работать со всем, что можно итерировать, включая повторяющиеся QuerySet от Django, которые, похоже, вы используете в вопросе.

Редактировать: В любом случае это, вероятно, так же хорошо, как и сокращение, потому что сокращение будет иметь такие же накладные расходы при копировании элементов в расширяемый список. chain будет нести эти (те же) накладные расходы, только если вы запустите list(chain) в конце.

Мета-редактирование: На самом деле, это меньше накладных расходов, чем предлагаемое решение вопроса, потому что вы отбрасываете временные списки, которые создаете, когда расширяете оригинал временным.

Редактировать: Поскольку J.F. Себастьян говоритitertools.chain.from_iterable избегает распаковки, вы должны использовать это, чтобы избежать магии *, но приложение timeit показывает незначительную разницу в производительности.

Явный цикл, использующий Метод .extend - самое быстрое решение согласно этому тесту

jfs 30.04.2014 06:02

не слышал от from_iterable. он красивее, чем *, если менее питонический

Jules G.M. 30.03.2016 01:04

Также стоит подчеркнуть, что, поскольку from_iterable избегает распаковки, он может избежать проблем, когда у вас есть много (потенциально неограниченных) элементов в итерируемом объекте. Если итерация достаточно длинная, у вас закончится память.

skeggse 27.02.2018 05:32

Вот правильное решение, использующее понимание списков (они заданы в вопросе):

>>> join = lambda it: (y for x in it for y in x)
>>> list(join([[1,2],[3,4,5],[]]))
[1, 2, 3, 4, 5]

В вашем случае это было бы

[image for menuitem in list_of_menuitems for image in menuitem.image_set.all()]

или вы можете использовать join и сказать

join(menuitem.image_set.all() for menuitem in list_of_menuitems)

В любом случае, проблема заключалась во вложении петель for.

Вы почти получили это! способ сделать понимание вложенных списков предназначен для размещения операторов for в том же порядке, что и в обычных вложенных операторах for.

Таким образом, это

for inner_list in outer_list:
    for item in inner_list:
        ...

соответствует

[... for inner_list in outer_list for item in inner_list]

Так ты хочешь

[image for menuitem in list_of_menuitems for image in menuitem]

+1, я смотрел это так много раз, и это единственный ответ, который я видел, что делает упорядочение явным ... Может быть, теперь я могу это вспомнить!

Izkata 20.03.2012 19:47

Я хотел бы проголосовать за это снова, потому что такой образ мышления значительно упрощает понимание вложенных списков.

Derek Litz 18.06.2013 01:36

тогда как [... for item in inner_list для inner_list в outer_list] - это ошибка Python: он повторяет [... for item in inner_list] только для последнего значения inner_list и столько раз, сколько len (external_list). Бесполезный.

smci 14.09.2013 08:47

Этот порядок В самом деле нечетный. Если вы измените for i in list: ... на ... for i in list, то почему бы вам также не изменить порядок циклов for?

naught101 26.11.2013 10:38

Лучшее объяснение вложенных списков, которые я нашел даже после прочтения документации. Комментирую, чтобы я мог найти это позже.

Rob 29.11.2014 20:43

Да, старый пост, но лучшее простое объяснение вложенных понятий. Спасибо ... Все еще неловко, когда поток возвращается на передний план, но теперь это имеет смысл.

Andrew Backer 05.08.2015 06:09

"в том же порядке, в каком они идут в обычных вложенных операторах for" - это очень ясный способ выразить это. Теперь я не забуду этого снова.

clacke 18.12.2015 14:53

Ха! Я снова забыл об этом. Я полагаю, что мозг Гвидо и мой просто расходятся во мнениях относительно того, что интуитивно понятно.

clacke 27.06.2016 16:33

Результаты производительности. Пересмотрено.

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)

Я выровнял двухуровневый список из 30 пунктов 1000 раз

itertools_flatten     0.00554
comprehension_flatten 0.00815
reduce_flatten2       0.01103
reduce_flatten1       0.01404

Уменьшить - всегда плохой выбор.

map(lambda x: list(x), [mi for mi in aList])) - это map(list, aList).
jfs 02.01.2009 23:33
reduce_flatten = lambda list_of_iters: reduce(list.__add__, map(list, list_of_iters))
jfs 02.01.2009 23:41
itertools_flatten2 = lambda aList: list(itertools.chain.from_iterable(aList))
jfs 04.01.2009 22:40

В 2.5.2 нет chain.from_iterable - извините - не может сравниться с другими решениями.

S.Lott 05.01.2009 03:29

@ рекурсивная версия: sum_flatten = lambda aList: sum(map(list, aList), [])

jfs 08.01.2009 19:31

Это решение работает для произвольной глубины вложенности, а не только для глубины «списка списков», которой некоторые (все?) Другие решения ограничены:

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

Это рекурсия, которая допускает произвольную глубину вложения - до тех пор, пока вы не достигнете максимальной глубины рекурсии, конечно ...

Возможно, стоит добавить hasattr(el, '__getitem__') для совместимости с функцией iter() и встроенным циклом for-in (хотя все последовательности Python (объекты с __getitem__) также являются итеративными (объект с __iter__)).

jfs 14.03.2009 13:56

я ожидал чего-то подобного уже в itertools. есть ли аналогичные решения с использованием понимания?

Josep Valls 18.06.2012 18:07

Это было для меня наиболее полезным, поскольку не разделяет строки.

cdhagmann 28.06.2013 00:49

@JosepValls, также, не могли бы вы сказать, почему аналогичный метод, подобный твоему дает вход RECURSION ERROR ONA = ['str1', [[[['str2']]]], [['str3'], 'str4'], 'str5'] and input A = [1.0, 2, 'a', (4,), ((6,), (8,)), (((8,) , (9,)), ((12,), (10)))] `, но с вашим решением работать нормально!

Anu 12.01.2019 22:51

@ S.Lott: Вы вдохновили меня написать приложение timeit.

Я подумал, что это также будет зависеть от количества разделов (количества итераторов в списке контейнеров) - в вашем комментарии не упоминалось, сколько разделов было из тридцати элементов. Этот сюжет сглаживает тысячу элементов за каждый прогон с различным количеством разделов. Предметы равномерно распределяются по перегородкам.

Flattening Comparison

Код (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()

Редактировать: Решил сделать это вики сообщества.

Примечание:METHODS, вероятно, следует накапливать с помощью декоратора, но я полагаю, что людям будет легче читать таким образом.

Попробуйте sum_flatten = lambda iter_lst: sum(map(list, iter_lst), [])

jfs 08.01.2009 19:34

или просто сумма (список, [])

hoju 01.09.2009 10:32

@EnTerr предложил reduce(operator.iaddstackoverflow.com/questions/3040335/…, который на данный момент является самым быстрым (код: ideone.com/NWThp изображение: i403.photobucket.com/albums/pp111/uber_ulrich/p1000.png)

jfs 16.06.2010 16:43
chain.from_iterable() немного быстрее, если много разделов i403.photobucket.com/albums/pp111/uber_ulrich/p10000.png
jfs 16.06.2010 17:21

Я знаю, что это старый поток, но я добавил метод, полученный от здесь, который использует list.extend, который показал себя самым быстрым по всем направлениям. графикобновленная суть

Mike S 05.04.2013 06:04

В Python 2.6 с использованием chain.from_iterable():

>>> from itertools import chain
>>> list(chain.from_iterable(mi.image_set.all() for mi in h.get_image_menu()))

Это позволяет избежать создания промежуточного списка.

В Python 3.4 вы сможете:

[*innerlist for innerlist in outer_list]

Хм. Хотя я бы приветствовал это, это уже обсуждалось еще для Py3.0. Теперь PEP 448 присутствует, но все еще находится в режиме «Черновик». Соответствующий билет об ошибке все еще находится в «обзоре исправлений» с еще неполным исправлением. До тех пор, пока ошибка не будет помечена как «совершенная», я буду осторожен, надеясь и сказав: «Вы сможете это сделать».

cfi 27.09.2013 21:33

Я понимаю, что вы имеете в виду, но недавно один из основных разработчиков объявил об этом на Kiwi PyCon 2013 как о «принятом к выпуску» в версии 3.4. Все еще не уверен на 100%, но полагаю, что весьма вероятно.

elyase 28.09.2013 01:09

Будем надеяться, что это просто нехватка документации за кодом, как обычно для sw перед выпуском ;-)

cfi 28.09.2013 02:12
SyntaxError: can use starred expression only as assignment target
dawg 10.04.2015 00:05

Этот синтаксис был не принято в окончательном PEP 448.

dawg 10.04.2015 00:13

ты пробовал сгладить? От matplotlib.cbook.flatten (seq, скаляр =)?

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

run("list(flatten(l))")
         3732 function calls (3303 primitive calls) in 0.007 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.007    0.007 <string>:1(<module>)
      429    0.001    0.000    0.001    0.000 cbook.py:475(iterable)
      429    0.002    0.000    0.003    0.000 cbook.py:484(is_string_like)
      429    0.002    0.000    0.006    0.000 cbook.py:565(is_scalar_or_string)
  727/298    0.001    0.000    0.007    0.000 cbook.py:605(flatten)
      429    0.000    0.000    0.001    0.000 core.py:5641(isMaskedArray)
      858    0.001    0.000    0.001    0.000 {isinstance}
      429    0.000    0.000    0.000    0.000 {iter}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("list(flatten(l))")
         7461 function calls (6603 primitive calls) in 0.007 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.007    0.007 <string>:1(<module>)
      858    0.001    0.000    0.001    0.000 cbook.py:475(iterable)
      858    0.002    0.000    0.003    0.000 cbook.py:484(is_string_like)
      858    0.002    0.000    0.006    0.000 cbook.py:565(is_scalar_or_string)
 1453/595    0.001    0.000    0.007    0.000 cbook.py:605(flatten)
      858    0.000    0.000    0.001    0.000 core.py:5641(isMaskedArray)
     1716    0.001    0.000    0.001    0.000 {isinstance}
      858    0.000    0.000    0.000    0.000 {iter}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("list(flatten(l))")
         11190 function calls (9903 primitive calls) in 0.010 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.010    0.010 <string>:1(<module>)
     1287    0.002    0.000    0.002    0.000 cbook.py:475(iterable)
     1287    0.003    0.000    0.004    0.000 cbook.py:484(is_string_like)
     1287    0.002    0.000    0.009    0.000 cbook.py:565(is_scalar_or_string)
 2179/892    0.001    0.000    0.010    0.000 cbook.py:605(flatten)
     1287    0.001    0.000    0.001    0.000 core.py:5641(isMaskedArray)
     2574    0.001    0.000    0.001    0.000 {isinstance}
     1287    0.000    0.000    0.000    0.000 {iter}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("list(flatten(l))")
         14919 function calls (13203 primitive calls) in 0.013 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.013    0.013 <string>:1(<module>)
     1716    0.002    0.000    0.002    0.000 cbook.py:475(iterable)
     1716    0.004    0.000    0.006    0.000 cbook.py:484(is_string_like)
     1716    0.003    0.000    0.011    0.000 cbook.py:565(is_scalar_or_string)
2905/1189    0.002    0.000    0.013    0.000 cbook.py:605(flatten)
     1716    0.001    0.000    0.001    0.000 core.py:5641(isMaskedArray)
     3432    0.001    0.000    0.001    0.000 {isinstance}
     1716    0.001    0.000    0.001    0.000 {iter}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler'

ОБНОВИТЬ Это дало мне еще одну идею:

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

run("flattenlist(l)")
         564 function calls (432 primitive calls) in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    133/1    0.000    0.000    0.000    0.000 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
      429    0.000    0.000    0.000    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("flattenlist(l)")
         1125 function calls (861 primitive calls) in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    265/1    0.001    0.000    0.001    0.001 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
      858    0.000    0.000    0.000    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("flattenlist(l)")
         1686 function calls (1290 primitive calls) in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    397/1    0.001    0.000    0.001    0.001 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1287    0.000    0.000    0.000    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("flattenlist(l)")
         2247 function calls (1719 primitive calls) in 0.002 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    529/1    0.001    0.000    0.002    0.002 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.000    0.000    0.002    0.002 <string>:1(<module>)
     1716    0.001    0.000    0.001    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



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

run("flattenlist(l)")
         22443 function calls (17163 primitive calls) in 0.016 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   5281/1    0.011    0.000    0.016    0.016 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.000    0.000    0.016    0.016 <string>:1(<module>)
    17160    0.005    0.000    0.005    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Итак, чтобы проверить, насколько это эффективно, когда рекурсивная становится глубже: насколько глубже?

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

new=[l]*33

run("flattenlist(new)")
         740589 function calls (566316 primitive calls) in 0.418 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 174274/1    0.281    0.000    0.417    0.417 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.001    0.001    0.418    0.418 <string>:1(<module>)
   566313    0.136    0.000    0.136    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



new=[l]*66

run("flattenlist(new)")
         1481175 function calls (1132629 primitive calls) in 0.809 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 348547/1    0.542    0.000    0.807    0.807 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.002    0.002    0.809    0.809 <string>:1(<module>)
  1132626    0.266    0.000    0.266    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



new=[l]*99

run("flattenlist(new)")
         2221761 function calls (1698942 primitive calls) in 1.211 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 522820/1    0.815    0.000    1.208    1.208 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.002    0.002    1.211    1.211 <string>:1(<module>)
  1698939    0.393    0.000    0.393    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



new=[l]*132

run("flattenlist(new)")
         2962347 function calls (2265255 primitive calls) in 1.630 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 697093/1    1.091    0.000    1.627    1.627 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.003    0.003    1.630    1.630 <string>:1(<module>)
  2265252    0.536    0.000    0.536    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}



new=[l]*1320

run("flattenlist(new)")
         29623443 function calls (22652523 primitive calls) in 16.103 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
6970921/1   10.842    0.000   16.069   16.069 <ipython-input-55-39b139bad497>:4(flattenlist)
        1    0.034    0.034   16.103   16.103 <string>:1(<module>)
 22652520    5.227    0.000    5.227    0.000 {isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Готов поспорить на "flattenlist". Я собираюсь использовать это вместо matploblib в течение долгого времени, если мне не нужен генератор доходности и быстрый результат, как "flatten" в matploblib.cbook

Это быстро.

  • А вот код

:

typ=(list,tuple)


def flattenlist(d):
    thelist = []
    for x in d:
        if not isinstance(x,typ):
            thelist += [x]
        else:
            thelist += flattenlist(x)
    return thelist

Эта версия является генератором. Подправьте его, если хотите список.

def list_or_tuple(l):
    return isinstance(l,(list,tuple))
## predicate will select the container  to be flattened
## write your own as required
## this one flattens every list/tuple


def flatten(seq,predicate=list_or_tuple):        
    ## recursive generator 
    for i in seq:
        if predicate(seq):
            for j in flatten(i):
                yield j
        else:
            yield i

Вы можете добавить предикат, если хотите сгладить те, которые удовлетворяют условию

Взято из кулинарной книги Python

По моему опыту, наиболее эффективный способ сгладить список списков:

flat_list = []
map(flat_list.extend, list_of_list)

Некоторое время его сравнения с другими предложенными методами:

list_of_list = [range(10)]*1000
%timeit flat_list=[]; map(flat_list.extend, list_of_list)
#10000 loops, best of 3: 119 µs per loop
%timeit flat_list=list(itertools.chain.from_iterable(list_of_list))
#1000 loops, best of 3: 210 µs per loop
%timeit flat_list=[i for sublist in list_of_list for i in sublist]
#1000 loops, best of 3: 525 µs per loop
%timeit flat_list=reduce(list.__add__,list_of_list)
#100 loops, best of 3: 18.1 ms per loop

Теперь при обработке более длинных подсписок повышение эффективности выглядит лучше:

list_of_list = [range(1000)]*10
%timeit flat_list=[]; map(flat_list.extend, list_of_list)
#10000 loops, best of 3: 60.7 µs per loop
%timeit flat_list=list(itertools.chain.from_iterable(list_of_list))
#10000 loops, best of 3: 176 µs per loop

И эти методы также работают с любым итеративным объектом:

class SquaredRange(object):
    def __init__(self, n): 
        self.range = range(n)
    def __iter__(self):
        for i in self.range: 
            yield i**2

list_of_list = [SquaredRange(5)]*3
flat_list = []
map(flat_list.extend, list_of_list)
print flat_list
#[0, 1, 4, 9, 16, 0, 1, 4, 9, 16, 0, 1, 4, 9, 16]

pylab обеспечивает сглаживание: ссылка на numpy flatten

Примечание: Flatten не работает с зубчатыми массивами. Попробуйте вместо этого использовать стек.

Stewbaca 27.01.2016 20:48

sum(list_of_lists, []) сгладил бы его.

l = [['image00', 'image01'], ['image10'], []]
print sum(l,[]) # prints ['image00', 'image01', 'image10']

Мне это нравится! Это напоминает мне использование iter[::-1] вместо sorted(iter, reverse=True). Интересно, будет ли это одной из тех вещей, которые с годами будут рассматривать как «плохой Python». Мне это кажется очень подходящим для TIMTOWTDI.

Droogans 04.11.2020 01:25

Вроде бы путаница с operator.add! Когда вы складываете два списка вместе, правильный термин для этого - concat, а не добавление. operator.concat - это то, что вам нужно.

Если вы думаете о функциональности, это очень просто:

>>> list2d = ((1,2,3),(4,5,6), (7,), (8,9))
>>> reduce(operator.concat, list2d)
(1, 2, 3, 4, 5, 6, 7, 8, 9)

Вы видите, что сокращение уважает тип последовательности, поэтому, когда вы предоставляете кортеж, вы получаете обратно кортеж. попробуем со списком:

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

Ага, вы вернули список.

Как насчет производительности:

>>> list2d = [[1,2,3],[4,5,6], [7], [8,9]]
>>> %timeit list(itertools.chain.from_iterable(list2d))
1000000 loops, best of 3: 1.36 µs per loop

from_iterable работает довольно быстро! Но сокращение с помощью concat - не сравнение.

>>> list2d = ((1,2,3),(4,5,6), (7,), (8,9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop

это, наверное, лучшее решение для одного уровня вложенности. но это может быть слишком ограничивающим ограничением. YMMV

LBarret 24.07.2019 00:00

Если каждый элемент в списке является строкой (и в любых строках внутри этих строк используется "", а не ""), вы можете использовать регулярные выражения (модуль re).

>>> flattener = re.compile("\'.*?\'")
>>> flattener
<_sre.SRE_Pattern object at 0x10d439ca8>
>>> stred = str(in_list)
>>> outed = flattener.findall(stred)

Приведенный выше код преобразует in_list в строку, использует регулярное выражение для поиска всех подстрок в кавычках (то есть каждого элемента списка) и выводит их в виде списка.

Вот версия, работающая для нескольких уровней списка с использованием collectons.Iterable:

import collections

def flatten(o, flatten_condition=lambda i: isinstance(i,
               collections.Iterable) and not isinstance(i, str)):
    result = []
    for i in o:
        if flatten_condition(i):
            result.extend(flatten(i, flatten_condition))
        else:
            result.append(i)
    return result

Не могли бы предложить, почему ваше решение дает RecursionError: maximum recursion depth exceeded in comparison на этом входе A = ['image1', [[[['image2']]]], [['image3'], 'image4'], 'image5'], в то время как он работает нормально, и снимает сглаживание этого ввода A = [1,[2,3],[4,5,[6,[7,8],9]]]

Anu 12.01.2019 21:29

Это проблема со сплющенным состоянием. Поскольку строки являются итеративными, они сглаживаются как символы, которые сами по себе являются строками длины один, и поскольку они являются строками, снова применяется та же логика, и создается бесконечный цикл. Поэтому я создал новую версию с условием выравнивания для большего контроля.

Pierre Thibault 13.01.2019 01:53

Большой! big Спасибо за разъяснения, сейчас работает.! Я вроде как понял ваши рассуждения, но не смог полностью их переварить. Не могли бы вы указать мне на какую-нибудь статью в Интернете или любой пост, который поможет разобраться в проблеме! Я понял, что `['image1'] -> ['i', 'm', 'a', 'g', 'e', ​​'1']` т.е. строки длины один !, и теперь, как это будет идти в бесконечном цикле, а что заставляет идти в бесконечном цикле? эту часть я еще не понял! Вы можете чем-то помочь!

Anu 13.01.2019 03:17

Чтобы функция сглаживалась до конца, если она входит в цикл for, в какой-то момент она должна перейти в оператор else. Если он входит в оператор else, он начинает разворачивать стек вызовов и возвращать результат. Основываясь на предыдущей версии, поскольку «image1» является итеративным, то o будет равно «image1», а i будет равно «i». «i» также может повторяться, поэтому в следующем вызове o будет равно «i», в то время как i также будет равно «i». Функция будет вызвана снова, что приведет к тому же состоянию и бесконечному циклу, нарушенному только переполнением стека.

Pierre Thibault 13.01.2019 08:28

Лучше использовать yield для генерации последовательности элементов через список result. Итератор может быть вычислен лениво, а функция fn, использующая его, может потреблять последовательность по мере необходимости.

kopos 14.01.2019 13:28

Если вам нужно сгладить более сложный список с не повторяемыми элементами или с глубиной более 2, вы можете использовать следующую функцию:

def flat_list(list_to_flat):
    if not isinstance(list_to_flat, list):
        yield list_to_flat
    else:
        for item in list_to_flat:
            yield from flat_list(item)

Он вернет объект-генератор, который вы можете преобразовать в список с помощью функции list(). Обратите внимание, что синтаксис yield from доступен начиная с python3.3, но вместо этого вы можете использовать явную итерацию. Пример:

>>> a = [1, [2, 3], [1, [2, 3, [1, [2, 3]]]]]
>>> print(list(flat_list(a)))
[1, 2, 3, 1, 2, 3, 1, 2, 3]

это решение дает RECURSION ERROR ON : входные A = ['str1', [[[['str2']]]], [['str3'], 'str4'], 'str5'] и A = [1.0, 2, 'a', [4,], [[6,], [8,]], [[[8,],[9,]], [[12,],[10]]]]. Вы знаете, почему и как это исправить?

Anu 12.01.2019 23:15

@anu Он работал без ошибок на ваших примерах для меня (python 3.7.1). Я не уверен, почему у тебя это не работает.

DartLenin 13.01.2019 02:46

Я использую python3.6, теперь я обнаружил проблему, вам нужно добавить or isinstance(list_to_flat, str) в первое условие if, поскольку оно должно защищать от строк. Ваше решение идеально подходило для ввода A = [1, [[[[2]]]], [[3], 4], 5], но не работает при использовании строк !, проводилось ли тестирование со строками в python3.7?

Anu 13.01.2019 03:28

@anu Я тестировал его на тех же примерах, которые вы предоставили. Ваш первый пример был со строками, и он работал нормально. Первый оператор if говорит, что любой элемент, не входящий в список, должен возвращаться как есть, без сглаживания. Сюда входят и строки, никаких дополнительных условий не требуется.

DartLenin 13.01.2019 04:06

о, хорошо, это может быть из-за различий в версии Python! Возможно, они выпустили некоторые обновления в 3.7.

Anu 13.01.2019 04:10

человек! Это как раз то, что мне нужно! Большое спасибо!

Gabs 30.11.2019 02:30

Если вам нужен встроенный простой однострочник, вы можете использовать:

a = [[1, 2, 3], [4, 5, 6]
b = [i[x] for i in a for x in range(len(i))]
print b

возвращается

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

Простая альтернатива - использовать конкатенация numpy, но он преобразует содержимое в float:

import numpy as np
print np.concatenate([[1,2],[3],[5,89],[],[6]])
# array([  1.,   2.,   3.,   5.,  89.,   6.])
print list(np.concatenate([[1,2],[3],[5,89],[],[6]]))
# [  1.,   2.,   3.,   5.,  89.,   6.]

Самый простой способ добиться этого в Python 2 или 3 - использовать библиотеку превращаться с помощью pip install morph.

Код такой:

import morph

list = [[1,2],[3],[5,89],[],[6]]
flattened_list = morph.flatten(list)  # returns [1, 2, 3, 5, 89, 6]

"самый простой" - сильное слово

cfi 17.12.2015 15:19

@cfi Предложенный вами ответ не работает в Python 2, и из комментариев не похоже, что это даже приемлемый ответ в Python 3. Библиотека морфинга - это простое решение с одной функцией, как у вас в lodash для javascript. В любом случае я отредактировал свой ответ, чтобы уточнить, что это самое простое решение, которое работает с Python 2 и 3.

YPCrumble 17.12.2015 20:46

Я действительно сожалею. Мой комментарий был немного ленивым, тем более, что вы указали на мой собственный комментарий к другому посту. Я хотел сказать, что «самый легкий» - это суперлятив, которого трудно достичь. Для вашего предложения требуется внешняя библиотека, которую некоторым может быть сложно установить (даже с venv и т. д.). Поскольку вопрос касается «мелких» списков и «баланса между производительностью и удобочитаемостью», ваш ответ может (!) Выиграть с точки зрения удобочитаемости. Но Вот этот выигрывает по производительности и проще в том, что не требует зависимостей.

cfi 17.12.2015 21:27

@cfi yeah - мой подход может быть "подходом ленивого". Что касается меня, видя все эти способы сглаживания, мне захотелось просто найти быструю библиотечную команду, такую ​​как я нашел с морфингом. В этой библиотеке хорошо то, что она намного меньше, чем numpy (мне нужно использовать файл подкачки для установки numpy на небольших экземплярах сервера). Он в основном использует функцию, которую вы описываете во втором комментарии; другой вариант заключался бы в том, чтобы я использовал это как вспомогательную функцию в моем коде. Вообще нет проблем, спасибо, что указали на варианты :).

YPCrumble 17.12.2015 21:53
def is_iterable(item):
   return isinstance(item, list) or isinstance(item, tuple)


def flatten(items):
    for i in items:
        if is_iterable(item):
            for m in flatten(i):
                yield m
        else:
            yield i

Контрольная работа:

print list(flatten2([1.0, 2, 'a', (4,), ((6,), (8,)), (((8,),(9,)), ((12,),(10)))]))

Это может привести к сглаживанию строк в отдельные символы, что может быть нежелательным поведением?

ericmjl 23.03.2018 15:11

Да, я не учел это условие. Спасибо.

kopos 24.03.2018 09:34

@kopos, спасибо за ваше решение, но я получаю эту ошибку for m in flatten(i): [Previous line repeated 996 more times] RecursionError: maximum recursion depth exceeded на ваших входных A = [1.0, 2, 'a', (4,), ((6,), (8,)), (((8,),(9,)), ((12,),(10)))] и A = ['str1', [[[['str2']]]], [['str3'], 'str4'], 'str5'], но она отлично работает с этим входом A = [1, [[[[2]]]], [[3], 4], 5]. Вы знаете, в чем причина его отказа? а как исправить? какие-либо предложения?

Anu 12.01.2019 21:47

@kopos, теперь у меня есть исправление !, вам нужно добавить еще одно условие в оператор if and not isinstance(i,str ) для защиты от строк в списке при выравнивании!

Anu 13.01.2019 03:35

@anu: Да, это исправление работает! Но проблема в том, что мы определяем тип коллекции на основе hasattr и isinstance. Если мы знаем тип узлов сбора, fn можно настроить для него. Возможно, нам придется настроить функцию в зависимости от того, как она должна себя вести, если коллекция - это set.

kopos 14.01.2019 13:21

@kopos, вы правы, есть способ обобщения коллекций с помощью typing.Sequence и использования Sequence вместо фиксированного типа коллекции, такого как list, strings, sets, range etc!, более того, чтобы защитить себя от строк, мы добавляем предыдущее условие, как я упоминал ранее вот о чем я говорю

Anu 14.01.2019 20:39

Другие вопросы по теме