J'ai un script Python qui prend en entrée une liste d'entiers, dont j'ai besoin pour travailler avec quatre entiers à la fois. Malheureusement, je n'ai pas le contrôle de l'entrée, ou je la ferais passer sous forme de liste de tuples à quatre éléments. Actuellement, je répète de cette façon:
for i in xrange(0, len(ints), 4):
# dummy op for example code
foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]
Cela ressemble beaucoup à "C-think", ce qui me fait soupçonner qu'il existe une façon plus pythonique de gérer cette situation. La liste est supprimée après l'itération, il n'est donc pas nécessaire de la conserver. Peut-être que quelque chose comme ça serait mieux?
while ints:
foo += ints[0] * ints[1] + ints[2] * ints[3]
ints[0:4] = []
Mais ne se sent pas tout à fait bien. : - /
Question connexe: comment diviser une liste en morceaux de taille égale en Python?
Réponses:
Modifié à partir de la section des recettes des documents itertools de Python :
Exemple
En pseudocode pour garder l'exemple concis.
Remarque: sur Python 2, utilisez
izip_longest
au lieu dezip_longest
.la source
izip_longest
elles seront alimentées en arguments de 256 Ko.None
remplir le dernier morceau?Facile. Facile. Vite. Fonctionne avec n'importe quelle séquence:
la source
itertools
module.chunker
renvoie agenerator
. Remplacez le retour par:return [...]
pour obtenir une liste.yield
:for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]
. Je ne sais pas si en interne cela serait traité différemment dans tous les aspects pertinents, mais cela pourrait même être un peu plus clair.__getitem__
méthode.Je suis fan de
la source
chunk
aura 1, 2 ou 3 éléments pour le dernier lot d'éléments. Consultez cette question pour savoir pourquoi les indices de tranche peuvent être hors limites .Autrement:
la source
size
, ce qui est parfois souhaitable.len
appel et ne fonctionnent donc pas sur d'autres générateurs.la source
izip_longest
est remplacé parzip_longest
La solution idéale pour ce problème fonctionne avec des itérateurs (pas seulement des séquences). Cela devrait également être rapide.
Voici la solution fournie par la documentation d'itertools:
Utiliser ipython
%timeit
sur mon mac book air, j'obtiens 47,5 us par boucle.Cependant, cela ne fonctionne vraiment pas pour moi car les résultats sont rembourrés pour être des groupes de taille égale. Une solution sans rembourrage est légèrement plus compliquée. La solution la plus naïve pourrait être:
Simple, mais assez lent: 693 us par boucle
La meilleure solution que j'ai pu trouver avec des utilisations
islice
pour la boucle intérieure:Avec le même ensemble de données, j'obtiens 305 us par boucle.
Impossible d'obtenir une solution pure plus rapidement que cela, je fournis la solution suivante avec une mise en garde importante: si vos données d'entrée contiennent des instances
filldata
, vous pouvez obtenir une mauvaise réponse.Je n'aime vraiment pas cette réponse, mais elle est beaucoup plus rapide. 124 us par boucle
la source
itertools
importations,map
doit être PY3map
ouimap
):def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n)))))
. Votre fonction finale peut être rendue moins fragile en utilisant une sentinelle: débarrassez-vous de l'fillvalue
argument; ajoutez une première lignefillvalue = object()
, puis changez laif
coche enif i[-1] is fillvalue:
et la ligne qu'elle contrôleyield tuple(v for v in i if v is not fillvalue)
. Garantit qu'aucune valeur neiterable
peut être confondue avec la valeur de remplissage.islice
objets (le n ° 3 gagne sin
est relativement grand, par exemple, le nombre de groupes est petit, mais cela optimise pour un cas rare), mais je ne m'attendais pas à ce que ce soit tout à fait cela extrême.izip_longest
du tuple final:yield i[:modulo]
. En outre, pour laargs
variable tuple au lieu d'une liste:args = (iter(iterable),) * n
. Rase encore quelques cycles d'horloge. Enfin, si nous ignorons fillvalue et supposonsNone
, le conditionnel peut devenirif None in i
pour encore plus de cycles d'horloge.yield
), tandis que le cas commun n'est pas affecté.J'avais besoin d'une solution qui fonctionnerait également avec des ensembles et des générateurs. Je n'ai rien trouvé de très court et de joli, mais c'est tout à fait lisible.
Liste:
Ensemble:
Générateur:
la source
Semblable à d'autres propositions, mais pas exactement identiques, j'aime le faire de cette façon, car c'est simple et facile à lire:
De cette façon, vous n'obtiendrez pas le dernier morceau partiel. Si vous voulez obtenir
(9, None, None, None)
le dernier morceau, utilisez simplementizip_longest
fromitertools
.la source
zip(*([it]*4))
Si cela ne vous dérange pas d'utiliser un package externe, vous pouvez utiliser à
iteration_utilities.grouper
partir de 1 . Il prend en charge tous les itérables (pas seulement les séquences):iteration_utilties
qui imprime:
Dans le cas où la longueur n'est pas un multiple de la taille du groupe, elle prend également en charge le remplissage (le dernier groupe incomplet) ou la troncature (suppression du dernier groupe incomplet) le dernier:
Repères
J'ai également décidé de comparer le temps d'exécution de quelques-unes des approches mentionnées. Il s'agit d'un tracé log-log groupé en groupes d'éléments "10" basés sur une liste de taille variable. Pour des résultats qualitatifs: plus bas signifie plus vite:
Au moins dans cette référence, la
iteration_utilities.grouper
meilleure performance. Suivi de l'approche de Craz .Le benchmark a été créé avec 1 . Le code utilisé pour exécuter ce benchmark était:
simple_benchmark
1 Avertissement: je suis l'auteur des bibliothèques
iteration_utilities
etsimple_benchmark
.la source
Puisque personne ne l'a encore mentionné, voici une
zip()
solution:Cela ne fonctionne que si la longueur de votre séquence est toujours divisible par la taille du morceau ou si vous ne vous souciez pas d'un morceau de fin s'il ne l'est pas.
Exemple:
Ou en utilisant itertools.izip pour renvoyer un itérateur au lieu d'une liste:
Le remplissage peut être corrigé en utilisant la réponse de @ ΤΖΩΤΖΙΟΥ :
la source
L'utilisation de map () au lieu de zip () corrige le problème de remplissage dans la réponse de JF Sebastian:
Exemple:
la source
itertools.izip_longest
(Py2) /itertools.zip_longest
(Py3); cette utilisation demap
est doublement obsolète et non disponible dans Py3 (vous ne pouvez pas passerNone
pour la fonction de mappeur, et elle s'arrête lorsque l'itérable le plus court est épuisé, pas le plus long; il ne se remplit pas).Une autre approche consisterait à utiliser la forme à deux arguments de
iter
:Cela peut être facilement adapté pour utiliser le rembourrage (cela est similaire à la réponse de Markus Jarderot ):
Ceux-ci peuvent même être combinés pour un rembourrage en option:
la source
Si la liste est grande, la façon la plus performante de le faire sera d'utiliser un générateur:
la source
iterable = range(100000000)
chunksize
L'utilisation de petites fonctions et de choses ne m'intéresse pas vraiment; Je préfère simplement utiliser des tranches:
la source
len
. vous pouvez faire un test avecitertools.repeat
ouitertools.cycle
.[...for...]
compréhension de liste pour construire physiquement une liste au lieu d'utiliser une(...for...)
expression de générateur qui se soucierait simplement de l'élément suivant et de la mémoire disponiblePour éviter toutes les conversions en liste
import itertools
et:Produit:
J'ai vérifié
groupby
et il ne se convertit pas en liste ou n'utiliselen
donc je pense que cela retardera la résolution de chaque valeur jusqu'à ce qu'elle soit réellement utilisée. Malheureusement, aucune des réponses disponibles (pour le moment) ne semblait offrir cette variation.De toute évidence, si vous devez gérer chaque élément à son tour, imbriquez une boucle for g:
Mon intérêt particulier pour cela était la nécessité de consommer un générateur pour soumettre des modifications par lots allant jusqu'à 1000 à l'API gmail:
la source
groupby(messages, lambda x: x/3)
vous obtiendriez alors une TypeError (pour essayer de diviser une chaîne par un entier), pas des groupes de 3 lettres. Si vous le faisiez,groupby(enumerate(messages), lambda x: x[0]/3)
vous pourriez avoir quelque chose. Mais vous ne l'avez pas dit dans votre message.Avec NumPy c'est simple:
production:
la source
la source
Sauf si je manque quelque chose, la solution simple suivante avec des expressions de générateur n'a pas été mentionnée. Il suppose que la taille et le nombre de morceaux sont connus (ce qui est souvent le cas) et qu'aucun rembourrage n'est requis:
la source
Dans votre deuxième méthode, je passerais au groupe suivant de 4 en faisant ceci:
Cependant, je n'ai fait aucune mesure de performance, donc je ne sais pas laquelle pourrait être la plus efficace.
Cela dit, je choisirais généralement la première méthode. Ce n'est pas joli, mais c'est souvent une conséquence de l'interfaçage avec le monde extérieur.
la source
Encore une autre réponse, dont les avantages sont:
1) Facilement compréhensible
2) Fonctionne sur toutes les séquences itérables, pas seulement (certaines des réponses ci-dessus s'étoufferont dans les descripteurs de fichiers)
3) Ne charge pas le morceau en mémoire d'un seul coup
4) Ne fait pas une longue liste de références à le même itérateur en mémoire
5) Pas de remplissage des valeurs de remplissage à la fin de la liste
Cela étant dit, je ne l'ai pas chronométré, donc cela pourrait être plus lent que certaines des méthodes les plus intelligentes, et certains des avantages peuvent ne pas être pertinents compte tenu du cas d'utilisation.
Mise à jour:
quelques inconvénients dus au fait que les boucles internes et externes tirent des valeurs du même itérateur:
1) continuer ne fonctionne pas comme prévu dans la boucle externe - il continue simplement sur l'élément suivant plutôt que de sauter un morceau . Cependant, cela ne semble pas être un problème car il n'y a rien à tester dans la boucle externe.
2) la rupture ne fonctionne pas comme prévu dans la boucle intérieure - le contrôle se retrouvera à nouveau dans la boucle intérieure avec l'élément suivant dans l'itérateur. Pour sauter des morceaux entiers, enveloppez l'itérateur interne (ii ci-dessus) dans un tuple, par exemple
for c in tuple(ii)
, ou définissez un drapeau et épuisez l'itérateur.la source
la source
Vous pouvez utiliser la fonction partition ou chunks de la bibliothèque funcy :
Ces fonctions ont également des versions itérateur
ipartition
etichunks
, qui seront plus efficaces dans ce cas.Vous pouvez également jeter un œil à leur mise en œuvre .
la source
À propos de la solution donnée par
J.F. Sebastian
ici :C'est intelligent, mais il a un inconvénient - renvoyez toujours le tuple. Comment obtenir une chaîne à la place?
Bien sûr, vous pouvez écrire
''.join(chunker(...))
, mais le tuple temporaire est quand même construit.Vous pouvez vous débarrasser du tuple temporaire en écrivant propre
zip
, comme ceci:alors
Exemple d'utilisation:
la source
zip
au lieu d'utiliser l'existant ne semble pas être la meilleure idée.J'aime cette approche. Il semble simple et non magique et prend en charge tous les types itérables et ne nécessite pas d'importations.
la source
Je ne veux jamais que mes morceaux soient rembourrés, donc cette exigence est essentielle. Je trouve que la capacité de travailler sur n'importe quel itérable est également requise. Compte tenu de cela, j'ai décidé d'étendre la réponse acceptée, https://stackoverflow.com/a/434411/1074659 .
Les performances prennent un léger coup dans cette approche si le remplissage n'est pas souhaité en raison de la nécessité de comparer et de filtrer les valeurs rembourrées. Cependant, pour les gros morceaux, cet utilitaire est très performant.
la source
Voici un chunker sans importations qui prend en charge les générateurs:
Exemple d'utilisation:
la source
Avec Python 3.8, vous pouvez utiliser l'opérateur morse et
itertools.islice
.la source
Il ne semble pas y avoir une jolie façon de procéder. Voici une page qui contient un certain nombre de méthodes, notamment:
la source
Si les listes sont de la même taille, vous pouvez les combiner en listes de 4 tuples avec
zip()
. Par exemple:Voici ce que la
zip()
fonction produit:Si les listes sont grandes et que vous ne souhaitez pas les combiner en une liste plus grande, utilisez
itertools.izip()
, qui produit un itérateur, plutôt qu'une liste.la source
Solution adhoc à une ligne pour parcourir une liste
x
en morceaux de taille4
-la source