Функция транспонирования/разархивирования (обратная молния)?


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

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

вот как вы можете транспонировать кортеж 2x4 в кортеж 4x2.

 >>> tuple(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)])) 

результат

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]