سؤال

لدي قائمة مكونة من صفين من العناصر وأرغب في تحويلهما إلى قائمتين حيث تحتوي الأولى على العنصر الأول في كل مجموعة والقائمة الثانية تحتوي على العنصر الثاني.

على سبيل المثال:

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

هو - هي يجب مقياس أفضل.خاصة إذا نجحت بايثون في عدم توسيع فهم القائمة ما لم تكن هناك حاجة لذلك.

(بالمناسبة، فإنه يقوم بإنشاء صفين (زوج) من القوائم، بدلاً من قائمة الصف، مثل zip يفعل.)

إذا كانت المولدات بدلاً من القوائم الفعلية مناسبة، فسيؤدي ذلك إلى ما يلي:

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

لا تقوم المولدات بقراءة القائمة حتى تسأل عن كل عنصر، ولكن من ناحية أخرى، فإنها تحتفظ بمراجع إلى القائمة الأصلية.

إذا كانت لديك قوائم ليست بنفس الطول، فقد لا ترغب في استخدام الرمز البريدي وفقًا لإجابة باتريك.هذا يعمل:

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

لكن هذا يُرجع صفًا لذا إذا كنت بحاجة إلى قائمة يمكنك استخدامها:

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

مثال بايثون لمثل هذه الحالة حيث 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 هو تكرار لانهائي من التكرارات المحدودة (tupleس في حالتنا):

>>> 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 وظيفة, ، هناك وصفة بايثون يمكن أن تساعد في حالتنا مع بعض التعديل

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 مصمم ديكور يحب

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

ملاحظة.

لا يوجد حل (على الأقل واضح) للتعامل مع العناصر التكرارية غير المحدودة المحتملة، ولكن هذه الحالة أقل شيوعًا.

نظرًا لأنه يُرجع صفوفًا (ويمكنه استخدام الكثير من الذاكرة)، فإن 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
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

النتيجة=(['أ'، 'ب'، 'ج'، 'د']، [1، 2، 3، 4])

فكر في استخدام 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]]     

ليس أيا من الإجابات السابقة بكفاءة توفير الإخراج المطلوب، وهو أ مجموعة من القوائم, ، بدلا من قائمة الصفوف.بالنسبة للسابق، يمكنك استخدام 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