Quel est le moyen le plus efficace de faire pivoter une liste en python? En ce moment, 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?
Un collections.deque
est optimisé pour tirer et pousser aux deux extrémités. Ils ont même une méthode dédiée rotate()
.
_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]
_
Pourquoi ne pas utiliser pop(0)
?
list.pop([i])
Supprimez l'élément à la position donnée dans la liste et renvoyez-le. Si aucun index n'est spécifié,
a.pop()
supprime et renvoie le dernier élément de la liste. (Les crochets autour dei
dans la signature de la méthode indiquent que le paramètre est facultatif et que vous ne devez pas taper de crochets à cette position. Vous verrez cette notation fréquemment dans le Python Référence de la bibliothèque .)
Numpy peut le faire en utilisant la commande roll
:
>>> 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])
Cela dépend de ce que vous voulez qu'il se passe quand vous faites ceci:
>>> shift([1,2,3], 14)
Vous voudrez peut-être changer votre:
def shift(seq, n):
return seq[n:]+seq[:n]
à:
def shift(seq, n):
n = n % len(seq)
return seq[n:] + seq[:n]
Le moyen le plus simple auquel je puisse penser:
a.append(a.pop(0))
Si vous souhaitez simplement parcourir ces ensembles d'éléments plutôt que de construire une structure de données distincte, envisagez d'utiliser des itérateurs pour créer une expression génératrice:
def shift(l,n):
return itertools.islice(itertools.cycle(l),n,n+len(l))
>>> list(shift([1,2,3],1))
[2, 3, 1]
Cela dépend également de savoir si vous souhaitez déplacer la liste à la place (la muter) ou si vous souhaitez que la fonction renvoie une nouvelle liste. Parce que, selon 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 l’ajout d’un l = l[:]
en haut de la liste pour pouvoir utiliser une copie de la liste transmise est toujours deux fois plus rapide.
Diverses implémentations avec un certain timing à http://Gist.github.com/288272
Juste quelques notes sur le timing:
Si vous commencez avec une liste, l.append(l.pop(0))
est la méthode la plus rapide que vous pouvez utiliser. Ceci peut être démontré uniquement par la complexité du temps:
Donc, si vous commencez avec deque
objets, vous pouvez deque.rotate()
au prix de O (k). Toutefois, si le point de départ est une liste, la complexité temporelle liée à l'utilisation de deque.rotate()
est O (n). l.append(l.pop(0)
est plus rapide en O (1).
Juste à titre d’illustration, voici quelques exemples de timings sur des itérations 1M:
Méthodes nécessitant une conversion de type:
deque.rotate
avec l'objet deque: .12380790710449219 secondes (le plus rapide)deque.rotate
avec conversion de type: 6.853878974914551 secondesnp.roll
avec nparray: 6.0491721630096436 secondesnp.roll
avec conversion de type: 27,558452129364014 secondesÉnumérer les méthodes mentionnées ici:
l.append(l.pop(0))
: ,32483696937561035 secondes (le plus rapide)shiftInPlace
": 4,819645881652832 secondesLe code de synchronisation utilisé est ci-dessous.
Montrer que la création de deques à partir de listes est O (n):
from collections import deque
import big_o
def create_deque_from_list(l):
return deque(l)
best, others = big_o.big_o(create_deque_from_list, lambda n: big_o.datagen.integers(n, -100, 100))
print best
# --> Linear: time = -2.6E-05 + 1.8E-08*n
Si vous devez créer des objets deque:
1 M itérations @ 6.85387897974914551 secondes
setup_deque_rotate_with_create_deque = """
from collections import deque
import random
l = [random.random() for i in range(1000)]
"""
test_deque_rotate_with_create_deque = """
dl = deque(l)
dl.rotate(-1)
"""
timeit.timeit(test_deque_rotate_with_create_deque, setup_deque_rotate_with_create_deque)
Si vous avez déjà des objets deque:
1M itérations @ 0.12380790710449219 secondes
setup_deque_rotate_alone = """
from collections import deque
import random
l = [random.random() for i in range(1000)]
dl = deque(l)
"""
test_deque_rotate_alone= """
dl.rotate(-1)
"""
timeit.timeit(test_deque_rotate_alone, setup_deque_rotate_alone)
Si vous avez besoin de créer nparrays
1M itérations @ 27.558452129364014 secondes
setup_np_roll_with_create_npa = """
import numpy as np
import random
l = [random.random() for i in range(1000)]
"""
test_np_roll_with_create_npa = """
np.roll(l,-1) # implicit conversion of l to np.nparray
"""
Si vous avez déjà nparrays:
1M itérations @ 6.0491721630096436 secondes
setup_np_roll_alone = """
import numpy as np
import random
l = [random.random() for i in range(1000)]
npa = np.array(l)
"""
test_roll_alone = """
np.roll(npa,-1)
"""
timeit.timeit(test_roll_alone, setup_np_roll_alone)
Ne nécessite aucune conversion de type
1M itérations @ 4,819645881652832 secondes
setup_shift_in_place="""
import random
l = [random.random() for i in range(1000)]
def shiftInPlace(l, n):
n = n % len(l)
head = l[:n]
l[:n] = []
l.extend(head)
return l
"""
test_shift_in_place="""
shiftInPlace(l,-1)
"""
timeit.timeit(test_shift_in_place, setup_shift_in_place)
Ne nécessite aucune conversion de type
1M itérations @ 0.32483696937561035
setup_append_pop="""
import random
l = [random.random() for i in range(1000)]
"""
test_append_pop="""
l.append(l.pop(0))
"""
timeit.timeit(test_append_pop, setup_append_pop)
Je me suis aussi intéressé à cela et ai comparé certaines des solutions suggérées avec perfplot (un de mes petits projets).
Il se trouve que
for _ in range(n):
data.append(data.pop(0))
est de loin la méthode la plus rapide pour les petits déplacements n
.
Pour les plus grandes n
,
data[n:] + data[:n]
c'est pas mal.
En gros, perfplot effectue le décalage pour augmenter le nombre de tableaux de grande taille et mesure le temps. Voici les résultats:
shift = 1
:
shift = 100
:
Code pour reproduire l'intrigue:
import numpy
import perfplot
import collections
shift = 100
def list_append(data):
return data[shift:] + data[:shift]
def shift_concatenate(data):
return numpy.concatenate([data[shift:], data[:shift]])
def roll(data):
return numpy.roll(data, -shift)
def collections_deque(data):
items = collections.deque(data)
items.rotate(-shift)
return items
def pop_append(data):
for _ in range(shift):
data.append(data.pop(0))
return data
perfplot.save(
"shift100.png",
setup=lambda n: numpy.random.Rand(n).tolist(),
kernels=[list_append, roll, shift_concatenate, collections_deque, pop_append],
n_range=[2 ** k for k in range(7, 20)],
logx=True,
logy=True,
xlabel="len(data)",
)
Peut-être un tampon de sonnerie est plus approprié. Ce n'est pas une liste, même s'il est probable qu'il puisse se comporter assez comme une liste pour vos besoins.
Le problème est que l'efficacité d'un décalage sur une liste est O (n), ce qui devient significatif pour des listes suffisamment grandes.
Passer dans un ringbuffer, c'est simplement mettre à jour l'emplacement de la tête qui est O (1)
Pour une implémentation immuable, vous pouvez utiliser quelque chose comme ceci:
def shift(seq, n):
shifted_seq = []
for i in range(len(seq)):
shifted_seq.append(seq[(i-n) % len(seq)])
return shifted_seq
print shift([1, 2, 3, 4], 1)
Je pense que vous cherchez ceci:
a.insert(0, x)
Si votre objectif est l'efficacité (cycles? Mémoire?), Vous feriez mieux de consulter le module tableau: http://docs.python.org/library/array.html
Les tableaux n'ont pas la surcharge des listes.
En ce qui concerne les listes pures, ce que vous avez est aussi bon que vous pouvez espérer faire.
Une autre alternative:
def move(arr, n):
return [arr[(idx-n) % len(arr)] for idx,_ in enumerate(arr)]
Pour une liste X = ['a', 'b', 'c', 'd', 'e', 'f']
et une valeur de décalage souhaitée de shift
longueur inférieure à la liste, nous pouvons définir la fonction list_shift()
comme ci-dessous
def list_shift(my_list, shift):
assert shift < len(my_list)
return my_list[shift:] + my_list[:shift]
Exemples,
list_shift(X,1)
renvoie ['b', 'c', 'd', 'e', 'f', 'a']
list_shift(X,3)
renvoie ['d', 'e', 'f', 'a', 'b', 'c']
Je pense que vous avez le moyen le plus efficace
def shift(l,n):
n = n % len(l)
return l[-U:] + l[:-U]
Je prends ce modèle de coûts comme référence:
http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model
Votre méthode de découpage de la liste et de concaténation de deux sous-listes est une opération linéaire. Je suggérerais d’utiliser Pop, qui est une opération à temps constant, par exemple:
def shift(list, n):
for i in range(n)
temp = list.pop()
list.insert(0, temp)
J'ai la même chose. Par exemple, pour changer de deux ...
def Shift(*args):
return args[len(args)-2:]+args[:len(args)-2]
La méthode suivante est O(n) en place avec une mémoire auxiliaire constante:
def rotate(arr, shift):
pivot = shift % len(arr)
dst = 0
src = pivot
while (dst != src):
arr[dst], arr[src] = arr[src], arr[dst]
dst += 1
src += 1
if src == len(arr):
src = pivot
Elif dst == pivot:
pivot = src
Notez qu'en Python, cette approche est horriblement inefficace par rapport aux autres car elle ne peut tirer parti des implémentations natives d'aucun des morceaux.
Je ne sais pas si c'est "efficace", mais ça marche aussi:
x = [1,2,3,4]
x.insert(0,x.pop())
EDIT: Bonjour à nouveau, je viens de trouver un gros problème avec cette solution! Considérons le code suivant:
class MyClass():
def __init__(self):
self.classlist = []
def shift_classlist(self): # right-shift-operation
self.classlist.insert(0, self.classlist.pop())
if __== '__main__':
otherlist = [1,2,3]
x = MyClass()
# this is where kind of a magic link is created...
x.classlist = otherlist
for ii in xrange(2): # just to do it 2 times
print '\n\n\nbefore shift:'
print ' x.classlist =', x.classlist
print ' otherlist =', otherlist
x.shift_classlist()
print 'after shift:'
print ' x.classlist =', x.classlist
print ' otherlist =', otherlist, '<-- SHOULD NOT HAVE BIN CHANGED!'
La méthode shift_classlist () exécute le même code que mon x.insert (0, x.pop ()) - solution, otherlist est une liste indépendante de la classe. Après avoir passé le contenu d’autrelist à la liste MyClass.classlist, l’appel à shift_classlist () modifie également la liste otherlist:
CONSOLE OUTPUT:
before shift:
x.classlist = [1, 2, 3]
otherlist = [1, 2, 3]
after shift:
x.classlist = [3, 1, 2]
otherlist = [3, 1, 2] <-- SHOULD NOT HAVE BIN CHANGED!
before shift:
x.classlist = [3, 1, 2]
otherlist = [3, 1, 2]
after shift:
x.classlist = [2, 3, 1]
otherlist = [2, 3, 1] <-- SHOULD NOT HAVE BIN CHANGED!
J'utilise Python 2.7. Je ne sais pas si c'est un bug, mais je pense qu'il est plus probable que j'ai mal compris quelque chose ici.
Est-ce que quelqu'un d'entre vous sait pourquoi cela se produit?
Quel est le cas d'utilisation? Souvent, nous n'avons pas réellement besoin d'un tableau totalement décalé - nous avons simplement besoin d'accéder à une poignée d'éléments du tableau décalé.
Obtenir Python tranches correspond à l'exécution O(k) où k est la tranche, de sorte qu'une rotation en tranches correspond à l'exécution N. La commande de rotation deque est également O (k). Pouvons-nous faire mieux?
Considérons un tableau extrêmement volumineux (si grand qu’il serait trop lent, en calcul, à le trancher). Une solution alternative serait de laisser le tableau d'origine seul et de calculer simplement l'indice de l'élément qui aurait existé dans notre index souhaité après un changement quelconque.
L'accès à un élément déplacé devient alors O (1).
def get_shifted_element(original_list, shift_to_left, index_in_shifted):
# back calculate the original index by reversing the left shift
idx_original = (index_in_shifted + shift_to_left) % len(original_list)
return original_list[idx_original]
my_list = [1, 2, 3, 4, 5]
print get_shifted_element(my_list, 1, 2) ----> outputs 4
print get_shifted_element(my_list, -2, 3) -----> outputs 2
La fonction suivante copie la liste envoyée à un modèle, de sorte que la fonction pop n’affecte pas la liste originale:
def shift(lst, n, toreverse=False):
templist = []
for i in lst: templist.append(i)
if toreverse:
for i in range(n): templist = [templist.pop()]+templist
else:
for i in range(n): templist = templist+[templist.pop(0)]
return templist
Essai:
lst = [1,2,3,4,5]
print("lst=", lst)
print("shift by 1:", shift(lst,1))
print("lst=", lst)
print("shift by 7:", shift(lst,7))
print("lst=", lst)
print("shift by 1 reverse:", shift(lst,1, True))
print("lst=", lst)
print("shift by 7 reverse:", shift(lst,7, True))
print("lst=", lst)
Sortie:
lst= [1, 2, 3, 4, 5]
shift by 1: [2, 3, 4, 5, 1]
lst= [1, 2, 3, 4, 5]
shift by 7: [3, 4, 5, 1, 2]
lst= [1, 2, 3, 4, 5]
shift by 1 reverse: [5, 1, 2, 3, 4]
lst= [1, 2, 3, 4, 5]
shift by 7 reverse: [4, 5, 1, 2, 3]
lst= [1, 2, 3, 4, 5]
Jon Bentley in Programmation des perles (Colonne 2) décrit un algorithme élégant et efficace permettant de faire pivoter un vecteur d’éléments n
- x
à gauche de i
:
Voyons le problème en transformant le tableau
ab
en tableauba
, mais supposons également que nous avons une fonction qui inverse les éléments dans une partie spécifiée du tableau. En commençant parab
, nous inversonsa
pour obtenirarb
, inversonsb
pour obtenirarbr
, puis inversons le tout pour obtenir(arbr)r
. , qui est exactementba
. Cela se traduit par le code suivant pour la rotation:reverse(0, i-1) reverse(i, n-1) reverse(0, n-1)
Ceci peut être traduit en Python comme suit:
def rotate(x, i):
i %= len(x)
x[:i] = reversed(x[:i])
x[i:] = reversed(x[i:])
x[:] = reversed(x)
return x
Démo:
>>> def rotate(x, i):
... i %= len(x)
... x[:i] = reversed(x[:i])
... x[i:] = reversed(x[i:])
... x[:] = reversed(x)
... return x
...
>>> rotate(list('abcdefgh'), 1)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a']
>>> rotate(list('abcdefgh'), 3)
['d', 'e', 'f', 'g', 'h', 'a', 'b', 'c']
>>> rotate(list('abcdefgh'), 8)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> rotate(list('abcdefgh'), 9)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a']