325 votes

Une façon efficace de faire tourner une liste en python

Quel est le moyen le plus efficace de faire tourner une liste en python ? Pour l'instant, j'ai quelque chose comme ça :

>>> def rotate(l, n):
...     return l[n:] + l[:n]
... 
>>> l = [1,2,3,4]
>>> rotate(l,1)
[2, 3, 4, 1]
>>> rotate(l,2)
[3, 4, 1, 2]
>>> rotate(l,0)
[1, 2, 3, 4]
>>> rotate(l,-1)
[4, 1, 2, 3]

Y a-t-il un meilleur moyen ?

15 votes

Ce n'est pas vraiment un déplacement car les autres langages (Perl, Ruby) utilisent ce terme. Il s'agit de rotate. La question devrait peut-être être modifiée en conséquence ?

0 votes

@dzhelil J'aime beaucoup votre solution originale car elle n'introduit pas de mutations.

2 votes

341voto

Ignacio Vazquez-Abrams Points 312628

A collections.deque est optimisé pour tirer et pousser aux deux extrémités. Ils ont même une rotate() méthode.

from collections import deque
items = deque([1, 2])
items.append(3)        # deque == [1, 2, 3]
items.rotate(1)        # The deque is now: [3, 1, 2]
items.rotate(-1)       # Returns deque to original state: [1, 2, 3]
item = items.popleft() # deque == [2, 3]

13 votes

Pour les futurs lecteurs : collections.deque rotate() est plus rapide que le découpage en tranches selon wiki.python.org/moin/TimeComplexity

3 votes

Mais attention, utiliser deque.rotate nécessite une conversion de type vers un deque d'abord, ce qui est plus lent que l.append(l.pop(0)) . Donc si vous avez un objet deque pour commencer, c'est sûr que c'est le plus rapide. Sinon, utilisez l.append(l.pop(0)) .

12 votes

Pour élaborer, deque.rotate est O(k) mais la conversion de type de liste en deque est O(n) . Donc si vous commencez avec une liste, utiliser deque.rotate est O(n)+O(k)=O(n). l.append(l.pop(0)) d'autre part est O(1).

96voto

Jamgold Points 463

Et si vous utilisiez simplement pop(0) ?

list.pop([i])

Supprime l'élément à la position donnée dans la liste, et le renvoie. Si aucun indice n'est spécifié, a.pop() supprime et renvoie le dernier élément de la la liste. (Les crochets autour des i dans la signature de la méthode indiquent que le paramètre est optionnel, et non que vous devez taper des crochets à cet endroit. à cet endroit. Vous verrez fréquemment cette notation dans la référence de la bibliothèque Python).

19 votes

Mais cela ne coûterait-il pas O(k) pour supprimer chaque élément de la liste où k est le nombre d'éléments restants. Donc le temps total sera de O(n^2). wiki.python.org/moin/TimeComplexity

5 votes

Cela ne répond pas vraiment à la question. La question ne porte pas sur le retour des éléments dans l'ordre, mais plutôt sur la création d'une nouvelle liste dans un ordre différent.

5 votes

Non, la réponse à la question en utilisant pop serait l.append(l.pop(0) . Ce qui, si je ne me trompe pas, est O(1).

80voto

Richard Points 5991

Numpy peut le faire en utilisant la fonction roll commandement :

>>> import numpy
>>> a=numpy.arange(1,10) #Generate some data
>>> numpy.roll(a,1)
array([9, 1, 2, 3, 4, 5, 6, 7, 8])
>>> numpy.roll(a,-1)
array([2, 3, 4, 5, 6, 7, 8, 9, 1])
>>> numpy.roll(a,5)
array([5, 6, 7, 8, 9, 1, 2, 3, 4])
>>> numpy.roll(a,9)
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

2 votes

Ce que j'aime dans le SO, c'est que parfois, au fil des réponses, on peut trouver de nouveaux trésors comme celui-ci :)

0 votes

Ceci, lorsque je l'ai testé, est très, très lent.

2 votes

@PeterHarrison : Comme vous ne fournissez pas de détails sur les tests, il est difficile de savoir ce que vous voulez dire. Cette réponse fournit tous les détails des tests et une comparaison des délais.

36voto

jcdyer Points 7956

Cela dépend de ce que vous voulez qu'il se passe quand vous faites ça :

>>> shift([1,2,3], 14)

Vous pourriez vouloir changer votre :

def shift(seq, n):
    return seq[n:]+seq[:n]

à :

def shift(seq, n):
    n = n % len(seq)
    return seq[n:] + seq[:n]

8 votes

NB : Cette opération se bloque pour les listes vides.

0 votes

N = n % len(seq) return = seq[-n :] + seq[:-n]

0 votes

Pouvez-vous expliquer pourquoi n = n%len(seq) ?

11voto

keturn Points 3249

Cela dépend également de la question de savoir si vous voulez déplacer la liste sur place (en la mutant), ou si vous voulez que la fonction renvoie une nouvelle liste. Parce que, d'après mes tests, quelque chose comme ça est au moins vingt fois plus rapide que votre implémentation qui ajoute deux listes :

def shiftInPlace(l, n):
    n = n % len(l)
    head = l[:n]
    l[:n] = []
    l.extend(head)
    return l

En fait, même en ajoutant un l = l[:] au sommet de cette liste pour opérer sur une copie de la liste transmise est toujours deux fois plus rapide.

Diverses implémentations avec un certain timing à http://gist.github.com/288272

3 votes

Au lieu de l[:n] = [] Je choisirais del l[:n] . Juste une alternative.

1 votes

Oh, oui, ce bon vieux del. J'oublie souvent del, l'opération de liste qui est une déclaration, pas une méthode. Est-ce que py3k a changé cette bizarrerie, ou est-ce qu'on l'a toujours ?

2 votes

@keturn : del est toujours une déclaration dans Py3. Cependant, x.__delitem__(y) <==> del x[y] donc si vous préférez utiliser des méthodes, l.__delitem__(slice(n)) est également équivalent et fonctionne à la fois en 2 et 3.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X