Каков наиболее эффективный способ переноса списка в 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