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

StackOverflow https://stackoverflow.com/questions/19339

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

IT следует масштабируйся лучше.Особенно, если 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:

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

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

x,y=(list(x),list(y))

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

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

Синтез

Теперь мы можем определить общую функцию для работы с итерациями iterables, одни из которых конечны, а другие потенциально бесконечны, используя 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)]

P.S.

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

Поскольку он возвращает кортежи (и может использовать тонны памяти), 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
original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]

#unzip 
a1 , a2 = zip(*original)
#make tuple with two list
result=(list(a1),list(a2))
result

результат=(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

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

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

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

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

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

Результат

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top