【问题标题】:Transpose/Unzip Function (inverse of zip)?转置/解压缩功能(zip的倒数)?
【发布时间】:2010-09-06 08:50:37
【问题描述】:

我有一个包含 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])

有没有内置函数可以做到这一点?

【问题讨论】:

标签: python list matrix transpose


【解决方案1】:

虽然 numpy 数组和 pandas 可能更可取,但此函数在以 unzip(args) 调用时模仿 zip(*args) 的行为。

允许生成器(如 Python 3 中 zip 的结果)在迭代值时作为 args 传递。

def unzip(items, cls=list, ocls=tuple):
    """Zip function in reverse.

    :param items: Zipped-like iterable.
    :type  items: iterable

    :param cls: Container factory. Callable that returns iterable containers,
        with a callable append attribute, to store the unzipped items. Defaults
        to ``list``.
    :type  cls: callable, optional

    :param ocls: Outer container factory. Callable that returns iterable
        containers. with a callable append attribute, to store the inner
        containers (see ``cls``). Defaults to ``tuple``.
    :type  ocls: callable, optional

    :returns: Unzipped items in instances returned from ``cls``, in an instance
        returned from ``ocls``.
    """
    # iter() will return the same iterator passed to it whenever possible.
    items = iter(items)

    try:
        i = next(items)
    except StopIteration:
        return ocls()

    unzipped = ocls(cls([v]) for v in i)

    for i in items:
        for c, v in zip(unzipped, i):
            c.append(v)

    return unzipped

要使用列表硬币容器,只需运行unzip(zipped),as

unzip(zip(["a","b","c"],[1,2,3])) == (["a","b","c"],[1,2,3])

要使用双端队列或其他任何带有append 的容器,请传递一个工厂函数。

from collections import deque

unzip([("a",1),("b",2)], deque, list) == [deque(["a","b"]),deque([1,2])]

(装饰cls 和/或main_cls 以微管理容器初始化,如上面最后的断言语句所示。)

【讨论】:

    【解决方案2】:

    考虑使用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]]     
    

    【讨论】:

      【解决方案3】:

      天真的方法

      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 对应于i-th 可迭代的j-th 元素,

      在应用transpose_finite_iterable 之后,我们得到

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

      a_ij == j, n == 2n == 2 的 Python 示例

      >>> 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 function 的文档之后,有一个 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 decorator like 定义通用函数来处理可迭代的迭代,其中一个是有限的,另一个可能是无限的

      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)
      

      在有限非空可迭代的二元运算符类中,它可以被认为是它自己的逆(数学家称这种函数"involutions")。


      作为singledispatching 的奖励,我们可以处理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 返回迭代器,如果有人想在 OP 中拥有 lists 中的 tuple - 这可以另外使用 map built-in function like 进行

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

      广告

      我已经从0.5.0 版本向lz package 添加了通用解决方案,可以像这样使用

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

      附言

      没有解决方案(至少显而易见)来处理潜在无限迭代的潜在无限迭代,但这种情况不太常见。

      【讨论】:

        【解决方案4】:

        这只是另一种方法,但它对我帮助很大,所以我在这里写它:

        拥有这个数据结构:

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

        在我看来,解压并返回原始文件的更 Pythonic 的方式是:

        x,y=zip(*XY)
        

        但这会返回一个元组,所以如果你需要一个列表,你可以使用:

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

        【讨论】:

          【解决方案5】:

          虽然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 集合可能更有意义。

          【讨论】:

            【解决方案6】:

            前面的答案都有效地提供了所需的输出,即列表元组,而不是元组列表。对于前者,您可以使用tuplemap。区别如下:

            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,您需要将结果传递给诸如 listtuple 之类的函数以耗尽迭代器。对于内存效率高的迭代器,您可以省略外部的 listtuple 调用以获取各自的解决方案。

            【讨论】:

              【解决方案7】:

              因为它返回元组(并且可以使用大量内存),所以 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
              

              【讨论】:

              • 连续重新创建元组对我来说似乎效率不高,但您可以使用可以预分配内存的双端队列扩展这种方法。
              【解决方案8】:

              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(转换为元组之后),因此无需担心参数数量太大。

              【讨论】:

              • 哦,要是这么简单就好了。以这种方式解压缩zip([], []) 不会得到[], []。它让你[]。要是……
              • 这在 Python3 中不起作用。见:stackoverflow.com/questions/24590614/…
              • @Tommy 这是不正确的。 zip 在 Python 3 中的工作方式完全相同,只是它返回的是迭代器而不是列表。为了获得与上面相同的输出,您只需将 zip 调用包装在一个列表中:list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)])) 将输出 [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
              • 注意:很长的列表可能会遇到内存和性能问题。
              • @JohnP: lists 很好。但是,如果您尝试一次实现全部结果(通过 listifying zip 的结果),您可能会使用大量内存(因为 all tuples 必须是一次创建)。如果您可以在没有listifying 的情况下迭代zip 的结果,您将节省大量内存。唯一的另一个问题是输入是否有很多元素;代价是它必须将它们全部解包为参数,并且zip 将需要为所有它们创建和存储迭代器。这只是 very long lists 的真正问题(想想数十万或更多元素)。
              【解决方案9】:
              >>> 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)]
              

              解压这两个列表。

              【讨论】:

              • 我认为这是最准确的答案,因为正如问题所要求的那样,它实际上返回了一对列表(而不是元组列表)。
              【解决方案10】:

              我喜欢在我的程序中使用zip(*iterable)(这是您要查找的代码):

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

              我发现unzip 更具可读性。

              【讨论】:

                【解决方案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), ('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() 稍微快一点。

                【讨论】:

                • 你也可以使用izip_longest
                • python3 用户称为zip_longest
                • @GrijeshChauhan 我知道这真的很老了,但它是一个奇怪的内置功能:docs.python.org/2/library/functions.html#map "如果 function 为 None,则假定身份函数;如果有多个参数,则 map() 返回由元组组成的列表,其中包含来自所有可迭代对象的相应项(一种转置操作)。可迭代参数可以是序列或任何可迭代对象;结果始终是列表。"
                【解决方案12】:

                你也可以

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

                在您请求每个元素之前,生成器不会遍历列表,但另一方面,它们会保留对原始列表的引用。

                【讨论】:

                • “特别是如果 Python 擅长不扩展列表推导,除非需要。”嗯...通常,列表推导会立即扩展 - 还是我有什么问题?
                • @glglgl:不,你可能是对的。我只是希望未来的某个版本可能会开始做正确的事情。 (改变不是不可能,需要改变的副作用语义可能已经不鼓励了。)
                • 你希望得到的是一个生成器表达式——它已经存在了。
                • 这并不比zip(*x) 版本“扩展得更好”。 zip(*x) 只需要循环一次,不会用完栈元素。
                • 它是否“更好地扩展”取决于原始数据与转置数据相比的生命周期。这个答案只比使用zip 更好,如果用例是转置的数据被立即使用并丢弃,而原始列表在内存中的保留时间更长。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-10-13
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多