Apa cara paling efisien untuk memutar daftar dengan python? Saat ini saya punya sesuatu seperti ini:
>>> 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]
Apakah ada cara yang lebih baik?
rotate
itu kata yang tepat, bukanshift
.Jawaban:
A
collections.deque
dioptimalkan untuk menarik dan mendorong kedua ujungnya. Mereka bahkan memilikirotate()
metode khusus .sumber
collections.deque rotate()
lebih cepat daripada mengiris menurut wiki.python.org/moin/TimeComplexitydeque.rotate
membutuhkan konversi tipe kedeque
objek terlebih dahulu, yang lebih lambat daril.append(l.pop(0))
. Jadi, jika Anda memiliki objek deque untuk memulainya, pasti itu yang tercepat. Kalau tidak, gunakanl.append(l.pop(0))
.deque.rotate
adalah O (k) tetapi ketik konversi dari daftar ke deque adalah O (n) . Jadi jika Anda mulai dengan daftar, menggunakan deque.rotate adalah O (n) + O (k) = O (n).l.append(l.pop(0))
di sisi lain adalah O (1).Bagaimana kalau hanya menggunakan
pop(0)
?sumber
l.append(l.pop(0)
. Yang jika saya tidak salah adalah O (1).Numpy dapat melakukan ini menggunakan
roll
perintah:sumber
Itu tergantung pada apa yang Anda inginkan terjadi ketika Anda melakukan ini:
Anda mungkin ingin mengubah:
untuk:
sumber
Cara paling sederhana yang bisa saya pikirkan:
sumber
collections.deque
lebih cepat tetapi untuk sebagian besar kasus panjang daftar pada satu iterasi, atau kasus iterasi ganda,a.append(a.pop(0))
akan lebih cepat daripada konversi tipe menjadi dequeJika Anda hanya ingin mengulangi set elemen ini daripada membangun struktur data terpisah, pertimbangkan untuk menggunakan iterator untuk membuat ekspresi generator:
sumber
Ini juga tergantung pada apakah Anda ingin menggeser daftar di tempatnya (memutasikannya), atau jika Anda ingin fungsi mengembalikan daftar baru. Karena, menurut pengujian saya, sesuatu seperti ini setidaknya dua puluh kali lebih cepat daripada implementasi Anda yang menambahkan dua daftar:
Bahkan, bahkan menambahkan a
l = l[:]
ke atas itu untuk beroperasi pada salinan daftar yang disahkan masih dua kali lebih cepat.Berbagai implementasi dengan sejumlah waktu di http://gist.github.com/288272
sumber
l[:n] = []
saya akan pergi untukdel l[:n]
. Hanya sebuah alternatif.del
masih merupakan pernyataan di Py3. Namunx.__delitem__(y) <==> del x[y]
, jadi jika Anda lebih suka menggunakan metode,l.__delitem__(slice(n))
ini juga setara dan berfungsi di 2 & 3.Hanya beberapa catatan tentang waktu:
Jika Anda memulai dengan daftar,
l.append(l.pop(0))
adalah metode tercepat yang dapat Anda gunakan. Ini dapat ditunjukkan dengan kompleksitas waktu saja:Jadi, jika Anda memulai dengan
deque
objek, Anda bisadeque.rotate()
dengan biaya O (k). Tetapi, jika titik awal adalah daftar, kompleksitas waktu penggunaandeque.rotate()
adalah O (n).l.append(l.pop(0)
lebih cepat di O (1).Hanya demi ilustrasi, berikut adalah beberapa contoh waktu pada iterasi 1M:
Metode yang memerlukan konversi jenis:
deque.rotate
dengan objek deque: 0,12380790710449219 detik (tercepat)deque.rotate
dengan konversi tipe: 6.853878974914551 detiknp.roll
dengan nparray: 6.0491721630096436 detiknp.roll
dengan konversi tipe: 27.558452129364014 detikDaftar metode yang disebutkan di sini:
l.append(l.pop(0))
: 0,32483696937561035 detik (tercepat)shiftInPlace
": 4,819645881652832 detikKode waktu yang digunakan adalah di bawah ini.
collections.deque
Menunjukkan bahwa membuat deques dari daftar adalah O (n):
Jika Anda perlu membuat objek deque:
Iterasi 1M @ 6,853878974914551 detik
Jika Anda sudah memiliki objek deque:
Iterasi 1M @ 0,12380790710449219 detik
np.roll
Jika Anda perlu membuat nparrays
Iterasi 1M @ 27.558452129364014 detik
Jika Anda sudah memiliki nparray:
Iterasi 1M @ 6.0491721630096436 detik
"Bergeser di tempat"
Tidak memerlukan konversi jenis
Iterasi 1M @ 4,819645881652832 detik
l.append (l.pop (0))
Tidak memerlukan konversi jenis
Iterasi 1M @ 0.32483696937561035
sumber
l = [random.random() for i in range(100000)]
l.append(l.pop(0))
ini adalah yang terbaik untuk menggeser daftar pendek (sekitar 7 elemen) satu per satu?l.append(l.pop(0))
sebagai jawaban: Pertanyaan ini ditutup sebagai duplikat. Mungkin Anda akan memilih untuk membukanya kembali?Saya juga tertarik dengan ini dan membandingkan beberapa solusi yang disarankan dengan perfplot (proyek kecil saya).
Ternyata itu
adalah jauh metode tercepat untuk pergeseran kecil
n
.Untuk yang lebih besar
n
,tidak buruk.
Pada dasarnya, perfplot melakukan pergeseran untuk meningkatkan array besar dan mengukur waktu. Inilah hasilnya:
shift = 1
:shift = 100
:Kode untuk mereproduksi plot:
sumber
l.append(l.pop(0))
jawaban: Pertanyaan ini ditutup sebagai duplikat. Mungkin Anda akan memilih untuk membukanya kembali?Mungkin ringbuffer lebih cocok. Ini bukan daftar, meskipun kemungkinan itu bisa berperilaku cukup seperti daftar untuk tujuan Anda.
Masalahnya adalah bahwa efisiensi pergeseran pada daftar adalah O (n), yang menjadi signifikan untuk daftar yang cukup besar.
Bergeser di ringbuffer hanya memperbarui lokasi kepala yaitu O (1)
sumber
Untuk implementasi yang tidak berubah, Anda dapat menggunakan sesuatu seperti ini:
sumber
Jika efisiensi adalah tujuan Anda, (siklus? Memori?) Anda mungkin lebih baik melihat modul array: http://docs.python.org/library/array.html
Array tidak memiliki overhead daftar.
Sejauh daftar murni pergi, apa yang Anda miliki adalah tentang sebaik yang bisa Anda harapkan untuk dilakukan.
sumber
Saya pikir Anda mencari ini:
sumber
Alternatif lain:
sumber
Saya menggunakan model biaya ini sebagai referensi:
http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model
Metode Anda mengiris daftar dan menggabungkan dua sub-daftar adalah operasi waktu linier. Saya sarankan menggunakan pop, yang merupakan operasi waktu-konstan, misalnya:
sumber
collections.dequeue
pop dan appendleft, yang keduanya adalah O (1) ops. Dalam jawaban pertama saya di atas, masukkan adalah O (n).collections.deque
Saya tidak tahu apakah ini 'efisien', tetapi juga berfungsi:
EDIT: Halo lagi, saya baru saja menemukan masalah besar dengan solusi ini! Pertimbangkan kode berikut:
Metode shift_classlist () mengeksekusi kode yang sama dengan x.insert (0, x.pop ()) - solusi saya, otherlist adalah daftar indipendent dari kelas. Setelah meneruskan konten daftar lain ke daftar MyClass.classlist, memanggil shift_classlist () juga mengubah daftar daftar lain:
OUTPUT KONSOL:
Saya menggunakan Python 2.7. Saya tidak tahu apakah itu bug, tapi saya pikir lebih mungkin saya salah mengerti sesuatu di sini.
Apakah ada di antara kalian yang tahu mengapa ini terjadi?
sumber
x.classlist = otherlist
membuatx.classlist
merujuk ke daftar yang sama sepertiotherlist
dan kemudian ketika Anda memanggilnyax.shift_classlist()
bermutasi daftar dan karena kedua nama merujuk ke objek daftar yang sama. Kedua nama tampaknya berubah karena mereka hanya alias untuk objek yang sama. Gunakanx.classlist = otherlist[:]
sebagai gantinya untuk menetapkan salinan daftar.Metode berikut adalah O (n) pada tempatnya dengan memori bantu konstan:
Perhatikan bahwa dalam python, pendekatan ini sangat tidak efisien dibandingkan dengan yang lain karena tidak dapat mengambil keuntungan dari implementasi asli dari salah satu bagian.
sumber
Saya punya hal serupa. Misalnya, untuk beralih dua ...
sumber
Saya pikir Anda punya cara yang paling efisien
sumber
Apa gunanya? Seringkali, kita tidak benar-benar membutuhkan array yang sepenuhnya bergeser - kita hanya perlu mengakses beberapa elemen dalam array yang digeser.
Mendapatkan irisan Python adalah runtime O (k) di mana k adalah slice, jadi rotasi irisan adalah runtime N. Perintah rotasi deque juga O (k). Bisakah kita berbuat lebih baik?
Pertimbangkan sebuah array yang sangat besar (katakanlah, begitu besar itu akan lambat untuk mengirisnya). Solusi alternatif adalah meninggalkan array asli sendirian dan hanya menghitung indeks item yang akan ada dalam indeks yang diinginkan setelah perubahan jenis.
Mengakses elemen yang bergeser dengan demikian menjadi O (1).
sumber
Fungsi berikut menyalin daftar yang dikirim ke templist, sehingga fungsi pop tidak mempengaruhi daftar asli:
Pengujian:
Keluaran:
sumber
Jon Bentley dalam Pemrograman Mutiara (Kolom 2) menjelaskan algoritma yang elegan dan efisien untuk memutar
n
vektor elemen-x
kiri olehi
posisi:Ini dapat diterjemahkan ke Python sebagai berikut:
Demo:
sumber
Untuk daftar
X = ['a', 'b', 'c', 'd', 'e', 'f']
dan nilai pergeseran yang diinginkanshift
kurang dari panjang daftar , kita dapat mendefinisikan fungsilist_shift()
seperti di bawah iniContoh,
list_shift(X,1)
mengembalikan['b', 'c', 'd', 'e', 'f', 'a']
list_shift(X,3)
pengembalian['d', 'e', 'f', 'a', 'b', 'c']
sumber
list_shift
dalam jawaban Anda identik dengan fungsishift
dalam pertanyaan awal, jadi ini bukan jawaban untuk pertanyaan aktual: "Apakah ada cara yang lebih baik?"Misalnya diberikan
fungsi harus kembali
[9, 7, 6, 3, 8]
. Tiga rotasi dibuat:Sebagai contoh lain, diberikan
fungsi harus kembali
[0, 0, 0]
Diberikan
fungsi harus kembali
[1, 2, 3, 4]
sumber
Saya sedang mencari solusi untuk masalah ini. Ini memecahkan tujuan dalam O (k).
sumber
untuk fungsi yang sama dengan pergeseran dalam bahasa lain:
sumber
L.pop(0)