розуміння списку python; стиснення списку списків?


79

хлопці. Я намагаюся знайти найелегантніше рішення проблеми і замислювався, чи python має щось вбудоване для того, що я намагаюся зробити.

Я роблю це. У мене є список, Aі у мене є функція, fяка бере елемент і повертає список. Я можу використовувати розуміння списку, щоб перетворити все Aприблизно так;

[f(a) for a in A]

Але це повертає список списків;

[a1,a2,a3] => [[b11,b12],[b21,b22],[b31,b32]]

Насправді я хочу отримати сплощений список;

[b11,b12,b21,b22,b31,b32]

Тепер це є в інших мовах; це традиційно називається flatmapу функціональних мовах програмування, а .Net - це SelectMany. Чи є у python щось подібне? Чи є акуратний спосіб відобразити функцію за списком і згладити результат?

Фактична проблема, яку я намагаюся вирішити, полягає в наступному; починаючи зі списку каталогів, знайдіть усі підкаталоги. так;

import os
dirs = ["c:\\usr", "c:\\temp"]
subs = [os.listdir(d) for d in dirs]
print subs

currentliy дає мені список зі списків, але я дуже хочу список.

Відповіді:


117

Ви можете вкласти ітерації в одному розумінні списку:

[filename for path in dirs for filename in os.listdir(path)]

що еквівалентно (принаймні функціонально):

filenames = []
for path in dirs:
    for filename in os.listdir(path):
        filenames.append(filename)

65
Хоча розумно, але це важко зрозуміти і не дуже читається.
Кертіс Яллоп,

2
Насправді не відповідає на запитання. Це швидше обхідний шлях для того, щоб спочатку не зіткнутися з проблемою. Що робити, якщо у вас вже є список списків. Наприклад, що, якщо ваш список списків є результатом функції відображення багатопроцесорного модуля? Можливо, найкраще підійти рішення itertools або рішення зменшення.
Dave31415

22
Dave31415:[ item for list in listoflists for item in list ]
Ремпіон

9
"читабельність" - це суб'єктивне судження. Я вважаю це рішення цілком читабельним.
Реб. Кабін

9
Я думав, що це теж читабельно, поки не побачив порядок умов ... :(
cz

83
>>> from functools import reduce
>>> listOfLists = [[1, 2],[3, 4, 5], [6]]
>>> reduce(list.__add__, listOfLists)
[1, 2, 3, 4, 5, 6]

Я припускаю, що рішення itertools є більш ефективним, ніж це, але це здається дуже пітонічним.

У Python 2 він уникає необхідності імпортувати бібліотеку лише заради однієї операції зі списком (оскільки reduceце вбудований).


5
Це, безумовно, найкраще рішення.
Коннор Дойл

Що слід імпортувати, щоб зателефонувати reduce, чи це pandas, scipyчи functools?
Саджон

1
У Python 2 це вбудований. Для Python3 functoolsверсія - це те саме.
Джуліан

блін! це геній
Тілак Медді

56

Ви можете знайти хорошу відповідь у рецептах itertools:

def flatten(listOfLists):
    return list(chain.from_iterable(listOfLists))

(Примітка: потрібен Python 2.6+)


Той самий підхід може бути використаний для визначення плоскої карти, як пропонується цією відповіддю та цим зовнішнім повідомленням у блозі
Джосія Йодер

28

Запропоноване питання flatmap. Запропоновано деякі реалізації, але вони можуть не вимагати створення проміжних списків. Ось одна реалізація, яка базується на ітераторах.

def flatmap(func, *iterable):
    return itertools.chain.from_iterable(map(func, *iterable))

In [148]: list(flatmap(os.listdir, ['c:/mfg','c:/Intel']))
Out[148]: ['SPEC.pdf', 'W7ADD64EN006.cdr', 'W7ADD64EN006.pdf', 'ExtremeGraphics', 'Logs']

У Python 2.x використовуйте itertools.mapзамість map.


18

Ви можете зробити просто:

subs = []
for d in dirs:
    subs.extend(os.listdir(d))

Так, це чудово (хоча і не зовсім так добре, як @Ants '), тому я даю йому +1, щоб вшанувати його простоту!
Alex Martelli

16

Ви можете об’єднати списки за допомогою звичайного оператора додавання:

>>> [1, 2] + [3, 4]
[1, 2, 3, 4]

Вбудована функція sumдодаватиме цифри в послідовності і може за бажанням починати з певного значення:

>>> sum(xrange(10), 100)
145

Поєднайте вищезазначене, щоб вирівняти список списків:

>>> sum([[1, 2], [3, 4]], [])
[1, 2, 3, 4]

Тепер ви можете визначити flatmap:

>>> def flatmap(f, seq):
...   return sum([f(s) for s in seq], [])
... 
>>> flatmap(range, [1,2,3])
[0, 0, 1, 0, 1, 2]

Редагувати: Я щойно побачив критику в коментарях щодо іншої відповіді, і, мабуть, правильно, що Python без потреби будуватиме та збиратиме багато менших списків за допомогою цього рішення. Тож найкраще, що можна сказати про це, це те, що це дуже просто і стисло, якщо ви звикли до функціонального програмування :-)


12
import itertools
x=[['b11','b12'],['b21','b22'],['b31']]
y=list(itertools.chain(*x))
print y

itertools працюватиме з python2.3 і вище


8
subs = []
map(subs.extend, (os.listdir(d) for d in dirs))

(але відповідь Мурахи краща; +1 для нього)


Використовуючи зменшити (або суму, що економить багато символів та імпорт ;-), це просто неправильно - ви продовжуєте марно відкидати старі списки, щоб створити новий для кожного d. @Ants має правильну відповідь (розумно @Steve прийняти це!).
Alex Martelli

Взагалі не можна сказати, що це погане рішення. Це залежить від того, чи є продуктивність навіть проблемою. Просте - краще, якщо немає причин для оптимізації. Ось чому метод зменшення може бути найкращим для багатьох проблем. Наприклад, у вас є повільна функція, яка створює список із декількох сотень об'єктів. Ви хочете пришвидшити його за допомогою багатопроцесорної функції "map". Отже, ви створюєте 4 процеси та використовуєте функцію зменшення, щоб зрівняти їх. У цьому випадку функція зменшення чудова і дуже читається. Тим не менш, добре, що ви вказали, чому це може бути неоптимальним. Але це не завжди неоптимально.
Dave31415

4

Ви можете спробувати itertools.chain(), як це:

import itertools
import os
dirs = ["c:\\usr", "c:\\temp"]
subs = list(itertools.chain(*[os.listdir(d) for d in dirs]))
print subs

itertools.chain()повертає ітератор, отже, перехід до list().


3

Google запропонував мені наступне рішення:

def flatten(l):
   if isinstance(l,list):
      return sum(map(flatten,l))
   else:
      return l

2
Було б трохи краще, якби він також обробляв вирази генератора, і було б набагато краще, якби ви пояснили, як його використовувати ...
ефеміент



0
If listA=[list1,list2,list3]
flattened_list=reduce(lambda x,y:x+y,listA)

Це буде робити.


Це дуже неефективне рішення, якщо підсписки великі. +Оператор між двома списками представляє собою О (п + т)
juanpa.arrivillaga
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.