web-dev-qa-db-fra.com

Moyen efficace de faire pivoter une liste dans python

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?

240
dzhelil

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]
_
250

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 de i 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 .)

87
Jamgold

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])
45
Richard

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]
33
jcdyer

Le moyen le plus simple auquel je puisse penser:

a.append(a.pop(0))
13
Thijs

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]
13
Phil H

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

11
keturn

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:

  • deque.rotate est O (k) (k = nombre d'éléments)
  • la liste à la conversion deque est O (n)
  • list.append et list.pop sont tous les deux O (1)

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 secondes
  • np.roll avec nparray: 6.0491721630096436 secondes
  • np.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 secondes
  • ...

Le code de synchronisation utilisé est ci-dessous.


collections.deque

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)

np.roll

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)

"Shift in place"

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)

l.append (l.pop (0))

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)
8
Purrell

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:

enter image description here

shift = 100:

enter image description here


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)",
)
5
Nico Schlömer

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)

4
John La Rooy

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)
4
Bittercoder

Je pense que vous cherchez ceci:

a.insert(0, x)
3
redreamality

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.

3
recursive

Une autre alternative:

def move(arr, n):
    return [arr[(idx-n) % len(arr)] for idx,_ in enumerate(arr)]
2
damio

Pour une liste X = ['a', 'b', 'c', 'd', 'e', 'f'] et une valeur de décalage souhaitée de shiftlongueur 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']

1
helcode

Je pense que vous avez le moyen le plus efficace

def shift(l,n):
    n = n % len(l)  
    return l[-U:] + l[:-U]
1
john ktejik

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)
1
herrfz

J'ai la même chose. Par exemple, pour changer de deux ...

def Shift(*args):
    return args[len(args)-2:]+args[:len(args)-2]
1
EyoelD

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.

1
DRayX

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?

1
wese3112

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 
0
Nick Lee

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]
0
rnso

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 tableau ba, mais supposons également que nous avons une fonction qui inverse les éléments dans une partie spécifiée du tableau. En commençant par ab, nous inversons a pour obtenir arb, inversons b pour obtenir arbr, puis inversons le tout pour obtenir (arbr)r. , qui est exactement ba. 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']
0
Eugene Yarmash