ฟังก์ชัน Transpose / Unzip (ตรงกันข้ามของ zip)


505

ฉันมีรายการ tuples 2 รายการและฉันต้องการแปลงเป็น 2 รายการโดยที่รายการแรกมีรายการแรกในแต่ละ tuple และรายการที่สองถือรายการที่สอง

ตัวอย่างเช่น:

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

มีฟังก์ชั่นบิวอินที่ทำเช่นนั้น?


6
คำตอบที่ดีด้านล่าง แต่ให้ดูที่การโยกย้ายของ numpy
opyate

3
ดูคำตอบที่ดีที่จะทำเช่นเดียวกันกับเครื่องกำเนิดไฟฟ้าแทนรายการ: วิธีการ unzip-an-iterator
YvesgereY

คำตอบ:


778

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โดยตรง (หลังจากถูกแปลงเป็นทูเปิล) ดังนั้นไม่จำเป็นต้องกังวลเกี่ยวกับจำนวนการโต้เถียงที่ใหญ่ขึ้น


20
โอ้ถ้ามันเรียบง่ายมาก การขยายไฟล์zip([], [])ด้วยวิธีนี้ไม่ได้[], []ผล []มันทำให้คุณได้รับ ถ้าเพียง ...
user2357112 รองรับ Monica

4
สิ่งนี้ไม่ทำงานใน Python3 ดู: stackoverflow.com/questions/24590614/…
Tommy

31
@Tommy นี้ไม่ถูกต้อง zipทำงานเหมือนกันทุกประการใน Python 3 ยกเว้นว่าจะส่งคืนตัววนซ้ำแทนรายการ เพื่อให้ได้ผลลัพธ์เดียวกันกับข้างบนคุณเพียงแค่ต้องการตัดการเรียก zip ในรายการ: list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))จะส่งออก[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes

4
แจ้งให้ทราบล่วงหน้า: คุณสามารถพบปัญหาหน่วยความจำและประสิทธิภาพด้วยรายการที่ยาวมาก
Laurent LAPORTE

1
@JohnP: listสบายดี แต่ถ้าคุณพยายามที่จะตระหนักถึงผลเต็มรูปแบบทั้งหมดในครั้งเดียว (โดยlistifying ผลมาจากการzip) คุณอาจใช้หน่วยความจำมาก (เพราะทุกtuple s จะต้องสร้างขึ้นในครั้งเดียว) หากคุณสามารถทำซ้ำได้มากกว่าผลลัพธ์ที่ได้zipโดยไม่ต้องสนใจlistคุณจะประหยัดหน่วยความจำได้มาก ข้อกังวลอื่น ๆ คือถ้าอินพุตมีองค์ประกอบหลายอย่าง ค่าใช้จ่ายที่มีนั้นจะต้องแตกพวกเขาทั้งหมดเป็นข้อโต้แย้งและzipจะต้องสร้างและเก็บตัววนซ้ำสำหรับพวกเขาทั้งหมด นี่เป็นเพียงปัญหาที่แท้จริงของ s ที่มีความยาวมากlist (คิดว่าองค์ประกอบนับแสนหรือมากกว่านั้น)
ShadowRanger

29

คุณสามารถทำได้

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

มันควรจะปรับขนาดได้ดีขึ้น โดยเฉพาะอย่างยิ่งถ้า Python ใช้ดีในการไม่เพิ่มความเข้าใจในรายการนอกจากจำเป็น

(โดยบังเอิญมันทำให้รายการ 2-tuple (คู่) แทนที่จะเป็นรายการของ tuples เช่นzipนั้น)

หากเครื่องกำเนิดไฟฟ้าแทนรายการจริงไม่เป็นไรสิ่งนี้จะทำ:

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

เครื่องปั่นไฟจะไม่แทะเล็มในรายการจนกว่าคุณจะถามแต่ละองค์ประกอบ แต่ในทางกลับกันพวกมันจะทำการอ้างอิงไปยังรายการต้นฉบับ


8
"โดยเฉพาะอย่างยิ่งถ้า Python ใช้ดีในการไม่เพิ่มความเข้าใจในรายการนอกจากจำเป็น" mmm ... โดยปกติความเข้าใจในรายการจะถูกขยายออกทันที - หรือฉันทำอะไรผิดพลาด?
glglgl

1
@glglgl: ไม่คุณอาจพูดถูก ฉันแค่หวังว่าบางรุ่นในอนาคตอาจเริ่มทำสิ่งที่ถูกต้อง (มันเป็นไปไม่ได้ที่จะเปลี่ยนแปลงความหมายของผลข้างเคียงที่จำเป็นต้องมีการเปลี่ยนแปลงอาจจะหมดกำลังใจไปแล้ว)
Anders Eurenius

9
สิ่งที่คุณหวังว่าจะได้รับคือการแสดงออกของเครื่องกำเนิดไฟฟ้า - ซึ่งมีอยู่แล้ว
glglgl

12
สิ่งนี้ไม่ได้ 'ปรับขนาดได้ดีกว่าzip(*x)รุ่น' zip(*x)ต้องการเพียงหนึ่งการส่งผ่านลูปและไม่ใช้อิลิเมนต์สแต็กขึ้น
habnabit

1
ไม่ว่าจะเป็นการ "ปรับขนาดได้ดีกว่า" หรือไม่นั้นขึ้นอยู่กับวงจรชีวิตของข้อมูลต้นฉบับเมื่อเทียบกับข้อมูลที่ถูกย้าย คำตอบนี้จะดีกว่าการใช้zipถ้าใช้กรณีที่ข้อมูล transposed จะถูกใช้และทิ้งทันทีในขณะที่รายการเดิมอยู่ในหน่วยความจำอีกต่อไป
Ekevoo

21

หากคุณมีรายการที่มีความยาวไม่เท่ากันคุณอาจไม่ต้องการใช้ zip ตามคำตอบของ Patricks งานนี้:

>>> 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 () นั้นเร็วกว่าเล็กน้อย


4
คุณสามารถใช้izip_longest
Marcin

3
รู้จักกันในzip_longestนามผู้ใช้ python3
zezollo

1
@GrijeshChauhan ฉันรู้ว่ามันเก่าจริง ๆ แต่มันเป็นคุณสมบัติแปลก ๆ ที่มีอยู่ในตัว: docs.python.org/2/library/functions.html#map "ถ้าฟังก์ชั่นไม่มี None จะมีการสันนิษฐานฟังก์ชันหากมีหลายอาร์กิวเมนต์ map () ส่งคืนรายการที่ประกอบด้วย tuples ที่มีรายการที่สอดคล้องกันจาก iterables ทั้งหมด (ชนิดของการดำเนินการแปลงข้อมูล) อาร์กิวเมนต์ iterable อาจเป็นลำดับหรือวัตถุใด ๆ ที่ซ้ำได้ผลลัพธ์จะเป็นรายการ "
cactus1

18

ฉันชอบที่จะใช้zip(*iterable)(ซึ่งเป็นรหัสที่คุณต้องการ) ในโปรแกรมของฉันดังนี้:

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

ฉันพบว่าunzipอ่านได้มากขึ้น


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

ให้สิ่งอันดับสองอย่างในคำถาม

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

คลายรายการทั้งสอง


8

วิธีการไร้เดียงสา

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

ใช้งานได้ดีสำหรับ จำกัด iterable (เช่นลำดับเช่นlist/ tuple/ str) ของ iterables (อาจไม่มีที่สิ้นสุด) ซึ่งสามารถแสดงได้เช่น

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

ที่ไหน

  • n in ℕ,
  • a_ijสอดคล้องกับjองค์ประกอบi-th ของ-th iterable

และหลังจากการสมัคร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 == 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มีการวนซ้ำ iterables ที่ไม่สิ้นสุด ( 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 ของ 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)

ซึ่งถือได้ว่าเป็นตัวผกผันของมันเอง (นักคณิตศาสตร์เรียกฟังก์ชันนี้ว่า "involutions" ) ในชั้นเรียนของตัวดำเนินการแบบไบนารีบนการ จำกัด iterables ที่ไม่ว่างเปล่า


เป็นโบนัสในการรับเข้า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

ไม่มีวิธีแก้ปัญหา (อย่างน้อยก็ชัดเจน) สำหรับการจัดการ iterable ที่ไม่มีขีด จำกัด ของ iterable ที่อาจเกิดขึ้นได้ แต่กรณีนี้พบได้น้อยกว่า


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 เพิ่มเติมเพื่อ unzip มันและกลับไปที่เดิมคืออันนี้ในความคิดของฉัน:

x,y=zip(*XY)

แต่สิ่งนี้จะคืนค่า tuple ดังนั้นหากคุณต้องการรายการคุณสามารถใช้

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


1

เนื่องจากมันคืนค่า tuples (และสามารถใช้หน่วยความจำจำนวนมากได้) zip(*zipped)เคล็ดลับดูฉลาดกว่าประโยชน์สำหรับฉัน

นี่คือฟังก์ชั่นที่จะให้ค่าผกผันกับคุณ

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

การสร้างสิ่งอันดับอย่างต่อเนื่องดูเหมือนจะไม่มีประสิทธิภาพสำหรับฉัน แต่คุณสามารถขยายวิธีการนี้ด้วยการใช้ deques ซึ่งสามารถจัดสรรหน่วยความจำล่วงหน้าได้
Charlie Clark

0

ไม่มีคำตอบก่อนหน้านี้ได้อย่างมีประสิทธิภาพให้การส่งออกที่จำเป็นซึ่งเป็นtuple ของรายการมากกว่ารายการ tuples สำหรับอดีตคุณสามารถใช้กับ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

แม้ว่าzip(*seq)มันจะมีประโยชน์มาก แต่ก็อาจจะไม่เหมาะกับการวนเวียนที่ยาวมากเพราะมันจะสร้างค่า tuple ที่จะส่งผ่านตัวอย่างเช่นฉันได้ทำงานกับระบบพิกัดที่มีมากกว่าหนึ่งล้านรายการและพบว่ามันสร้างเร็วขึ้น ลำดับโดยตรง

วิธีการทั่วไปจะเป็นดังนี้:

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

ในขณะที่อาร์เรย์ numpy และหมีแพนด้าอาจจะ preferrable ฟังก์ชันนี้เลียนแบบพฤติกรรมของเมื่อเรียกว่าเป็นzip(*args)unzip(args)

ช่วยให้เครื่องกำเนิดไฟฟ้าจะถูกส่งผ่านตามargsที่ทำซ้ำผ่านค่า ตกแต่งclsและ / หรือmain_clsไมโครจัดการการเริ่มต้นคอนเทนเนอร์

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.