Функция транспонирования / распаковки (обратная сторона zip)?

У меня есть список кортежей из 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])

Есть ли встроенная функция, которая делает это?

21.08.2008 04:29:07
Великолепные ответы ниже, но также посмотрите на транспонирование numpy
opyate 3.03.2015 13:17:26
Смотрите этот хороший ответ, чтобы сделать то же самое с генераторами вместо списка: как распаковать итератор
YvesgereY 5.01.2016 20:31:56
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напрямую (после преобразования в кортеж), поэтому нет необходимости беспокоиться о том, что количество аргументов становится слишком большим.

770
13.06.2017 00:09:24
О, если бы это было так просто. Разархивировать zip([], [])таким способом не получится [], []. Это получает вас []. Если только ...
user2357112 supports Monica 24.02.2014 12:06:58
Это не работает в Python3. Смотрите: stackoverflow.com/questions/24590614/…
Tommy 5.07.2014 21:35:16
@ Томми Это неправильно. zipработает точно так же в Python 3 за исключением того, что он возвращает итератор вместо списка. Чтобы получить тот же вывод, что и выше, вам просто нужно list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes 11.03.2015 14:11:12
обратите внимание: вы можете столкнуться с проблемами памяти и производительности с очень длинными списками.
Laurent LAPORTE 14.10.2016 12:44:09
@JohnP: listвсе хорошо. Но если вы попытаетесь реализовать полный результат все сразу (по listifying результата zip), вы можете использовать много памяти (потому что все , что tupleей необходимо создать сразу). Если вы можете просто перебрать результат zipбез listifying, вы сэкономите много памяти. Единственное другое беспокойство - если вход имеет много элементов; цена его заключается в том, что он должен распаковать их все в качестве аргументов и zipдолжен будет создать и сохранить итераторы для всех них. Это реальная проблема только с очень длинными listсимволами (например, сотнями тысяч элементов или более).
ShadowRanger 5.09.2019 15:05:48

Вы могли бы также сделать

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

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

29
24.08.2008 17:14:46
«Особенно, если Python преуспевает в том, чтобы не расширять список пониманий без необходимости». ммм ... нормально, списки сразу расширяются - или я что-то не так делаю?
glglgl 15.08.2011 19:52:57
@glglgl: Нет, вы, вероятно, правы. Я просто надеялся, что какая-то будущая версия может начать делать правильные вещи. (Это не невозможно изменить, семантика побочных эффектов, которая нуждается в изменениях, вероятно, уже не рекомендуется.)
Anders Eurenius 15.10.2012 12:54:03
То, что вы надеетесь получить, это выражение генератора, которое уже существует.
glglgl 15.10.2012 13:12:19
Это не «масштабируется лучше», чем zip(*x)версия. zip(*x)требуется только один проход через цикл и не использует элементы стека.
habnabit 17.11.2013 16:38:20
Будет ли оно «лучше масштабироваться» или нет, зависит от жизненного цикла исходных данных по сравнению с транспонированными данными. Этот ответ только лучше, чем использование, zipесли в сценарии использования транспонированные данные используются и сразу отбрасываются, тогда как исходные списки остаются в памяти гораздо дольше.
Ekevoo 15.11.2015 06:55:50

Если у вас есть списки, которые не имеют одинаковую длину, вы можете не использовать 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')]

Вы можете использовать карту без функции, чтобы заполнить пустые результаты с помощью None:

>>> map(None, *[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, None)]

Хотя zip () немного быстрее.

21
2.01.2011 12:14:17
Вы также можете использоватьizip_longest
Marcin 26.09.2013 16:52:55
Известен как zip_longestдля пользователей python3.
zezollo 8.03.2016 09:02:13
@GrijeshChauhan Я знаю , что это на самом деле старый, но это странно , встроенная функция: docs.python.org/2/library/functions.html#map «Если функция не является None, функция тождества принимается, если есть несколько аргументов, map () возвращает список, состоящий из кортежей, содержащих соответствующие элементы из всех итераций (разновидность операции транспонирования). Итерируемые аргументы могут быть последовательностью или любым итерируемым объектом; результатом всегда является список. "
cactus1 14.07.2017 19:26:37

Мне нравится использовать zip(*iterable)(это фрагмент кода, который вы ищете) в моих программах так:

def unzip(iterable):
    return zip(*iterable)

Я нахожу unzipболее читабельным.

17
1.03.2014 15:00:15

Это всего лишь другой способ сделать это, но он мне очень помог, поэтому я пишу это здесь:

Имея эту структуру данных:

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)

Но это возвращает кортеж, поэтому, если вам нужен список, вы можете использовать:

x,y=(list(x),list(y))
4
16.11.2018 11:24:11
>>> 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)]

Распаковывает два списка.

12
5.03.2016 11:08:28

Поскольку он возвращает кортежи (и может использовать тонны памяти), 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
1
11.06.2018 13:35:08
Постоянное воссоздание кортежей не кажется мне эффективным, но вы могли бы расширить этот подход, используя запросы, которые могли бы выделять память.
Charlie Clark 26.09.2018 11:17:43

Ни один из предыдущих ответов эффективно не обеспечивает требуемый вывод, который является кортежем списков , а не списком кортежей . Для первого вы можете использовать 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вызовы соответствующих решений.

0
23.08.2018 17:36:23

Хотя 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.

0
26.09.2018 14:08:01

Наивный подход

def transpose_finite_iterable(iterable):
    return zip(*iterable)  # `itertools.izip` for Python 2 users

отлично работает для конечных итерируемых (например, последовательностей типа list/ tuple/ str) (потенциально бесконечных) итерируемых, которые можно проиллюстрировать как

| |a_00| |a_10| ... |a_n0| |
| |a_01| |a_11| ... |a_n1| |
| |... | |... | ... |... | |
| |a_0i| |a_1i| ... |a_ni| |
| |... | |... | ... |... | |

где

  • n in ℕ,
  • a_ijсоответствует -ому jэлементу i-й итерируемой,

и после подачи заявления transpose_finite_iterableмы получаем

| |a_00| |a_01| ... |a_0i| ... |
| |a_10| |a_11| ... |a_1i| ... |
| |... | |... | ... |... | ... |
| |a_n0| |a_n1| ... |a_ni| ... |

Python пример такого случая , когда a_ij == j,n == 2

>>> from itertools import count
>>> iterable = [count(), count()]
>>> result = transpose_finite_iterable(iterable)
>>> next(result)
(0, 0)
>>> next(result)
(1, 1)

Но мы не можем использовать transpose_finite_iterableснова, чтобы вернуться к структуре оригинала, iterableпотому что resultэто бесконечная итерация конечных итераций ( tuples в нашем случае):

>>> transpose_finite_iterable(result)
... hangs ...
Traceback (most recent call last):
  File "...", line 1, in ...
  File "...", line 2, in transpose_finite_iterable
MemoryError

Итак, как мы можем справиться с этим делом?

... и вот идет deque

После того, как мы посмотрим на документы по itertools.teeфункциям , есть рецепт Python, который с некоторыми изменениями может помочь в нашем случае

def transpose_finite_iterables(iterable):
    iterator = iter(iterable)
    try:
        first_elements = next(iterator)
    except StopIteration:
        return ()
    queues = [deque([element])
              for element in first_elements]

    def coordinate(queue):
        while True:
            if not queue:
                try:
                    elements = next(iterator)
                except StopIteration:
                    return
                for sub_queue, element in zip(queues, elements):
                    sub_queue.append(element)
            yield queue.popleft()

    return tuple(map(coordinate, queues))

Давайте проверим

>>> from itertools import count
>>> iterable = [count(), count()]
>>> result = transpose_finite_iterables(transpose_finite_iterable(iterable))
>>> result
(<generator object transpose_finite_iterables.<locals>.coordinate at ...>, <generator object transpose_finite_iterables.<locals>.coordinate at ...>)
>>> next(result[0])
0
>>> next(result[0])
1

Синтез

Теперь мы можем определить общую функцию для работы с итерациями итерируемых, одни из которых конечны, а другие потенциально бесконечны, используя functools.singledispatchдекоратор, такой как

from collections import (abc,
                         deque)
from functools import singledispatch


@singledispatch
def transpose(object_):
    """
    Transposes given object.
    """
    raise TypeError('Unsupported object type: {type}.'
                    .format(type=type))


@transpose.register(abc.Iterable)
def transpose_finite_iterables(object_):
    """
    Transposes given iterable of finite iterables.
    """
    iterator = iter(object_)
    try:
        first_elements = next(iterator)
    except StopIteration:
        return ()
    queues = [deque([element])
              for element in first_elements]

    def coordinate(queue):
        while True:
            if not queue:
                try:
                    elements = next(iterator)
                except StopIteration:
                    return
                for sub_queue, element in zip(queues, elements):
                    sub_queue.append(element)
            yield queue.popleft()

    return tuple(map(coordinate, queues))


def transpose_finite_iterable(object_):
    """
    Transposes given finite iterable of iterables.
    """
    yield from zip(*object_)

try:
    transpose.register(abc.Collection, transpose_finite_iterable)
except AttributeError:
    # Python3.5-
    transpose.register(abc.Mapping, transpose_finite_iterable)
    transpose.register(abc.Sequence, transpose_finite_iterable)
    transpose.register(abc.Set, transpose_finite_iterable)

который можно рассматривать как собственный обратный (математики называют этот тип функций «инволюциями» ) в классе бинарных операторов над конечными непустыми итерациями.


В качестве бонуса singledispatchмы можем обрабатывать numpyмассивы, такие как

import numpy as np
...
transpose.register(np.ndarray, np.transpose)

а затем использовать его как

>>> array = np.arange(4).reshape((2,2))
>>> array
array([[0, 1],
       [2, 3]])
>>> transpose(array)
array([[0, 2],
       [1, 3]])

Заметка

Поскольку transposeвозвращает итераторы , и если кто - то хочет иметь tupleв listс , как в OP - это может быть сделано дополнительно с mapвстроенной функцией , как

>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> tuple(map(list, transpose(original)))
(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

Реклама

Я добавил обобщенное решение для lzпакета из 0.5.0версии, которая может быть использована как

>>> from lz.transposition import transpose
>>> list(map(tuple, transpose(zip(range(10), range(10, 20)))))
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), (10, 11, 12, 13, 14, 15, 16, 17, 18, 19)]

PS

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

8
1.03.2019 19:13:53

Рассмотрите возможность использования more_itertools.unzip :

>>> from more_itertools import unzip
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> [list(x) for x in unzip(original)]
[['a', 'b', 'c', 'd'], [1, 2, 3, 4]]     
2
22.04.2019 17:34:33