Approche naïve
def transpose_finite_iterable(iterable):
return zip(*iterable) # `itertools.izip` for Python 2 users
fonctionne très bien pour des itérables finis (par exemple des séquences comme list/ tuple/ str) d'itérables (potentiellement infinis) qui peuvent être illustrés comme
| |a_00| |a_10| ... |a_n0| |
| |a_01| |a_11| ... |a_n1| |
| |... | |... | ... |... | |
| |a_0i| |a_1i| ... |a_ni| |
| |... | |... | ... |... | |
où
n in ℕ,
a_ijcorrespond au j-ème élément du i-ème itérable,
et après avoir appliqué, transpose_finite_iterablenous obtenons
| |a_00| |a_01| ... |a_0i| ... |
| |a_10| |a_11| ... |a_1i| ... |
| |... | |... | ... |... | ... |
| |a_n0| |a_n1| ... |a_ni| ... |
Exemple Python d'un tel cas où 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)
Mais nous ne pouvons pas utiliser à transpose_finite_iterablenouveau pour revenir à la structure de l'original iterablecar il results'agit d'un itérable infini d'itérables finis ( tuples dans notre cas):
>>> transpose_finite_iterable(result)
... hangs ...
Traceback (most recent call last):
File "...", line 1, in ...
File "...", line 2, in transpose_finite_iterable
MemoryError
Alors, comment pouvons-nous traiter ce cas?
... et voici deque
Après avoir regardé les documents de itertools.teefonction , il existe une recette Python qui, avec quelques modifications, peut aider dans notre cas
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))
Allons vérifier
>>> 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
Synthèse
Maintenant, nous pouvons définir une fonction générale pour travailler avec des itérables dont certains sont finis et d'autres potentiellement infinis en utilisant un functools.singledispatchdécorateur comme
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)
qui peut être considérée comme son propre inverse (les mathématiciens appellent ce type de fonctions "involutions" ) dans la classe des opérateurs binaires sur des itérables finis non vides.
En prime, singledispatchnous pouvons gérer des numpytableaux comme
import numpy as np
...
transpose.register(np.ndarray, np.transpose)
puis l'utiliser comme
>>> array = np.arange(4).reshape((2,2))
>>> array
array([[0, 1],
[2, 3]])
>>> transpose(array)
array([[0, 2],
[1, 3]])
Remarque
Depuis transposeretourne les itérateurs et si quelqu'un veut avoir un tuplede lists comme dans OP - cela peut être fait en plus avec une mapfonction intégrée comme
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> tuple(map(list, transpose(original)))
(['a', 'b', 'c', 'd'], [1, 2, 3, 4])
Publicité
J'ai ajouté une solution généralisée au lzpackage à partir de la 0.5.0version qui peut être utilisée comme
>>> 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
Il n'y a pas de solution (du moins évidente) pour gérer les itérables potentiellement infinis des itérables potentiellement infinis, mais ce cas est cependant moins courant.