وظيفة التبديل/فك الضغط (عكس الرمز البريدي)؟
سؤال
لدي قائمة مكونة من صفين من العناصر وأرغب في تحويلهما إلى قائمتين حيث تحتوي الأولى على العنصر الأول في كل مجموعة والقائمة الثانية تحتوي على العنصر الثاني.
على سبيل المثال:
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)]