Функция транспонирования/разархивирования (обратная молния)?
У меня есть список кортежей из 2 элементов, и я хотел бы преобразовать их в 2 списка, где первый содержит первый элемент в каждом кортеже, а второй список содержит второй элемент.
например:
original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
# and I want to become...
result = (['a', 'b', 'c', 'd'], [1, 2, 3, 4])
есть ли встроенная функция, которая это делает?
11 ответов:
zip
это его собственный обратный! При условии использования специального оператора*.>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]) [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
как это работает, позвонив
zip
аргументы:zip(('a', 1), ('b', 2), ('c', 3), ('d', 4))
... за исключением того, что аргументы передаются в
zip
непосредственно (после преобразования в кортеж), поэтому нет необходимости беспокоиться о том, что количество аргументов становится слишком большим.
вы также можете сделать
result = ([ a for a,b in original ], [ b for a,b in original ])
Это должны шкала лучше. Особенно если Python делает хорошо на не расширяя список понимания, если это не требуется.
(кстати, это делает 2-кортеж (пара) списков, а не список кортежей, как
zip
делает.)Если генераторы вместо реальных списков в порядке, это будет делать это:
result = (( a for a,b in original ), ( b for a,b in original ))
генераторы не жевать через список, пока вы не попросите для каждого элемента, но на с другой стороны, они сохраняют ссылки на исходный список.
Если у вас есть списки, которые не имеют одинаковой длины, вы не можете использовать zip в соответствии с ответом Патрика. Это работает:
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]) [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
но с разными списками длины zip усекает каждый элемент до длины самого короткого списка:
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )]) [('a', 'b', 'c', 'd', 'e')]
вы можете использовать карту без функции, чтобы заполнить пустые результатов нет:
>>> map(None, *[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )]) [('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, None)]
zip () немного быстрее, хотя.
мне нравится использовать
zip(*iterable)
(это кусок кода, который вы ищете) в моих программах, как так:def unzip(iterable): return zip(*iterable)
найти
unzip
более читабельным.
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] >>> tuple([list(tup) for tup in zip(*original)]) (['a', 'b', 'c', 'd'], [1, 2, 3, 4])
дает кортеж списков, как в вопросе.
list1, list2 = [list(tup) for tup in zip(*original)]
распаковывает два списка.
это всего лишь еще один способ сделать это, но это очень помогло мне, поэтому я пишу здесь:
имея такую структуру данных:
X=[1,2,3,4] Y=['a','b','c','d'] XY=zip(X,Y)
в результате:
In: XY Out: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
более питонический способ распаковать его и вернуться к оригиналу-это, на мой взгляд:
x,y=zip(*XY)
но это возвращает кортеж, так что если вам нужен массив, вы можете использовать:
xy=(list(x),list(y))
Так как он возвращает кортежи (и может использовать тонны памяти), то
zip(*zipped)
трюк кажется мне более умным, чем полезным.вот функция, которая на самом деле даст вам обратную zip.
def unzip(zipped): """Inverse of built-in zip function. Args: zipped: a list of tuples Returns: a tuple of lists Example: a = [1, 2, 3] b = [4, 5, 6] zipped = list(zip(a, b)) assert zipped == [(1, 4), (2, 5), (3, 6)] unzipped = unzip(zipped) assert unzipped == ([1, 2, 3], [4, 5, 6]) """ unzipped = () if len(zipped) == 0: return unzipped dim = len(zipped[0]) for i in range(dim): unzipped = unzipped + ([tup[i] for tup in zipped], ) return unzipped
ни один из предыдущих ответов эффективно обеспечить необходимый выход, который является кортеж из списков, а не список кортежей. Для первого, вы можете использовать
tuple
Сmap
. Вот в чем разница:res1 = list(zip(*original)) # [('a', 'b', 'c', 'd'), (1, 2, 3, 4)] res2 = tuple(map(list, zip(*original))) # (['a', 'b', 'c', 'd'], [1, 2, 3, 4])
кроме того, большинство предыдущих решений предполагают Python 2.7, где
zip
возвращает список, а не итератор.Для Python 3.x, вам нужно будет передать результат в функцию, такую как
list
илиtuple
для исчерпания итератора. Для итераторов с эффективной памятью вы можете опустить внешнийlist
иtuple
вызывает соответствующие решения.
пока
zip(*seq)
очень полезно, он может быть непригоден для очень длинных последовательностей, поскольку он создаст кортеж значений, которые будут переданы. Например, я работаю с системой координат с более чем миллионом записей и нахожу ее значительно быстрее для непосредственного создания последовательностей.общий подход будет примерно таким:
from collections import deque seq = ((a1, b1, …), (a2, b2, …), …) width = len(seq[0]) output = [deque(len(seq))] * width # preallocate memory for element in seq: for s, item in zip(output, element): s.append(item)
но, в зависимости от того, что вы хотите сделать с результатом, выбор, коллекция может сделать большую разницу. В моем фактический вариант использования, используя наборы и без внутреннего цикла, заметно быстрее, чем все другие подходы.
и, как отмечали другие, если вы делаете это с наборами данных, вместо этого может иметь смысл использовать коллекции Numpy или Pandas.
другой способ думать о
unzip
илиtranspose
преобразует список строк в список столбцов.pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clements'), ('Schilling','Curt')] first_names, last_names = zip(*pitchers) In [45]: first_names Out[45]: ('Nolan', 'Roger', 'Schilling') In [46]: last_names Out[46]: ('Ryan', 'Clements', 'Curt')