Naiver Ansatz
def transpose_finite_iterable(iterable):
return zip(*iterable) # `itertools.izip` for Python 2 users
funktioniert gut für endliche iterierbare (z. B. Sequenzen wie list
/ tuple
/ str
) von (möglicherweise unendlichen) iterablen, die wie folgt dargestellt werden können
| |a_00| |a_10| ... |a_n0| |
| |a_01| |a_11| ... |a_n1| |
| |... | |... | ... |... | |
| |a_0i| |a_1i| ... |a_ni| |
| |... | |... | ... |... | |
wo
n in ℕ
,
a_ij
entspricht dem j
-ten Element des i
-ten iterierbaren,
und nach der Bewerbung transpose_finite_iterable
bekommen wir
| |a_00| |a_01| ... |a_0i| ... |
| |a_10| |a_11| ... |a_1i| ... |
| |... | |... | ... |... | ... |
| |a_n0| |a_n1| ... |a_ni| ... |
Python-Beispiel für einen solchen Fall a_ij == j
, in demn == 2
>>> from itertools import count
>>> iterable = [count(), count()]
>>> result = transpose_finite_iterable(iterable)
>>> next(result)
(0, 0)
>>> next(result)
(1, 1)
Aber wir können nicht transpose_finite_iterable
wieder verwenden, um zur Struktur des Originals zurückzukehren, iterable
da result
es sich um eine unendliche Iteration endlicher Iterablen handelt ( tuple
in unserem Fall s):
>>> transpose_finite_iterable(result)
... hangs ...
Traceback (most recent call last):
File "...", line 1, in ...
File "...", line 2, in transpose_finite_iterable
MemoryError
Wie können wir mit diesem Fall umgehen?
... und hier kommt die deque
Nachdem wir uns die itertools.tee
Funktionsdokumente angesehen haben , gibt es ein Python-Rezept, das mit einigen Änderungen in unserem Fall helfen kann
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))
Lass uns nachsehen
>>> 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
Synthese
Jetzt können wir allgemeine Funktionen für die Arbeit mit Iterablen von Iterablen definieren, von denen einige endlich und andere potenziell unendlich sind, wenn functools.singledispatch
Dekorateure wie verwendet werden
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)
Dies kann als seine eigene Umkehrung (Mathematiker nennen diese Art von Funktionen "Involutionen" ) in der Klasse von Binäroperatoren über endliche nicht leere Iterablen betrachtet werden.
Als Bonus singledispatch
können wir mit numpy
Arrays wie umgehen
import numpy as np
...
transpose.register(np.ndarray, np.transpose)
und dann benutze es gerne
>>> array = np.arange(4).reshape((2,2))
>>> array
array([[0, 1],
[2, 3]])
>>> transpose(array)
array([[0, 2],
[1, 3]])
Hinweis
Da transpose
gibt Iteratoren zurück und wenn jemand ein tuple
von list
s wie in OP haben möchte - kann dies zusätzlich mit map
eingebauter Funktion wie gemacht werden
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> tuple(map(list, transpose(original)))
(['a', 'b', 'c', 'd'], [1, 2, 3, 4])
Werbung
Ich habe verallgemeinerte Lösung hinzugefügt lz
Paket aus 0.5.0
Version , die verwendet werden kann , wie
>>> 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
Es gibt keine (zumindest offensichtliche) Lösung für die Behandlung von potenziell unendlich iterierbaren oder potenziell unendlich iterierbaren Elementen, aber dieser Fall ist weniger häufig.