Bagaimana cara menghapus item dari daftar saat iterasi?

934

Saya mengulangi daftar tupel dengan Python, dan saya berusaha menghapusnya jika memenuhi kriteria tertentu.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

Apa yang harus saya gunakan sebagai pengganti code_to_remove_tup? Saya tidak tahu cara menghapus item dengan cara ini.

lfaraone
sumber
Sebagian besar jawaban pada halaman ini tidak benar-benar menjelaskan mengapa menghapus elemen saat mengulangi daftar menghasilkan hasil yang aneh, tetapi jawaban yang diterima dalam pertanyaan ini , dan mungkin merupakan penipuan yang lebih baik bagi pemula yang mengalami masalah ini untuk pertama kalinya.
ggorlen

Jawaban:

827

Anda dapat menggunakan pemahaman daftar untuk membuat daftar baru yang hanya berisi elemen yang tidak ingin Anda hapus:

somelist = [x for x in somelist if not determine(x)]

Atau, dengan menetapkan ke slice somelist[:], Anda dapat mengubah daftar yang ada untuk hanya berisi item yang Anda inginkan:

somelist[:] = [x for x in somelist if not determine(x)]

Pendekatan ini bisa bermanfaat jika ada referensi lain somelistyang perlu mencerminkan perubahan.

Alih-alih pemahaman, Anda juga bisa menggunakannya itertools. Dengan Python 2:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

Atau dengan Python 3:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

Demi kejelasan dan bagi mereka yang menemukan penggunaan [:]notasi hackish atau fuzzy, berikut adalah alternatif yang lebih eksplisit. Secara teoritis, itu harus melakukan hal yang sama berkaitan dengan ruang dan waktu daripada satu-liners di atas.

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

Ini juga berfungsi dalam bahasa lain yang mungkin tidak memiliki kemampuan mengganti item dari daftar Python, dengan modifikasi minimal. Misalnya, tidak semua bahasa memberikan daftar kosong ke FalsePython. Anda dapat menggantikan while somelist:sesuatu yang lebih eksplisit seperti while len(somelist) > 0:.

David Raznick
sumber
4
Bisakah Anda membuatnya lebih cepat jika Anda tahu hanya sedikit yang akan dihapus, yaitu hanya menghapusnya dan membiarkan yang lain di tempat daripada menulis ulang?
highBandWidth
20
Bagaimana jika daftar saya sangat besar dan tidak mampu membuat salinan?
jpcgt
15
@ jpcgt Anda harus menggunakan somelist[:] = (x for x in somelist if determine(x))ini untuk membuat generator yang mungkin tidak membuat salinan yang tidak perlu.
Rostislav Kondratenko
8
@RostislavKondratenko: list_ass_slice()fungsi yang mengimplementasikan somelist[:]=panggilan secara PySequence_Fast()internal. Fungsi ini selalu mengembalikan daftar yaitu, solusi @Alex Martelli yang sudah menggunakan daftar bukan generator yang paling mungkin lebih efisien
jfs
6
Maukah Anda menjelaskan apa perbedaan antara menetapkan pemahaman daftar ke daftar dan mengkloning daftar? Bukankah daftar asli somelistdimutasi dalam kedua metode?
Bowen Liu
589

Jawaban yang menyarankan pemahaman daftar HAMPIR HAMPIR benar - kecuali bahwa mereka membangun daftar yang sama sekali baru dan kemudian memberi nama yang sama dengan daftar lama, mereka TIDAK mengubah daftar lama di tempat. Itu berbeda dari apa yang akan Anda lakukan dengan penghapusan selektif, seperti dalam saran @ Lennart - lebih cepat, tetapi jika daftar Anda diakses melalui beberapa referensi, fakta bahwa Anda hanya mengulangi salah satu referensi dan TIDAK mengubah objek daftar itu sendiri dapat menyebabkan bug yang halus dan berbahaya.

Untungnya, sangat mudah untuk mendapatkan kecepatan pemahaman daftar DAN semantik yang diperlukan dari perubahan di tempat - cukup kode:

somelist[:] = [tup for tup in somelist if determine(tup)]

Perhatikan perbedaan halus dengan jawaban lain: yang ini BUKAN menugaskan ke nama bar - itu menugaskan ke daftar slice yang kebetulan menjadi seluruh daftar, dengan demikian mengganti isi daftar dalam objek daftar Python yang sama , bukan hanya mengulangi satu referensi (dari objek daftar sebelumnya ke objek daftar baru) seperti jawaban lainnya.

Alex Martelli
sumber
1
Bagaimana saya melakukan tugas irisan yang sama dengan dict? Dalam Python 2.6?
PaulMcG
11
@ Paul: Karena dikte tidak teratur, irisan tidak ada artinya bagi dikte. Jika Anda ingin mengganti konten dict adengan isi dict b, gunakan a.clear(); a.update(b).
Sven Marnach
1
Mengapa bisa 'mengulang' salah satu referensi dengan mengganti apa yang dirujuk variabel penyebab bug? Sepertinya itu hanya akan menjadi masalah potensial dalam aplikasi multi-threaded, bukan single-threaded.
Derek Dahmer
59
@ Serek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Ini menugaskan kembali xke hasil pemahaman daftar, tetapi ymasih mengacu pada daftar asli['foo','bar','baz'] . Jika Anda mengharapkan xdan ymerujuk pada daftar yang sama, Anda mungkin telah memperkenalkan bug. Anda mencegah ini dengan menetapkan untuk sepotong seluruh daftar, seperti Alex show, dan saya tunjukkan di sini: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. Daftar dimodifikasi di tempat. memastikan bahwa semua referensi ke daftar (keduanya xdan di ysini) merujuk ke daftar baru.
Steven T. Snyder
pada kenyataannya, menggunakan filterfungsi juga membuat daftar baru, tidak mengubah elemen pada tempatnya ... hanyaolist[:] = [i for i in olist if not dislike(i)]
John Strood
303

Anda perlu mengambil salinan daftar dan mengulanginya terlebih dahulu, atau iterasi akan gagal dengan apa yang mungkin hasil yang tidak terduga.

Misalnya (tergantung pada jenis daftar apa):

for tup in somelist[:]:
    etc....

Sebuah contoh:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]
Lennart Regebro
sumber
13
@ Zen Karena yang kedua beralih pada salinan daftar. Jadi, ketika Anda memodifikasi daftar asli, Anda tidak memodifikasi salinan yang Anda ulangi.
Lennart Regebro
3
Apa yang lebih baik dalam melakukan somelist [:] dibandingkan dengan daftar (somelist)?
Mariusz Jamro
3
list(somelist)akan mengubah iterable menjadi daftar. somelist[:]membuat salinan dari objek yang mendukung pengirisan. Jadi mereka tidak perlu melakukan hal yang sama. Dalam hal ini saya ingin membuat salinan somelistobjek, jadi saya menggunakan[:]
Lennart Regebro
33
Catatan untuk siapa pun yang membaca ini, ini SANGAT lambat untuk daftar. remove()harus memeriksa daftar SELURUH untuk setiap iterasi, jadi itu akan selamanya.
vitiral
7
Waktu O besar tidak masalah ketika berurusan dengan daftar hanya selusin item. Seringkali jelas dan sederhana untuk dipahami oleh programmer masa depan jauh lebih berharga daripada kinerja.
Steve
127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Anda harus mundur jika tidak seperti memotong cabang pohon tempat Anda duduk :-)

Pengguna Python 2: ganti rangedengan xrangeuntuk menghindari membuat daftar hardcoded

John Machin
sumber
13
Dalam versi Python terbaru, Anda dapat melakukan ini lebih bersih dengan menggunakan reversed()builtin
ncoghlan
16
terbalik () tidak membuat daftar baru, itu membuat iterator terbalik atas urutan yang disediakan. Seperti enumerate (), Anda harus membungkusnya dalam daftar () untuk benar-benar mendapatkan daftar darinya. Anda mungkin berpikir tentang diurutkan (), yang memang membuat daftar baru setiap kali (harus, sehingga dapat mengurutkannya).
ncoghlan
1
@Mauris karena enumeratemengembalikan iterator dan reversedmengharapkan urutan. Saya kira Anda bisa melakukannya reversed(list(enumerate(somelist)))jika Anda tidak keberatan membuat daftar tambahan dalam memori.
drevicko
2
Ini adalah O (N * M) untuk array, ini sangat lambat jika Anda menghapus banyak item dari daftar besar. Jadi tidak direkomendasikan.
Sam Watkins
2
@ SamWatkins Ya, jawaban ini untuk ketika Anda menghapus beberapa elemen dari array yang sangat besar. Lebih sedikit penggunaan memori, tetapi bisa lebih mlambat.
Navin
52

Tutorial Python 2 resmi 4.2. "untuk Pernyataan"

https://docs.python.org/2/tutorial/controlflow.html#for-statements

Bagian dokumen ini memperjelas bahwa:

  • Anda perlu membuat salinan dari daftar iterated untuk memodifikasinya
  • salah satu cara untuk melakukannya adalah dengan notasi slice [:]

Jika Anda perlu mengubah urutan yang Anda lakukan berulang di dalam lingkaran (misalnya untuk menduplikasi item yang dipilih), Anda disarankan untuk terlebih dahulu membuat salinan. Mengulangi urutan tidak secara implisit membuat salinan. Notasi slice membuat ini sangat nyaman:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

Dokumentasi Python 2 7.3. "Pernyataan untuk"

https://docs.python.org/2/reference/compound_stmts.html#for

Bagian dokumen ini mengatakan sekali lagi bahwa Anda harus membuat salinan, dan memberikan contoh penghapusan yang sebenarnya:

Catatan: Ada kehalusan ketika urutan sedang dimodifikasi oleh loop (ini hanya bisa terjadi untuk urutan yang bisa berubah, yaitu daftar). Penghitung internal digunakan untuk melacak item mana yang digunakan selanjutnya, dan ini bertambah pada setiap iterasi. Ketika penghitung ini telah mencapai panjang urutan, loop berakhir. Ini berarti bahwa jika suite menghapus item saat ini (atau sebelumnya) dari urutan, item berikutnya akan dilewati (karena mendapat indeks dari item saat ini yang telah diperlakukan). Demikian juga, jika suite memasukkan item dalam urutan sebelum item saat ini, item saat ini akan diperlakukan lagi pada saat berikutnya melalui loop. Ini dapat menyebabkan bug jahat yang dapat dihindari dengan membuat salinan sementara menggunakan sepotong dari keseluruhan urutan, misalnya,

for x in a[:]:
    if x < 0: a.remove(x)

Namun, saya tidak setuju dengan implementasi ini, karena .remove()harus mengulangi seluruh daftar untuk menemukan nilai.

Penanganan terbaik

Antara:

Umumnya Anda hanya ingin memilih .append()opsi yang lebih cepat secara default kecuali jika memori adalah masalah besar.

Bisakah Python melakukan ini dengan lebih baik?

Sepertinya API Python khusus ini dapat ditingkatkan. Bandingkan, misalnya, dengan:

  • Java ListIterator :: hapus dokumen mana "Panggilan ini hanya dapat dilakukan satu kali per panggilan ke yang berikutnya atau sebelumnya"
  • C ++ std::vector::eraseyang mengembalikan interator yang valid ke elemen setelah yang dihapus

keduanya membuatnya sangat jelas bahwa Anda tidak dapat mengubah daftar yang di-iterasi kecuali dengan iterator itu sendiri, dan memberi Anda cara yang efisien untuk melakukannya tanpa menyalin daftar.

Mungkin alasan yang mendasarinya adalah bahwa daftar Python diasumsikan sebagai array dinamis yang didukung, dan oleh karena itu segala jenis penghapusan akan menjadi waktu yang tidak efisien, sementara Java memiliki hierarki antarmuka yang lebih baik dengan keduanya ArrayListdan LinkedListimplementasi dariListIterator .

Tampaknya tidak ada jenis daftar tertaut eksplisit di stdlib Python baik: Daftar Tertaut Python

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
48

Pendekatan terbaik Anda untuk contoh seperti itu adalah pemahaman daftar

somelist = [tup for tup in somelist if determine(tup)]

Dalam kasus di mana Anda melakukan sesuatu yang lebih kompleks daripada memanggil suatu determinefungsi, saya lebih suka membuat daftar baru dan hanya menambahkannya ketika saya pergi. Sebagai contoh

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

Menyalin daftar menggunakan removemungkin membuat kode Anda terlihat sedikit lebih bersih, seperti yang dijelaskan dalam salah satu jawaban di bawah ini. Anda seharusnya tidak melakukan ini untuk daftar yang sangat besar, karena ini melibatkan pertama-tama menyalin seluruh daftar, dan juga melakukan O(n) removeoperasi untuk setiap elemen yang dihapus, menjadikan ini suatu O(n^2)algoritma.

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
Eli Courtwright
sumber
37

Bagi mereka yang suka pemrograman fungsional:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

atau

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Cide
sumber
1. Pemahaman daftar dan ekspresi generator dipinjam dari Haskell, bahasa fungsional murni; mereka sama fungsionalnya filter, dan lebih Pythonic. 2. Jika Anda perlu lambdamenggunakan mapatau filter, daftar comp atau genexpr selalu merupakan opsi yang lebih baik; mapdan filterbisa menjadi sedikit lebih cepat ketika fungsi transformasi / predikat adalah built-in Python diimplementasikan dalam C dan iterable tidak kecil, tapi mereka selalu lebih lambat ketika Anda membutuhkan lambdayang listcomp / genexpr bisa hindari.
ShadowRanger
13

Saya perlu melakukan ini dengan daftar besar, dan menduplikasi daftar itu tampak mahal, terutama karena dalam kasus saya jumlah penghapusan akan sedikit dibandingkan dengan item yang tersisa. Saya mengambil pendekatan tingkat rendah ini.

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

Yang saya tidak tahu adalah seberapa efisien beberapa penghapusan dibandingkan dengan menyalin daftar besar. Berikan komentar jika Anda memiliki wawasan.

Michael
sumber
Dalam kasus saya, saya perlu memindahkan elemen 'yang tidak diinginkan' ke daftar lain. Apakah Anda punya komentar baru tentang solusi ini? Saya juga berpikir bahwa lebih baik menggunakan beberapa penghapusan daripada menduplikasi daftar.
gustavovelascoh
Ini adalah jawaban yang tepat jika kinerja adalah masalah (meskipun sama dengan @Alexey). Yang mengatakan, pilihan listsebagai struktur data di tempat pertama harus dipertimbangkan dengan hati-hati karena penghapusan dari tengah daftar membutuhkan waktu linier dalam panjang daftar. Jika Anda tidak benar-benar membutuhkan akses acak ke item sekuensial k-th, mungkin pertimbangkan OrderedDict?
maks
@GVelascoh mengapa tidak membuat newlist = [], lalu newlist.append(array[i])sebelum itu del array[i]?
maks
2
Perhatikan bahwa ini kemungkinan waktu tidak efisien: jika list()daftar yang ditautkan, akses acak mahal, jika list()array, penghapusan mahal karena mereka perlu untuk memajukan semua elemen berikut. Iterator yang layak dapat membuat hal-hal yang baik untuk implementasi daftar tertaut. Namun ini bisa menghemat ruang.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
10

Mungkin pintar juga membuat daftar baru jika item daftar saat ini memenuhi kriteria yang diinginkan.

begitu:

for item in originalList:
   if (item != badValue):
        newList.append(item)

dan untuk menghindari kode ulang seluruh proyek dengan nama daftar baru:

originalList[:] = newList

perhatikan, dari dokumentasi Python:

copy.copy (x) Kembalikan salinan dangkal x.

copy.deepcopy (x) Kembalikan salinan x yang dalam.

ntk4
sumber
3
Ini tidak menambahkan informasi baru yang tidak ada dalam jawaban yang diterima tahun sebelumnya.
Mark Amery
2
Ini sederhana dan hanyalah cara lain untuk melihat masalah @MarkAmery. Itu kurang kental untuk orang-orang yang tidak suka sintaks coding terkompresi.
ntk4
9

Jawaban ini awalnya ditulis sebagai jawaban atas pertanyaan yang telah ditandai sebagai duplikat: Menghapus koordinat dari daftar di python

Ada dua masalah dalam kode Anda:

1) Saat menggunakan remove (), Anda mencoba menghapus integer sedangkan Anda perlu menghapus tuple.

2) Loop for akan melewatkan item dalam daftar Anda.

Mari kita jalankan apa yang terjadi ketika kami mengeksekusi kode Anda:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

Masalah pertama adalah Anda memberikan 'a' dan 'b' untuk menghapus (), tetapi menghapus () hanya menerima argumen tunggal. Jadi bagaimana kami bisa menghapus () agar berfungsi dengan baik dengan daftar Anda? Kami perlu mencari tahu apa setiap elemen daftar Anda. Dalam hal ini, masing-masing adalah tuple. Untuk melihat ini, mari kita akses satu elemen daftar (pengindeksan dimulai pada 0):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

Aha! Setiap elemen L1 sebenarnya adalah tuple. Jadi itu yang harus kita lewati untuk menghapus (). Tuple dalam python sangat mudah, mereka hanya dibuat dengan menyertakan nilai dalam tanda kurung. "a, b" bukan tuple, tapi "(a, b)" adalah tuple. Jadi kami memodifikasi kode Anda dan menjalankannya lagi:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

Kode ini berjalan tanpa kesalahan, tetapi mari kita lihat daftar yang dihasilkannya:

L1 is now: [(1, 2), (5, 6), (1, -2)]

Mengapa (1, -2) masih ada di daftar Anda? Ternyata memodifikasi daftar sambil menggunakan loop untuk beralih lebih dari itu adalah ide yang sangat buruk tanpa perawatan khusus. Alasan bahwa (1, -2) tetap dalam daftar adalah bahwa lokasi setiap item dalam daftar berubah antara iterasi dari for loop. Mari kita lihat apa yang terjadi jika kita memberi kode yang lebih panjang pada daftar di atas:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Seperti yang dapat Anda simpulkan dari hasil itu, setiap kali pernyataan bersyarat mengevaluasi ke true dan item daftar dihapus, iterasi loop berikutnya akan melewati evaluasi item berikutnya dalam daftar karena nilainya sekarang berada di indeks yang berbeda.

Solusi paling intuitif adalah menyalin daftar, kemudian beralih ke daftar asli dan hanya memodifikasi salinan. Anda dapat mencoba melakukannya seperti ini:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

Namun, hasilnya akan sama dengan sebelumnya:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Ini karena ketika kita membuat L2, python tidak benar-benar membuat objek baru. Sebaliknya, itu hanya mereferensikan L2 ke objek yang sama dengan L1. Kami dapat memverifikasi ini dengan 'is' yang berbeda dari sekadar "sama dengan" (==).

>>> L2=L1
>>> L1 is L2
True

Kita dapat membuat salinan yang benar menggunakan copy.copy (). Kemudian semuanya berfungsi seperti yang diharapkan:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Akhirnya, ada satu solusi yang lebih bersih daripada harus membuat salinan L1 yang sama sekali baru. Fungsi terbalik ():

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Sayangnya, saya tidak bisa menjelaskan cara membalikkan () bekerja. Ini mengembalikan objek 'listreverseiterator' ketika daftar diteruskan ke sana. Untuk tujuan praktis, Anda dapat menganggapnya sebagai membuat salinan argumennya yang terbalik. Ini solusi yang saya rekomendasikan.

Cinghiale
sumber
4

Jika Anda ingin melakukan hal lain selama iterasi, mungkin lebih baik untuk mendapatkan kedua indeks (yang menjamin Anda dapat referensi itu, misalnya jika Anda memiliki daftar dicts) dan isi item daftar yang sebenarnya.

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumeratememberi Anda akses ke item dan indeks sekaligus. reversedadalah agar indeks yang akan Anda hapus nanti tidak berubah pada Anda.

fantabolous
sumber
Mengapa mendapatkan indeks lebih relevan dalam kasus di mana Anda memiliki daftar dicts daripada dalam kasus jenis daftar lainnya? Ini tidak masuk akal sejauh yang saya tahu.
Mark Amery
4

Anda mungkin ingin menggunakan filter() tersedia sebagai bawaan.

Untuk lebih jelasnya cek di sini

Xolve
sumber
4

Sebagian besar jawaban di sini ingin Anda membuat salinan daftar. Saya memiliki kasus penggunaan di mana daftarnya cukup panjang (110 ribu item) dan lebih pintar untuk terus mengurangi daftarnya.

Pertama-tama Anda harus mengganti foreach loop dengan while ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

Nilai itidak berubah di blok if karena Anda ingin mendapatkan nilai item baru dari indeks yang sama, setelah item lama dihapus.

Mujeeb
sumber
3

Anda dapat mencoba untuk mengulang secara terbalik sehingga untuk some_list Anda akan melakukan sesuatu seperti:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

Dengan cara ini indeks selaras dan tidak mengalami pembaruan daftar (terlepas apakah Anda pop elemen atau tidak).

Queequeg
sumber
Looping reversed(list(enumerate(some_list)))akan lebih mudah daripada menghitung indeks sendiri.
Mark Amery
@MarkAmery tidak berpikir Anda dapat mengubah daftar dengan cara ini.
Queequeg
3

Salah satu solusi yang mungkin, berguna jika Anda ingin tidak hanya menghapus beberapa hal, tetapi juga melakukan sesuatu dengan semua elemen dalam satu loop:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1
Alexey
sumber
Anda harus benar-benar hanya menggunakan pemahaman. Mereka jauh lebih mudah dimengerti.
Beefster
Bagaimana jika saya ingin menghapus badsesuatu, melakukan sesuatu dengannya dan juga melakukan sesuatu dengan goodsesuatu dalam satu lingkaran?
Alexey
1
Sebenarnya, saya menyadari ada beberapa kepintaran di sini bahwa Anda membuat salinan daftar dengan irisan terbuka ( alist[:]) Dan karena Anda mungkin melakukan sesuatu yang mewah, itu sebenarnya memiliki use case. Revisi bagus itu bagus. Ambil upvote saya.
Beefster
2

Saya perlu melakukan sesuatu yang serupa dan dalam kasus saya masalahnya adalah memori - saya perlu menggabungkan beberapa objek dataset dalam daftar, setelah melakukan beberapa hal dengan mereka, sebagai objek baru, dan perlu menyingkirkan setiap entri yang saya gabungkan. hindari menduplikasi semuanya dan meledakkan memori. Dalam kasus saya memiliki objek dalam kamus, bukan daftar berfungsi dengan baik:

`` `

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

`` `

rafa
sumber
2

TLDR:

Saya menulis perpustakaan yang memungkinkan Anda melakukan ini:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

Cara terbaik adalah menggunakan metode lain jika mungkin yang tidak mengharuskan Anda memodifikasi iterable saat iterating di atasnya, tetapi untuk beberapa algoritma mungkin tidak semudah itu. Dan jika Anda yakin benar-benar menginginkan pola kode yang dijelaskan dalam pertanyaan awal, itu mungkin.

Harus bekerja pada semua urutan yang bisa berubah, bukan hanya daftar.


Jawaban lengkap:

Sunting: Contoh kode terakhir dalam jawaban ini memberikan kasus penggunaan mengapa Anda kadang-kadang ingin mengubah daftar di tempat daripada menggunakan pemahaman daftar. Bagian pertama dari jawaban berfungsi sebagai tutorial tentang bagaimana sebuah array dapat dimodifikasi di tempat.

Solusinya mengikuti dari ini jawaban (untuk pertanyaan terkait) dari pengirim. Yang menjelaskan bagaimana indeks array diperbarui saat iterasi melalui daftar yang telah dimodifikasi. Solusi di bawah ini dirancang untuk melacak indeks array dengan benar bahkan jika daftar diubah.

Unduh fluidIter.pydari sini https://github.com/alanbacon/FluidIterator , hanya satu file sehingga tidak perlu menginstal git. Tidak ada installer sehingga Anda perlu memastikan bahwa file tersebut ada di jalur python diri Anda. Kode telah ditulis untuk python 3 dan belum diuji pada python 2.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

Ini akan menghasilkan output berikut:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

Di atas kami telah menggunakan popmetode pada objek daftar cairan. Metode iterable umum lainnya juga dilaksanakan seperti del fluidL[i], .remove, .insert, .append, .extend. Daftar ini juga dapat dimodifikasi menggunakan irisan ( sortdanreverse metode tidak diterapkan).

Satu-satunya syarat adalah Anda hanya harus mengubah daftar di tempat, jika pada titik fluidLataul dipindahkan ke objek daftar yang berbeda kode tidak akan berfungsi. fluidLObjek asli masih akan digunakan oleh for loop tetapi akan menjadi di luar ruang bagi kita untuk memodifikasi.

yaitu

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

Jika kita ingin mengakses nilai indeks saat ini dari daftar kita tidak bisa menggunakan penghitungan, karena ini hanya menghitung berapa kali loop for telah berjalan. Sebaliknya kita akan menggunakan objek iterator secara langsung.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

Ini akan menampilkan yang berikut:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

The FluidIterablekelas hanya menyediakan pembungkus untuk asli daftar objek. Objek asli dapat diakses sebagai properti dari objek fluida seperti:

originalList = fluidArr.fixedIterable

Lebih banyak contoh / tes dapat ditemukan di if __name__ is "__main__":bagian di bagian bawahfluidIter.py . Ini layak untuk dilihat karena menjelaskan apa yang terjadi dalam berbagai situasi. Seperti: Mengganti sebagian besar daftar menggunakan irisan. Atau menggunakan (dan memodifikasi) iterable yang sama di nested for loop.

Seperti yang saya nyatakan di awal: ini adalah solusi rumit yang akan mengganggu keterbacaan kode Anda dan membuatnya lebih sulit untuk di-debug. Karena itu solusi lain seperti daftar pemahaman yang disebutkan dalam jawaban David Raznick harus dipertimbangkan terlebih dahulu. Yang sedang berkata, saya telah menemukan saat-saat di mana kelas ini bermanfaat bagi saya dan lebih mudah digunakan daripada melacak indeks elemen-elemen yang perlu dihapus.


Sunting: Seperti yang disebutkan dalam komentar, jawaban ini tidak benar-benar menimbulkan masalah dimana pendekatan ini memberikan solusi. Saya akan mencoba mengatasinya di sini:

Pemahaman daftar menyediakan cara untuk menghasilkan daftar baru tetapi pendekatan ini cenderung melihat setiap elemen secara terpisah daripada keadaan saat ini daftar secara keseluruhan.

yaitu

newList = [i for i in oldList if testFunc(i)]

Tetapi bagaimana jika hasil dari testFunctergantung pada elemen yang telah ditambahkan newList? Atau elemen-elemennya masih adaoldList yang mungkin ditambahkan selanjutnya? Mungkin masih ada cara untuk menggunakan pemahaman daftar tetapi akan mulai kehilangan keanggunannya, dan bagi saya rasanya lebih mudah untuk memodifikasi daftar di tempat.

Kode di bawah ini adalah salah satu contoh algoritma yang mengalami masalah di atas. Algoritma akan mengurangi daftar sehingga tidak ada elemen yang merupakan kelipatan dari elemen lainnya.

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

Output dan daftar pengurangan akhir ditunjukkan di bawah ini

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]
Resonansi
sumber
Sulit untuk mengatakan apakah ini over-engineered karena tidak jelas masalah apa yang ingin diselesaikan; apa yang dicapai dengan menghapus elemen yang menggunakan pendekatan ini yang some_list[:] = [x for x in some_list if not some_condition(x)]tidak tercapai? Tanpa jawaban untuk itu, mengapa ada yang percaya bahwa mengunduh dan menggunakan perpustakaan 600-line Anda lengkap dengan kesalahan ketik dan kode komentar adalah solusi yang lebih baik untuk masalah mereka daripada one-liner? -1.
Mark Amery
@MarkAmery. Kasus penggunaan utama untuk saat ini adalah ketika mencoba untuk menentukan apakah suatu item harus dihapus (atau ditambahkan atau dipindahkan) berdasarkan bukan hanya pada item itu sendiri, tetapi pada keadaan item lain dalam daftar atau keadaan daftar sebagai seluruh. Misalnya, tidak mungkin dengan pemahaman daftar untuk menulis sesuatu seperti di some_list[:] = [x for x in some_list if not some_condition(y)]mana yelemen daftar berbeda x. Juga tidak mungkin untuk menulis some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Resonansi
2

Metode yang paling efektif adalah daftar pemahaman, banyak orang menunjukkan kasus mereka, tentu saja, itu juga merupakan cara yang baik untuk mendapatkan iteratormelalui filter.

Filtermenerima fungsi dan urutan. Filtermenerapkan fungsi yang diteruskan ke setiap elemen pada gilirannya, dan kemudian memutuskan apakah akan mempertahankan atau membuang elemen tergantung pada apakah nilai pengembalian fungsi adalah Trueatau False.

Ada sebuah contoh (dapatkan peluang di tuple):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Perhatian: Anda juga tidak bisa menangani iterator. Iterator terkadang lebih baik daripada urutan.

chseng
sumber
2

untuk loop akan beralih melalui indeks ..

menganggap Anda memiliki daftar,

[5, 7, 13, 29, 65, 91]

Anda telah menggunakan variabel daftar yang dipanggil lis . dan Anda menggunakan yang sama untuk menghapus ..

variabel Anda

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

selama 5 iterasi,

Anda nomor 35 itu tidak prima sehingga Anda dihapus dari daftar.

lis.remove(y)

dan kemudian nilai selanjutnya (65) beralih ke indeks sebelumnya.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

jadi iterasi ke-4 dilakukan pointer pindah ke 5 ..

Itulah mengapa loop Anda tidak mencakup 65 sejak dipindahkan ke indeks sebelumnya.

jadi Anda tidak harus merujuk daftar ke variabel lain yang masih referensi asli daripada salinan.

ite = lis #dont do it will reference instead copy

begitu juga salinan daftar menggunakan list[::]

sekarang kamu akan memberi,

[5, 7, 13, 29]

Masalahnya adalah Anda menghapus nilai dari daftar selama iterasi maka indeks daftar Anda akan runtuh.

jadi Anda bisa mencoba pemahaman sebagai gantinya.

yang mendukung semua iterable seperti, daftar, tuple, dict, string dll

Mohideen bin Mohammed
sumber
Ini membantu saya memahami mengapa kode saya gagal.
Wahid Sadik
2

Jika Anda ingin menghapus elemen dari daftar saat iterasi, gunakan loop-sementara sehingga Anda dapat mengubah indeks saat ini dan indeks akhir setelah setiap penghapusan.

Contoh:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1
Tanpa nama
sumber
1

Jawaban lainnya benar bahwa biasanya ide buruk dihapus dari daftar yang Anda iterasi. Membalikkan iterasi menghindari jebakan, tetapi jauh lebih sulit untuk mengikuti kode yang melakukan itu, jadi biasanya Anda lebih baik menggunakan daftar pemahaman atau filter.

Namun, ada satu kasus di mana aman untuk menghapus elemen dari urutan yang Anda iterasi: jika Anda hanya menghapus satu item saat Anda iterasi. Ini dapat dipastikan menggunakan a returnatau a break. Sebagai contoh:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

Ini seringkali lebih mudah dipahami daripada pemahaman daftar ketika Anda melakukan beberapa operasi dengan efek samping pada item pertama dalam daftar yang memenuhi beberapa kondisi dan kemudian menghapus item itu dari daftar segera setelahnya.

Beefster
sumber
1

Saya dapat memikirkan tiga pendekatan untuk menyelesaikan masalah Anda. Sebagai contoh, saya akan membuat daftar tuple secara acak somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. Kondisi yang saya pilih adalah sum of elements of a tuple = 15. Dalam daftar terakhir kita hanya akan memiliki tupel-tupel yang jumlahnya tidak sama dengan 15.

Apa yang saya pilih adalah contoh yang dipilih secara acak. Jangan ragu untuk mengubah dalam daftar tupel dan kondisi yang saya telah memilih.

Metode 1.> Gunakan kerangka kerja yang Anda sarankan (di mana seseorang mengisi kode di dalam for for loop). Saya menggunakan kode kecil dengan deluntuk menghapus tuple yang memenuhi kondisi tersebut. Namun, metode ini akan kehilangan tupel (yang memenuhi kondisi tersebut) jika dua tupel yang ditempatkan secara berurutan memenuhi kondisi yang diberikan.

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Metode 2.> Buat daftar baru yang berisi elemen (tuple) di mana kondisi yang diberikan tidak terpenuhi (ini adalah hal yang sama dengan menghapus elemen daftar di mana kondisi yang diberikan terpenuhi). Berikut ini adalah kode untuk itu:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Metode 3.> Temukan indeks di mana kondisi yang diberikan terpenuhi, dan kemudian gunakan elemen hapus (tupel) yang sesuai dengan indeks tersebut. Berikut ini adalah kode untuk itu.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Metode 1 dan metode 2 lebih cepat dari metode 3 . Metode2 dan metode3 lebih efisien daripada metode1. Saya lebih suka metode2 . Untuk contoh di atas,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Siddharth Satpathy
sumber
0

Untuk apa pun yang berpotensi menjadi sangat besar, saya menggunakan yang berikut ini.

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

Itu harusnya jauh lebih cepat dari yang lainnya.

CENTURION
sumber
Dari apa yang saya ukur, NumPy mulai lebih cepat untuk daftar lebih dari 20 elemen, dan mencapai penyaringan> 12x lebih cepat untuk daftar besar 1000 elemen dan banyak lagi.
Georgy
0

Dalam beberapa situasi, saat Anda melakukan lebih dari sekadar memfilter daftar item satu per satu, Anda ingin iterasi Anda berubah saat iterasi.

Berikut adalah contoh di mana menyalin daftar sebelumnya salah, iterasi terbalik tidak mungkin dan pemahaman daftar juga bukan pilihan.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p
CodeKid
sumber
0

Jika Anda akan menggunakan daftar baru nanti, Anda bisa mengatur elem ke Tidak ada, dan kemudian menilai di loop kemudian, seperti ini

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

Dengan cara ini, Anda tidak perlu menyalin daftar dan lebih mudah dimengerti.

john zhang
sumber
-1

susun daftar nomor dan Anda ingin menghapus semua tidak ada yang habis dibagi 3,

list_number =[i for i in range(100)]

menggunakan list comprehension, ini akan membuat daftar baru dan membuat ruang memori baru

new_list =[i for i in list_number if i%3!=0]

menggunakan lambda filterfungsi, ini akan membuat daftar baru yang dihasilkan dan mengkonsumsi ruang memeory

new_list = list(filter(lambda x:x%3!=0, list_number))

tanpa menghabiskan ruang memori untuk daftar baru dan memodifikasi daftar yang ada

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
sumber