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_ij
correspond au j
-ème élément du i
-ème itérable,
et après avoir appliqué, transpose_finite_iterable
nous 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_iterable
nouveau pour revenir à la structure de l'original iterable
car il result
s'agit d'un itérable infini d'itérables finis ( tuple
s 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.tee
fonction , 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.singledispatch
dé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, singledispatch
nous pouvons gérer des numpy
tableaux 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 transpose
retourne les itérateurs et si quelqu'un veut avoir un tuple
de list
s comme dans OP - cela peut être fait en plus avec une map
fonction 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 lz
package à partir de la 0.5.0
version 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.