2009-04-04 2 views
221

Является ли короткий синтаксис для объединения списка списков в один список (или итератор) в python?Список списков в python

Например, у меня есть следующий список, и я хочу перебирать символы a, b и c.

x = [["a","b"], ["c"]] 

Лучшее, что я могу придумать, заключается в следующем.

result = [] 
[ result.extend(el) for el in x] 

for el in result: 
    print el 
+0

Дубликат: http://stackoverflow.com/questions/120886/python-idiom-to-chain-flatten-an-infinite-iterable-of-finite-iterables, http://stackoverflow.com/questions/406121/flattening -a-small-list-in-python –

ответ

318
import itertools 
a = [["a","b"], ["c"]] 
print list(itertools.chain.from_iterable(a)) 
+5

нет необходимости перечислять() это! для элемента в itertools.chain (* a): сделайте что-нибудь с товаром – hasen

+9

Немного объяснения также будет приятным. http://docs.python.org/library/itertools.html#itertools.chain – hasen

+87

Чуть лучше: 'list (itertools.chain.from_iterable (a))' –

27

Это известно как уплощение, и есть много реализаций там:

Как об этом, хотя он будет работать только на глубину 1-го уровня:

>>> x = [["a","b"], ["c"]] 
>>> for el in sum(x, []): 
...  print el 
... 
a 
b 
c 

Из этих связей, по-видимому, наиболее полная-быстро-элегантная и т.д. реализация заключается в следующем:

def flatten(l, ltypes=(list, tuple)): 
    ltype = type(l) 
    l = list(l) 
    i = 0 
    while i < len(l): 
     while isinstance(l[i], ltypes): 
      if not l[i]: 
       l.pop(i) 
       i -= 1 
       break 
      else: 
       l[i:i + 1] = l[i] 
     i += 1 
    return ltype(l) 
+3

Ах, 'сумма (L, I)' это сокращение для 'уменьшения (plus_operator, L, I)'. Это здорово. – Aaron

+4

ваш «самый полный-элегантный и т. д.» не является «элегантным» вообще! см. документы для itertools.chain, чтобы увидеть истинную элегантность! – hasen

+4

@hasen j: Я считаю, что он лучше всего подходит для произвольных вложенных списков. цепочка принимает последовательный, один-глубокий список списков (что, вероятно, все вопросы), но сглаживает обрабатывает такие вещи, как [a, b, [c], [d, [e, f]], [[g] ]]]. – Brian

5

То, что вы описываете, как известно уплощения списка, и с этим новые знания вы сможете найти в Google много решений (нет встроенного метода сглаживания). Вот один из них, из http://www.daniel-lemire.com/blog/archives/2006/05/10/flattening-lists-in-python/:

def flatten(x): 
    flat = True 
    ans = [] 
    for i in x: 
     if (i.__class__ is list): 
      ans = flatten(i) 
     else: 
      ans.append(i) 
    return ans 
+0

Этот метод хорошо работает для сочетания списков строк и строк (например, '[['some', 'string'], 'and', 'another']'), в то время как методы itertools этого не делают. Это хорошо работает для моих нужд. –

1

К сожалению, Python не имеет простой способ сглаживаться списков. Попробуйте следующее:

def flatten(some_list): 
    for element in some_list: 
     if type(element) in (tuple, list): 
      for item in flatten(element): 
       yield item 
     else: 
      yield element 

Что будет рекурсивно сгладить список; то вы можете сделать

result = [] 
[ result.extend(el) for el in x] 

for el in flatten(result): 
     print el 
96
x = [["a","b"], ["c"]] 

result = sum(x, []) 
+2

@ Аарон, объясните ученику-пионеру noob, пожалуйста: O (n^2) хорошо или плохо в этом случае? ;-) – Aufwind

+12

O (n^2) здесь в основном означает, что время, необходимое для выполнения этой функции, пропорционально квадрату длины входов. Поэтому, если вы удваиваете входные данные, вы в четыре раза увеличиваете время. Это Bad Thing, если у вас большие входы, но для маленьких это должно быть хорошо. Но более быстрый метод будет лучше. – andronikus

+0

Нет, это линейно в общем количестве элементов во всех подсписках в сочетании, как и все другие методы, предлагаемые на этой странице. – Julian

3

Там всегда уменьшало (устаревшим к functools):

>>> x = [ [ 'a', 'b'], ['c'] ] 
>>> for el in reduce(lambda a,b: a+b, x, []): 
... print el 
... 
__main__:1: DeprecationWarning: reduce() not supported in 3.x; use functools.reduce() 
a 
b 
c 
>>> import functools 
>>> for el in functools.reduce(lambda a,b: a+b, x, []): 
... print el 
... 
a 
b 
c 
>>> 

К сожалению, оператор плюс список конкатенации не может быть использован в качестве функции - или счастье , если вы предпочитаете лямбды быть уродливыми для улучшения видимости.

+1

GAH, я не могу поверить, что они обесценивают это для functools. Во всяком случае, вам не нужен лишний пустой список, это будет работать очень хорошо: уменьшить (lambda a, b: a + b, x) – Benson

+2

Версии операторов определяются как функции в операторном модуле, которые быстрее и менее уродливы, чем лямбда: «functools.reduce (operator.add, [[1,2,3], [4,5]], [ ])». В качестве альтернативы просто используйте sum() – Brian

+0

Лично я считаю, что способ лямбда довольно хорош. :-) – Benson

120

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

>>> x = [["a","b"], ["c"]] 
>>> [inner 
...  for outer in x 
...   for inner in outer] 
['a', 'b', 'c'] 

На одной линии, которая становится:

>>> [j for i in x for j in i] 
['a', 'b', 'c'] 
+0

Очень круто, поэтому для следующего уровня глубины он станет [i для ll in x для l в ll для i в l] - на этом этапе он начинает получать бит ламе для читателя, но тем не менее прохладно :) – Cyrus

+0

Для трех уровней он становится неприятным: >>> x = [[["a", "b"], ["c"]], [["d" ]]] >>> [k для i в x для j в i для k в j] ['a', 'b', 'c', 'd'] –

+0

Listception .. это определенно unpythonic/против zen python в том, что это не самый простой или самый явный способ сделать это. Вы закончите жесткую рекурсию кодирования. Тем не менее, прохладно. –

15

Это работает рекурсивно для бесконечно вложенных элементов:

def iterFlatten(root): 
    if isinstance(root, (list, tuple)): 
     for element in root: 
      for e in iterFlatten(element): 
       yield e 
    else: 
     yield root 

Результат:

 
>>> b = [["a", ("b", "c")], "d"] 
>>> list(iterFlatten(b)) 
['a', 'b', 'c', 'd'] 
+1

'>>> a = [] >>> a.append (a) >>> b = iterFlatten (a) >>> next (b) RuntimeError: максимальная глубина рекурсии превышена в __instancecheck__' :) – Darthfett

+2

@ Darthfett Вы ожидаете значимого результата для сглаживания« бесконечно-вложенного списка »? :-) – Kos

+0

@ Kos. Версия, которая проверяет такие случаи (используя стек/набор для проверки самореференций в списке), может быть предпочтительнее, чем просто продолжать сглаживание до достижения предела глубины рекурсии. Это может обойти проблему, просто указав значение, вместо того, чтобы пытаться сгладить его. – Darthfett

2

Или рекурсивная операция:

def flatten(input): 
    ret = [] 
    if not isinstance(input, (list, tuple)): 
     return [input] 
    for i in input: 
     if isinstance(i, (list, tuple)): 
      ret.extend(flatten(i)) 
     else: 
      ret.append(i) 
    return ret 
23
l = [] 
map(l.extend, list_of_lists) 

кратчайшее!

+9

sum (listoflists, []) # короче! – recursive

+4

@recursive Более короткие, но разные функционально = намного худшие по производительности, см. Комментарии к другим вариантам для объяснения. – Kos

+0

Этот крошечный фрагмент, по-видимому, является самым быстрым способом для нерекурсивного сглаживания , Требуется больше оборотов. – Kos

6

Late к партии, но ...

Я новичок в Python и происхожу из LISP фона. Это то, что я придумал (ознакомьтесь с названиями var для lulz):

def flatten(lst): 
    if lst: 
     car,*cdr=lst 
     if isinstance(car,(list,tuple)): 
      if cdr: return flatten(car) + flatten(cdr) 
      return flatten(car) 
     if cdr: return [car] + flatten(cdr) 
     return [car] 

Кажется, нужно работать. Тест:

flatten((1,2,3,(4,5,6,(7,8,(((1,2))))))) 

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

[1, 2, 3, 4, 5, 6, 7, 8, 1, 2] 
+15

Вы родом из lisp фона? Я никогда не догадался бы из кода ... haha ​​ – Tom

+0

Приятно, я занимаюсь Python в течение некоторого времени, и я не видел распаковку var-arg tuple, как вы делали с 'car, * cdr'. (возможно, потому, что это Python 3, и я по-прежнему копаю 2 по некоторым причинам :-)) – Kos

1

Для одного уровня сплющивается, если вы заботитесь о скорости, это быстрее, чем любой из предыдущих ответов в любых условиях я попробовал. (То есть, если вам нужен результат в виде списка. Если вам нужно только перебирать его на лету, то пример цепи, вероятно, лучше.) Он работает, предварительно распределяя список конечного размера и копируя части в на срезе (который является копией нижнего уровня блока, чем любой из методов итератора):

def join(a): 
    """Joins a sequence of sequences into a single sequence. (One-level flattening.) 
    E.g., join([(1,2,3), [4, 5], [6, (7, 8, 9), 10]]) = [1,2,3,4,5,6,(7,8,9),10] 
    This is very efficient, especially when the subsequences are long. 
    """ 
    n = sum([len(b) for b in a]) 
    l = [None]*n 
    i = 0 
    for b in a: 
     j = i+len(b) 
     l[i:j] = b 
     i = j 
    return l 

отсортированный список раза с комментариями:

[(0.5391559600830078, 'flatten4b'), # join() above. 
(0.5400412082672119, 'flatten4c'), # Same, with sum(len(b) for b in a) 
(0.5419249534606934, 'flatten4a'), # Similar, using zip() 
(0.7351131439208984, 'flatten1b'), # list(itertools.chain.from_iterable(a)) 
(0.7472689151763916, 'flatten1'), # list(itertools.chain(*a)) 
(1.5468521118164062, 'flatten3'), # [i for j in a for i in j] 
(26.696547985076904, 'flatten2')] # sum(a, []) 
+0

Можете ли вы добавить тайминги, чтобы подтвердить, что это быстрее, чем другие методы, представленные? – esmit

+0

Сортировка списка с комментариями: '[(0.5391559600830078, 'flatten4b'), # join() выше. (0.5400412082672119, 'flatten4c'), # То же, с суммой (len (b) для b в a) (0.5419249534606934, 'flatten4a'), # Аналогично, используя zip() (0.7351131439208984, 'flatten1b'), # list (itertools.chain.from_iterable (a)) (0.7472689151763916, 'flatten1'), # list (itertools.chain (* a)) (1.5468521118164062, 'flatten3'), # [i for j in a for i in j] (26.696547985076904, 'flatten2')] # sum (a, []) ' – Brandyn

+0

Вы пропустили 'map (result.extend, a)' – Kos

1

Если вам нужен список, а не генератор, использование list():

from itertools import chain 
x = [["a","b"], ["c"]] 
y = list(chain(*x)) 
+0

s/'x' /' * x'/(или 'chain.from_iterable (x)' предпочтительно) – Kos

+0

Я не понимаю, что он делает. Предполагается, что 'join' имеет разделитель. – Val

+0

@Val 'chain' создает генератор, который будет выводить 'a', 'b', 'c'. 'list' преобразует его в список. –

1

У меня была аналогичная проблема, когда мне приходилось создавать словарь, содержащий элементы массива и их счет. Ответ важен, потому что я сглаживаю список списков, получаю нужные мне элементы, а затем делаю группу и подсчитываю. Я использовал функцию отображения Python для создания кортежа элемента и его счет и группировку по массиву. Обратите внимание, что groupby принимает элемент массива как keyfunc. Как относительно новый кодер Python, я считаю это более понятным для меня, будучи также Pythonic.

Прежде чем обсуждать код, вот пример данных я должен выравниваться первым:

{ "_id" : ObjectId("4fe3a90783157d765d000011"), "status" : [ "opencalais" ], 
    "content_length" : 688, "open_calais_extract" : { "entities" : [ 
    {"type" :"Person","name" : "Iman Samdura","rel_score" : 0.223 }, 
    {"type" : "Company", "name" : "Associated Press", "rel_score" : 0.321 },   
    {"type" : "Country", "name" : "Indonesia", "rel_score" : 0.321 }, ... ]}, 
    "title" : "Indonesia Police Arrest Bali Bomb Planner", "time" : "06:42 ET",   
    "filename" : "021121bn.01", "month" : "November", "utctime" : 1037836800, 
    "date" : "November 21, 2002", "news_type" : "bn", "day" : "21" } 

Это результат запроса из Монго. Код ниже выравнивает набор таких списков.

def flatten_list(items): 
    return sorted([entity['name'] for entity in [entities for sublist in 
    [item['open_calais_extract']['entities'] for item in items] 
    for entities in sublist]) 

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

4

Сравнение производительности:

import itertools 
import timeit 
big_list = [[0]*1000 for i in range(1000)] 
timeit.repeat(lambda: list(itertools.chain.from_iterable(big_list)), number=100) 
timeit.repeat(lambda: list(itertools.chain(*big_list)), number=100) 
timeit.repeat(lambda: (lambda b: map(b.extend, big_list))([]), number=100) 
timeit.repeat(lambda: [el for list_ in big_list for el in list_], number=100) 
[100*x for x in timeit.repeat(lambda: sum(big_list, []), number=1)] 

Производство:

>>> import itertools 
>>> import timeit 
>>> big_list = [[0]*1000 for i in range(1000)] 
>>> timeit.repeat(lambda: list(itertools.chain.from_iterable(big_list)), number=100) 
[3.016212113769325, 3.0148865239060227, 3.0126415732791028] 
>>> timeit.repeat(lambda: list(itertools.chain(*big_list)), number=100) 
[3.019953987082083, 3.528754223385439, 3.02181439266457] 
>>> timeit.repeat(lambda: (lambda b: map(b.extend, big_list))([]), number=100) 
[1.812084445152557, 1.7702404451095965, 1.7722977998725362] 
>>> timeit.repeat(lambda: [el for list_ in big_list for el in list_], number=100) 
[5.409658160700605, 5.477502077679354, 5.444318360412744] 
>>> [100*x for x in timeit.repeat(lambda: sum(big_list, []), number=1)] 
[399.27587954973444, 400.9240571138051, 403.7521153804846] 

Это с Python 2.7.1 на Windows XP 32-бит, но @temoto в комментариях выше получил from_iterable быть быстрее чем map+extend, поэтому он довольно зависим от платформы и ввода.

Избегают sum(big_list, [])

+0

Супер полезно. Благодаря! Обратите внимание, что в Python3 нам нужен список() вокруг версии map(), иначе результаты слишком хороши, чтобы быть правдой. –

+0

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

Смежные вопросы