Эффективный способ переместить список в python

Каков наиболее эффективный способ переноса списка в python? Прямо сейчас у меня есть что-то вроде этого:

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

Есть ли лучший способ?

Ответ 1

A collections.deque оптимизирован для вытягивания и нажатия на обоих концах. У них даже есть выделенный метод 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]

Ответ 2

Как насчет использования pop(0)?

list.pop([i])

Удалите элемент в указанной позиции в списке и верните его. Если индекс не указан, a.pop() удаляет и возвращает последний элемент в список. (Квадратные скобки вокруг i в сигнатуре метода что параметр является необязательным, а не то, что вы должны ввести квадрат скобки в этом положении. Вы часто видите это обозначение в Справочник по библиотеке Python.)

Ответ 3

Это зависит от того, что вы хотите, когда вы это сделаете:

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

Возможно, вы захотите изменить свой:

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

в

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

Ответ 4

Numpy может сделать это с помощью команды 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])

Ответ 5

Если вы просто хотите итерации по этим наборам элементов, а не создавать отдельную структуру данных, подумайте об использовании итераторов для построения выражения генератора:

def shift(l,n):
    return itertools.islice(itertools.cycle(l),n,n+len(l))

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

Ответ 6

Это также зависит от того, хотите ли вы переместить список на место (мутируя его), или если вы хотите, чтобы функция возвращала новый список. Поскольку, согласно моим тестам, что-то вроде этого, по крайней мере, в двадцать раз быстрее, чем ваша реализация, которая добавляет два списка:

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

Фактически даже добавление a l = l[:] в начало этого для работы с копией переданного списка еще в два раза быстрее.

Различные реализации с некоторым сроком в http://gist.github.com/288272

Ответ 7

Простейший способ, о котором я могу думать:

a.append(a.pop(0))

Ответ 8

Для неизменной реализации вы можете использовать что-то вроде этого:

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)

Ответ 9

Просто некоторые заметки о времени:

Если вы начинаете со списка, l.append(l.pop(0)) - это самый быстрый способ, который вы можете использовать. Это может быть показано только с временной сложностью:

  • deque.rotate O (k) (k = количество элементов)
  • для преобразования deque O (n)
  • list.append и list.pop оба O (1)

Итак, если вы начинаете с объектов deque, вы можете deque.rotate() за счет O (k). Но, если отправной точкой является список, временной сложностью использования deque.rotate() является O (n). l.append(l.pop(0) быстрее в O (1).

Просто для иллюстрации, вот несколько примерных таймингов на итерациях 1M:

Методы, требующие преобразования типов:

  • deque.rotate с объектом deque: 0.12380790710449219 секунд (самый быстрый)
  • deque.rotate с преобразованием типов: 6.853878974914551 секунд
  • np.roll с nparray: 6.0491721630096436 секунд
  • np.roll с преобразованием типов: 27.558452129364014 секунд

Список методов, упомянутых здесь:

  • l.append(l.pop(0)): 0.32483696937561035 секунд (самый быстрый)
  • "shiftInPlace": 4.819645881652832 секунды
  • ...

Используемый код синхронизации ниже.


collections.deque

Показывая, что создание deques из списков - 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

Если вам нужно создать объекты deque:

1M итераций @6.853878974914551 секунд

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)

Если у вас уже есть объекты deque:

1M итераций @0.12380790710449219 секунд

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

Если вам нужно создать nparrays

1M итераций @27.558452129364014 секунд

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
"""

Если у вас уже есть nparrays:

1M итераций @6.0491721630096436 секунд

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)

"Сдвиг на месте"

Не требуется преобразование типа

1M итераций @4.819645881652832 секунд

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

Не требуется преобразование типа

1M итерации @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)

Ответ 10

Если эффективность - это ваша цель (память циклов?), вам может быть лучше смотреть на модуль массива: http://docs.python.org/library/array.html

Массивы не имеют накладных расходов на списки.

Что касается чистых списков, то, что у вас есть, так же хорошо, как вы можете надеяться.

Ответ 11

Возможно, более подходящим является ringbuffer. Это не список, хотя вполне вероятно, что он может вести себя достаточно, как список для ваших целей.

Проблема заключается в том, что эффективность сдвига в списке равна O (n), что становится значительным для достаточно больших списков.

Смещение в ringbuffer просто обновляет местоположение головы, которое является O (1)

Ответ 12

Я думаю, вы ищете это:

a.insert(0, x)

Ответ 13

Я беру эту модель затрат в качестве ссылки:

http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model

Ваш метод нарезки списка и объединение двух подписок - это операции с линейным временем. Я бы предложил использовать pop, который является операцией с постоянным временем, например:

def shift(list, n):
    for i in range(n)
        temp = list.pop()
        list.insert(0, temp)

Ответ 14

Я не знаю, является ли это "эффективным", но он также работает:

x = [1,2,3,4]
x.insert(0,x.pop())

EDIT: Здравствуйте, еще раз, я просто нашел большую проблему с этим решением! Рассмотрим следующий код:

class MyClass():
    def __init__(self):
        self.classlist = []

    def shift_classlist(self): # right-shift-operation
        self.classlist.insert(0, self.classlist.pop())

if __name__ == '__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!'

Метод shift_classlist() выполняет тот же код, что и мое решение x.insert(0, x.pop()), другой список - это список, не относящийся к классу. Пропустив содержимое другого списка в список MyClass.classlist, вызов shift_classlist() также изменит список других списков:

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!

Я использую Python 2.7. Я не знаю, если это ошибка, но я думаю, что более вероятно, что я пропустил что-то здесь.

Кто-нибудь из вас знает, почему это происходит?

Ответ 15

У меня есть аналогичная вещь. Например, сдвинуть на два...

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

Ответ 16

Другая альтернатива:

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

Ответ 17

Следующий метод - это O (n) с постоянной вспомогательной памятью:

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

Обратите внимание, что в python этот подход ужасно неэффективен по сравнению с другими, поскольку он не может использовать собственные реализации любой из частей.

Ответ 18

Я думаю, что у вас самый эффективный способ

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

Ответ 19

Каков прецедент? Часто нам не нужен полностью переложенный массив - нам просто нужно получить доступ к нескольким элементам в смещенном массиве.

Получение фрагментов Python - это время выполнения O (k), где k - срез, поэтому срезанное вращение - это время выполнения N. Команда вращения deque также является O (k). Мы можем сделать лучше?

Рассмотрим массив, который является чрезвычайно большим (пусть, скажем так, он будет слишком медленно вычислить, чтобы срезать его). Альтернативным решением было бы оставить исходный массив в покое и просто вычислить индекс элемента, который существовал бы в нашем желаемом индексе после некоторого сдвига.

Доступ к сдвинутому элементу таким образом становится 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 

Ответ 20

Следующая функция копирует список отправителю в templist, так что функция pop не влияет на исходный список:

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

Тестирование:

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)

Вывод:

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]

Ответ 21

для аналогичной функциональности, как сдвиг в других языках:

def shift(l):
    x = l[0]
    del(l[0])
    return x