Qual é a maneira mais eficiente de rotacionar uma lista em python? Agora eu tenho algo parecido com isto:
>>> 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]
Existe uma maneira melhor?
rotate
é a palavra certa, nãoshift
.Respostas:
UMA
collections.deque
é otimizado para puxar e empurrar nas duas extremidades. Eles ainda têm umrotate()
método dedicado .fonte
collections.deque rotate()
é mais rápido do que fatiar de acordo com wiki.python.org/moin/TimeComplexitydeque.rotate
requer uma conversão de tipo para umdeque
objeto primeiro, que é mais lento quel.append(l.pop(0))
. Portanto, se você tem um objeto deque para começar, verifique se ele é o mais rápido. Caso contrário, usel.append(l.pop(0))
.deque.rotate
é O (k), mas a conversão de tipo da lista para deque é O (n) . Portanto, se você começar com uma lista, usar deque.rotate é O (n) + O (k) = O (n).l.append(l.pop(0))
por outro lado, é O (1).Que tal apenas usar
pop(0)
?fonte
l.append(l.pop(0)
. O que, se não me engano, é O (1).O Numpy pode fazer isso usando o
roll
comando:fonte
Depende do que você deseja que aconteça ao fazer isso:
Você pode alterar seu:
para:
fonte
A maneira mais simples de pensar:
fonte
collections.deque
é mais rápido, mas para a maioria dos casos comuns de comprimento lista em uma única iteração, ou qualquer caso de várias iterações,a.append(a.pop(0))
vai ser mais rápido do que a conversão de tipo de dequeSe você deseja iterar sobre esses conjuntos de elementos em vez de construir uma estrutura de dados separada, considere usar iteradores para construir uma expressão de gerador:
fonte
Isso também depende se você deseja alterar a lista no lugar (modificá-la) ou se deseja que a função retorne uma nova lista. Porque, de acordo com meus testes, algo assim é pelo menos vinte vezes mais rápido que a sua implementação, que adiciona duas listas:
De fato, mesmo adicionando um
l = l[:]
ao topo para operar em uma cópia da lista passada ainda é duas vezes mais rápido.Várias implementações com algum tempo em http://gist.github.com/288272
fonte
l[:n] = []
eu ir paradel l[:n]
. Apenas uma alternativa.del
ainda é uma declaração no Py3. No entantox.__delitem__(y) <==> del x[y]
, por isso, se você preferir usar métodos,l.__delitem__(slice(n))
também é equivalente e trabalha em ambos 2 e 3.Apenas algumas notas sobre o tempo:
Se você está começando com uma lista,
l.append(l.pop(0))
é o método mais rápido que pode usar. Isso pode ser mostrado apenas com a complexidade do tempo:Portanto, se você está começando com
deque
objetos, pode fazê-lodeque.rotate()
ao custo de O (k). Mas, se o ponto de partida for uma lista, a complexidade do tempo de usodeque.rotate()
será O (n).l.append(l.pop(0)
é mais rápido em O (1).Apenas para fins ilustrativos, aqui estão alguns exemplos de tempos nas iterações 1M:
Métodos que requerem conversão de tipo:
deque.rotate
com objeto deque: 0.12380790710449219 segundos (mais rápido)deque.rotate
com conversão de tipo: 6.853878974914551 segundosnp.roll
com nparray: 6.0491721630096436 segundosnp.roll
com conversão de tipo: 27.558452129364014 segundosListe os métodos mencionados aqui:
l.append(l.pop(0))
: 0.32483696937561035 segundos (mais rápido)shiftInPlace
": 4.819645881652832 segundosO código de tempo usado está abaixo.
collections.deque
Mostrando que criar deques a partir de listas é O (n):
Se você precisar criar objetos deque:
1M iterações @ 6.853878974914551 segundos
Se você já possui objetos deque:
1M iterações @ 0.12380790710449219 segundos
np.roll
Se você precisar criar nparrays
1M iterações @ 27.558452129364014 segundos
Se você já possui nparrays:
1M iterações @ 6.0491721630096436 segundos
"Mudança no lugar"
Não requer conversão de tipo
1M iterações @ 4.819645881652832 segundos
l.append (l.pop (0))
Não requer conversão de tipo
1M iterações @ 0.32483696937561035
fonte
l = [random.random() for i in range(100000)]
l.append(l.pop(0))
é o melhor desempenho para mudar listas curtas (cerca de 7 elementos) por um?l.append(l.pop(0))
como resposta: Esta pergunta está encerrada como duplicada. Talvez você vote para reabri-lo?Também me interessei por isso e comparei algumas das soluções sugeridas com o perfplot (um pequeno projeto meu).
Acontece que
é de longe o método mais rápido para pequenos turnos
n
.Para maiores
n
,não é ruim.
Essencialmente, o perfplot executa o turno para aumentar matrizes grandes e mede o tempo. Aqui estão os resultados:
shift = 1
:shift = 100
:Código para reproduzir o gráfico:
fonte
l.append(l.pop(0))
como resposta: Esta pergunta está encerrada como duplicada. Talvez você vote para reabri-lo?Possivelmente um ringbuffer é mais adequado. Não é uma lista, embora seja provável que ela possa se comportar o suficiente como uma lista para seus propósitos.
O problema é que a eficiência de uma mudança em uma lista é O (n), que se torna significativa para listas grandes o suficiente.
Mudar em um ringbuffer é simplesmente atualizar o local da cabeça que é O (1)
fonte
Para uma implementação imutável, você pode usar algo como isto:
fonte
Se a sua meta é a eficiência (ciclos? Memória?), É melhor observar o módulo do array: http://docs.python.org/library/array.html
Matrizes não têm a sobrecarga de listas.
No entanto, no que diz respeito às listas puras, o que você tem é tão bom quanto você espera fazer.
fonte
Eu acho que você está procurando por isso:
fonte
Outra alternativa:
fonte
Tomo esse modelo de custo como referência:
http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model
Seu método de fatiar a lista e concatenar duas sub-listas são operações de tempo linear. Eu sugeriria o uso de pop, que é uma operação de tempo constante, por exemplo:
fonte
collections.dequeue
pop e appendleft, que são O (1) ops. Na minha primeira resposta acima, insert é O (n).collections.deque
Não sei se isso é 'eficiente', mas também funciona:
EDIT: Olá novamente, acabei de encontrar um grande problema com esta solução! Considere o seguinte código:
O método shift_classlist () executa o mesmo código que minha solução x.insert (0, x.pop ()) -, otherlist é uma lista independente da classe. Depois de passar o conteúdo de otherlist para a lista MyClass.classlist, chamar shift_classlist () também altera a lista de otherlist:
SAÍDA DA CONSOLA:
Eu uso o Python 2.7. Não sei se isso é um bug, mas acho que é mais provável que eu tenha entendido algo errado aqui.
Alguém sabe por que isso acontece?
fonte
x.classlist = otherlist
marcas sex.classlist
referem à mesma listaotherlist
e quando você a chamax.shift_classlist()
muda a lista e porque os dois nomes se referem ao mesmo objeto de lista. Ambos os nomes parecem mudar porque são apenas aliases para o mesmo objeto. Usex.classlist = otherlist[:]
para atribuir uma cópia da lista.O método a seguir está O (n) em vigor com memória auxiliar constante:
Observe que, em python, essa abordagem é terrivelmente ineficiente em comparação com outras, pois não pode tirar proveito das implementações nativas de nenhuma das partes.
fonte
Eu tenho coisa parecida. Por exemplo, para mudar por dois ...
fonte
Eu acho que você tem a maneira mais eficiente
fonte
Qual é o caso de uso? Frequentemente, na verdade não precisamos de uma matriz totalmente deslocada - precisamos apenas acessar alguns elementos na matriz deslocada.
Obter fatias do Python é o tempo de execução O (k), onde k é a fatia, portanto, uma rotação fatiada é o tempo de execução N. O comando deque rotation também é O (k). Podemos fazer melhor?
Considere uma matriz extremamente grande (digamos, tão grande que seria computacionalmente lenta para cortá-la). Uma solução alternativa seria deixar a matriz original em paz e simplesmente calcular o índice do item que existiria no índice desejado após uma mudança de algum tipo.
O acesso a um elemento deslocado passa a ser O (1).
fonte
A função a seguir copia a lista enviada para um modelo, para que a função pop não afete a lista original:
Teste:
Resultado:
fonte
Jon Bentley em Programming Pearls (Coluna 2) descreve um algoritmo elegante e eficiente para rotacionar um
n
vetor de elementox
deixado pelasi
posições:Isso pode ser traduzido para Python da seguinte maneira:
Demo:
fonte
Para uma lista
X = ['a', 'b', 'c', 'd', 'e', 'f']
e um valor de turno desejadoshift
menor que o tamanho da lista , podemos definir a funçãolist_shift()
como abaixoExemplos,
list_shift(X,1)
retorna['b', 'c', 'd', 'e', 'f', 'a']
list_shift(X,3)
retorna['d', 'e', 'f', 'a', 'b', 'c']
fonte
list_shift
na sua resposta é idêntica à dashift
pergunta original, portanto, essa não é uma resposta à pergunta real: "Existe uma maneira melhor?"Por exemplo, dado
a função deve retornar
[9, 7, 6, 3, 8]
. Foram realizadas três rotações:Por outro exemplo, dado
a função deve retornar
[0, 0, 0]
Dado
a função deve retornar
[1, 2, 3, 4]
fonte
Eu estava procurando uma solução para esse problema. Isso resolve o objetivo em O (k).
fonte
para funcionalidade semelhante à mudança em outros idiomas:
fonte
L.pop(0)