Geçiş / Sıkıştırma İşlevi (zip'in tersi)?


505

Ben 2 maddelik tuples bir listesi var ve ben ilk her tuple ilk öğeyi içeren ve ikinci liste ikinci öğeyi tutan 2 listeye dönüştürmek istiyorum.

Örneğin:

original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
# and I want to become...
result = (['a', 'b', 'c', 'd'], [1, 2, 3, 4])

Bunu yapan yerleşik bir işlev var mı?


6
Büyük cevaplar aşağıda, ama aynı zamanda bakmak numpy en devrik
opyate

3
Liste yerine jeneratörlerle aynı şeyi yapmak için bu güzel cevaba bakın: nasıl yapılır-yineleyici-yineleyici
YvesgereY

Yanıtlar:


778

zipkendi tersidir! Özel * operatörünü kullanmanız şartıyla.

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

Bunun nasıl çalıştığı şu zipargümanlarla çağrılır:

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

… Bağımsız değişkenlerin zipdoğrudan aktarılması dışında (bir gruba dönüştürüldükten sonra), bu yüzden argüman sayısının çok büyük olması konusunda endişelenmenize gerek yoktur.


20
Sadece bu kadar basit olsaydı. zip([], [])Bu şekilde unzip etmek sizi alamaz [], []. Seni alır []. Sadece ...
user2357112 Monica

4
Bu Python3'te çalışmaz. Bakınız: stackoverflow.com/questions/24590614/…
Tommy

31
@Tommy Bu yanlış. zipliste yerine yineleyici döndürmesi dışında Python 3'te tamamen aynı şekilde çalışır. Yukarıdaki ile aynı çıkışı elde etmek için sadece zip çağrısını bir listeye sarmanız gerekir: list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))will output[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes

4
uyarı: bellek ve performans sorunlarını çok uzun listelerle karşılayabilirsiniz.
Laurent LAPORTE

1
@JohnP: lists iyi. Eğer (bütün seferde tam bir sonuç gerçekleştirmek çalışırsanız Ama listsonucunu ifying zip) (çünkü, sen çok fazla bellek kullanıyor olabilir tümtuple ler seferde oluşturulması gerekir). Ifying zipolmadan sonucunu yineleyebilirseniz list, çok fazla bellek tasarrufu yapacaksınız. Diğer tek endişe, girdinin birçok unsuru olması; maliyet, hepsini argüman olarak açması zipve hepsi için yineleyiciler oluşturması ve saklaması gerekmesidir. Bu sadece çok uzun lists ile ilgili gerçek bir sorundur (yüz binlerce veya daha fazla unsur düşünün).
ShadowRanger

29

Ayrıca yapabilirsin

result = ([ a for a,b in original ], [ b for a,b in original ])

Daha iyi ölçeklenmelidir. Özellikle Python, gerekmedikçe liste kavrayışlarını genişletmemekte başarılı olursa.

(Bu arada, olduğu gibi bir tuples listesi yerine 2-tuple (çift) liste zipyapar.)

Gerçek listeler yerine jeneratörler uygunsa, bunu yaparsınız:

result = (( a for a,b in original ), ( b for a,b in original ))

Jeneratörler, siz her bir öğeyi talep edene kadar listede gezinmezler, ancak öte yandan, orijinal listeye referanslar tutarlar.


8
Diyerek şöyle devam etti: "Özellikle Python, gerekmedikçe liste kavrayışlarını genişletmemede iyiyse." mmm ... normalde, liste kavrayışları hemen genişletilir - yoksa yanlış bir şey alır mıyım?
glglgl

1
@glglgl: Hayır, muhtemelen haklısın. Gelecekteki bir sürümün doğru şeyi yapmaya başlayabileceğini umuyordum. (Değiştirmek imkansız değil, değişikliklere ihtiyaç duyan yan etki semantiği muhtemelen cesaretini kırdı.)
Anders Eurenius

9
Almayı umduğunuz şey, zaten var olan bir jeneratör ekspresyondur.
glglgl

12
Bu zip(*x)sürümden daha iyi ölçeklenmez . zip(*x)döngüden yalnızca bir geçiş gerektirir ve yığın öğelerini kullanmaz.
habnabit

1
"Daha iyi ölçeklendirilip" ölçeklenmemesi, aktarılan verilere kıyasla orijinal verilerin yaşam döngüsüne bağlıdır. Bu yanıt, yalnızca zipkullanım örneği, aktarılan verilerin hemen kullanılması ve atılması, orijinal listeler daha uzun süre bellekte kalması durumunda kullanmaktan daha iyidir .
Ekevoo

21

Aynı uzunlukta olmayan listeleriniz varsa Patricks yanıtına göre zip kullanmak istemeyebilirsiniz. Bu çalışıyor:

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

Ancak farklı uzunluk listelerinde, zip, her öğeyi en kısa listenin uzunluğuna göre keser:

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

Boş sonuçları Hiçbiri ile doldurmak için işlevi olmayan bir harita kullanabilirsiniz:

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

zip () biraz daha hızlı.


4
Ayrıca kullanabilirsinizizip_longest
Marcin

3
zip_longestPython3 kullanıcıları olarak bilinir.
zezollo

1
@GrijeshChauhan Bunun gerçekten eski olduğunu biliyorum, ancak garip bir yerleşik özellik var: docs.python.org/2/library/functions.html#map "İşlev Yok ise, kimlik işlevi varsayılır; birden fazla argüman varsa, map (), tüm yinelenebilir öğelerden karşılık gelen öğeleri içeren tupleslerden oluşan bir liste döndürür (bir tür transpoze işlemi). Yinelenebilir argümanlar bir dizi veya yinelenebilir herhangi bir nesne olabilir; sonuç her zaman bir listedir. "
cactus1

18

zip(*iterable)Programlarımda (aradığınız kod parçası) kullanmayı seviyorum :

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

unzipDaha okunabilir buluyorum .


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

Soruda olduğu gibi bir liste listesi verir.

list1, list2 = [list(tup) for tup in zip(*original)]

İki listeyi paketinden çıkarır.


8

Saf yaklaşım

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

gibi gösterilebilen (potansiyel olarak sonsuz) tekrarlanabilirlerin sonlu yinelenebilir (örneğin list/ tuple/ gibi diziler) için iyi çalışırstr

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

nerede

  • n in ℕ,
  • a_ij-Tekrarlanabilir olanın- jelementine karşılık gelir i,

ve uyguladıktan sonra transpose_finite_iterablealdığımız

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

Böyle bir durumun Python örneği 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)

Ancak transpose_finite_iterableorijinal yapısına dönmek için tekrar kullanamayız iterableçünkü resultsonsuz yinelenebilir yinelenebilir ( tuplebizim durumumuzda):

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

Peki bu dava ile nasıl başa çıkabiliriz?

... ve işte geliyor deque

itertools.teeİşlev belgelerine baktıktan sonra , bazı değişikliklerle durumumuzda yardımcı olabilecek Python tarifi var

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

Hadi kontrol edelim

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

sentez

Şimdi bunlardan biri sonlu olan ve diğeri potansiyel olarak functools.singledispatchdekoratör gibi yinelenebilir tekrarlanabilirlerle çalışmak için genel işlevi tanımlayabiliriz.

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)

ikili operatörler sınıfında sonlu boş olmayan yinelenebilirler üzerinden kendi tersi olarak düşünülebilen (matematikçiler bu tür işlevleri " katılımlar" olarak adlandırır).


Bir avantaj olarak singledispatchbiz işleyebilir ing numpygibi diziler

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

ve sonra onu

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

Not

Yana transposebirisi bir olmasını istiyorsa döner Yineleyiciler ve tuplebir listOP gibi s - bununla ayrıca yapılabilir mapdahili fonksiyonu gibi

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

İlan

Ben genelleştirilmiş çözüm ekledik lzpaketinden gelen 0.5.0gibi kullanılabilir sürümü

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

Potansiyel olarak sonsuz tekrarlanabilir potansiyel tekrarlanabilir işlemek için (en azından açık) bir çözüm yoktur, ancak bu durum daha az yaygındır.


4

Bunu yapmanın başka bir yolu var ama bana çok yardımcı oldu, bu yüzden buraya yazıyorum:

Bu veri yapısına sahip olmak:

X=[1,2,3,4]
Y=['a','b','c','d']
XY=zip(X,Y)

Sonuçlanan:

In: XY
Out: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

Ben onu açmak ve orijinaline geri dönmek için daha pythonic yolu bu bence:

x,y=zip(*XY)

Ama bu bir demet döndürür, böylece bir listeye ihtiyacınız varsa kullanabilirsiniz:

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

3

More_itertools.unzip kullanmayı düşünün :

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

1

Tuples döndürdüğünden (ve tonlarca bellek kullanabildiğinden), zip(*zipped)hile bana göre daha zekice görünüyor.

İşte size fermuarın tersini verecek bir işlev.

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

Tuples'i sürekli olarak yeniden yaratmak benim için o kadar etkili görünmüyor, ancak bu yaklaşımı hafızayı önceden konumlandırabilen deques kullanarak genişletebilirsiniz.
Charlie Clark

0

Önceki cevapların hiçbiri , bir grup listesi yerine bir liste listesi olan gerekli çıktıyı verimli bir şekilde sağlamaz . İlk amaçla kullanabilirsiniz ile . Fark şu: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])

Ayrıca, önceki çözümlerin çoğunda zipyineleyici yerine bir liste döndüren Python 2.7 varsayılmaktadır .

Python 3.x için, sonucu yineleyici gibi bir işleve geçirmeniz listveya tupleyinelemeyi tüketmeniz gerekir. Bellek tasarruflu yineleyiciler için dış kısmı atlayabilir listve tupleilgili çözümleri arayabilirsiniz .


0

zip(*seq)Çok yararlı olsa da, iletilecek bir dizi değer oluşturacağı için çok uzun diziler için uygun olmayabilir. Örneğin, bir milyondan fazla girişi olan bir koordinat sistemi ile çalışıyorum ve oluşturmak için daha hızlı buluyorum dizileri doğrudan.

Genel bir yaklaşım şöyle olacaktır:

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)

Ancak, sonuçla ne yapmak istediğinize bağlı olarak, koleksiyon seçimi büyük bir fark yaratabilir. Gerçek kullanım durumumda, setler kullanmak ve dahili döngü kullanmamak, diğer tüm yaklaşımlardan çok daha hızlıdır.

Diğerlerinin de belirttiği gibi, bunu veri kümeleriyle yapıyorsanız, bunun yerine Numpy veya Pandas koleksiyonlarını kullanmak mantıklı olabilir.


0

Numpy diziler ve pandalar tercih edilebilirken, bu işlev zip(*args)olarak adlandırıldığında davranışını taklit eder unzip(args).

Jeneratörlerin argsdeğerler arasında yinelendiği gibi geçirilmesine izin verir . Konteyner başlatmayı süsleyin clsve / veya main_clsmikro yönetmek için.

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

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

    :param cls: Callable that returns iterable with callable append attribute.
        Defaults to `list`.
    :type  cls: callable, optional

    :param main_cls: Callable that returns iterable with callable append
        attribute. Defaults to `tuple`.
    :type  main_cls: callable, optional

    :returns: Unzipped items in instances returned from `cls`, in an instance
        returned from `main_cls`.

    :Example:

        assert unzip(zip(["a","b","c"],[1,2,3])) == (["a","b",c"],[1,2,3])
        assert unzip([("a",1),("b",2),("c",3)]) == (["a","b","c"],[1,2,3])
        assert unzip([("a",1)], deque, list) == [deque(["a"]),deque([1])]
        assert unzip((["a"],["b"]), lambda i: deque(i,1)) == (deque(["b"]),)
    """
    items = iter(items)

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

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

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

    return unzipped
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.