Menghapus dari larik selama pencacahan di Swift?

86

Saya ingin menghitung melalui array di Swift, dan menghapus item tertentu. Saya bertanya-tanya apakah ini aman untuk dilakukan, dan jika tidak, bagaimana saya bisa mencapai ini.

Saat ini, saya akan melakukan ini:

for (index, aString: String) in enumerate(array) {
    //Some of the strings...
    array.removeAtIndex(index)
}
Andrew
sumber

Jawaban:

72

Di Swift 2 ini cukup mudah digunakan enumeratedan reverse.

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
    a.removeAtIndex(i)
}
print(a)
Johnston
sumber
1
Bekerja tetapi filter benar-benar cara yang harus dilakukan
13
@Sayangkoeh "Saya ingin menghitung melalui array di Swift, dan menghapus item tertentu." filtermengembalikan array baru. Anda tidak menghapus apa pun dari larik. Saya bahkan tidak akan memanggil filterpencacahan. Selalu ada lebih dari satu cara untuk menguliti kucing.
Johnston
6
rigth, my bad! Tolong jangan kulit kucing apa pun
56

Anda mungkin mempertimbangkan filtercara:

var theStrings = ["foo", "bar", "zxy"]

// Filter only strings that begins with "b"
theStrings = theStrings.filter { $0.hasPrefix("b") }

Parameter dari filterhanyalah sebuah closure yang mengambil sebuah instance tipe array (dalam hal ini String) dan mengembalikan a Bool. Ketika hasilnya adalah trueitu menyimpan elemen, jika tidak, elemen akan disaring.

Matteo Piombo
sumber
16
Saya akan membuat eksplisit yang filtertidak memperbarui array, itu hanya mengembalikan yang baru
Antonio
Tanda kurung harus dihapus; itu penutupan tertinggal.
Jessy
@Anda benar. Memang itulah mengapa saya mempostingnya sebagai solusi yang lebih aman. Solusi yang berbeda dapat dipertimbangkan untuk array yang sangat besar.
Matteo Piombo
Hm, seperti yang Anda katakan ini mengembalikan array baru. Apakah mungkin untuk membuat filtermetode menjadi mutatingsatu kemudian (karena saya telah membaca mutatingkata kunci memungkinkan fungsi seperti ini untuk mengubah selfsebagai gantinya)?
ampun
@ Gee.E tentu saja Anda dapat menambahkan filter di tempat sebagai ekstensi untuk Arraymenandainya sebagai mutatingdan mirip dengan kode pertanyaan. Bagaimanapun pertimbangkan bahwa ini mungkin tidak selalu menjadi keuntungan. Bagaimanapun setiap kali Anda menghapus objek, array Anda mungkin diatur ulang dalam memori. Dengan demikian dapat lebih efisien mengalokasikan larik baru dan kemudian membuat substitusi atom dengan hasil fungsi filter. Kompilator dapat melakukan lebih banyak pengoptimalan, bergantung pada kode Anda.
Matteo Piombo
38

Di Swift 3 dan 4 , ini akan menjadi:

Dengan angka, menurut jawaban Johnston:

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
   a.remove(at: i)
}
print(a)

Dengan string sebagai pertanyaan OP:

var b = ["a", "b", "c", "d", "e", "f"]

for (i,str) in b.enumerated().reversed()
{
    if str == "c"
    {
        b.remove(at: i)
    }
}
print(b)

Namun, sekarang di Swift 4.2 atau yang lebih baru, ada cara yang lebih baik dan lebih cepat yang direkomendasikan oleh Apple di WWDC2018:

var c = ["a", "b", "c", "d", "e", "f"]
c.removeAll(where: {$0 == "c"})
print(c)

Cara baru ini memiliki beberapa keunggulan:

  1. Ini lebih cepat daripada penerapan dengan filter.
  2. Itu menghilangkan kebutuhan untuk membalikkan array.
  3. Ini menghapus item di tempat, dan dengan demikian memperbarui larik asli alih-alih mengalokasikan dan mengembalikan larik baru.
jvarela.dll
sumber
bagaimana jika item adalah sebuah objek dan saya perlu memeriksanya, {$0 === Class.self}tidak berfungsi
TomSawyer
14

Ketika sebuah elemen pada indeks tertentu dihapus dari sebuah array, semua elemen berikutnya akan mengalami perubahan posisi (dan indeks), karena mereka bergeser mundur satu posisi.

Jadi cara terbaik adalah menavigasi array dalam urutan terbalik - dan dalam hal ini saya sarankan menggunakan for loop tradisional:

for var index = array.count - 1; index >= 0; --index {
    if condition {
        array.removeAtIndex(index)
    }
}

Namun menurut saya pendekatan terbaik adalah dengan menggunakan filtermetode tersebut, seperti yang dijelaskan oleh @perlfly dalam jawabannya.

Antonio
sumber
tapi sayangnya itu telah dihapus dengan cepat
Sergey Brazhnik
4

Tidak, tidak aman untuk mengubah array selama enumaration, kode Anda akan macet.

Jika Anda ingin menghapus hanya beberapa objek, Anda dapat menggunakan filterfungsi tersebut.

Starscream
sumber
3
Ini tidak benar untuk Swift. Array adalah tipe nilai , jadi mereka "disalin" saat diteruskan ke fungsi, ditetapkan ke variabel, atau digunakan dalam pencacahan. (Swift mengimplementasikan fungsionalitas salin-saat-tulis untuk tipe nilai, jadi penyalinan aktual dijaga seminimal mungkin.) Coba hal berikut untuk memverifikasi: var x = [1, 2, 3, 4, 5]; cetak (x); var i = 0; untuk v dalam x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; cetak (x)
404 tidak ditemukan pada
Ya, Anda benar, asalkan Anda tahu persis apa yang Anda lakukan. Mungkin saya tidak mengungkapkan tanggapan saya dengan jelas. Saya seharusnya mengatakan Itu mungkin tetapi itu tidak aman . Ini tidak aman karena Anda mengubah ukuran penampung dan jika Anda membuat kesalahan dalam kode Anda, aplikasi Anda akan mogok. Semua tentang Swift tentang menulis kode aman yang tidak akan mogok secara tak terduga saat runtime. Itu sebabnya menggunakan fungsi pemrograman functionnal seperti filterini lebih aman . Inilah contoh bodoh saya:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream
Saya hanya ingin membedakan bahwa dimungkinkan untuk memodifikasi koleksi yang disebutkan di Swift, sebagai lawan dari perilaku pengecualian-melempar saat melakukan iterasi melalui NSArray dengan enumerasi cepat atau bahkan tipe koleksi C #. Bukan modifikasi yang akan menimbulkan pengecualian di sini, tetapi kemungkinan untuk salah mengelola indeks dan melampaui batas (karena mereka telah memperkecil ukurannya). Tetapi saya sangat setuju dengan Anda bahwa biasanya lebih aman dan lebih jelas untuk menggunakan jenis metode pemrograman fungsional untuk memanipulasi koleksi. Terutama di Swift.
404 tidak ditemukan pada
2

Buatlah larik yang bisa berubah untuk menyimpan item yang akan dihapus dan kemudian, setelah pencacahan, hapus item tersebut dari aslinya. Atau, buat salinan dari array (tidak dapat diubah), menghitung itu dan menghapus objek (bukan dengan indeks) dari aslinya saat menghitung.

Gerbong
sumber
2

Perulangan for tradisional dapat diganti dengan perulangan while sederhana, berguna jika Anda juga perlu melakukan beberapa operasi lain pada setiap elemen sebelum penghapusan.

var index = array.count-1
while index >= 0 {

     let element = array[index]
     //any operations on element
     array.remove(at: index)

     index -= 1
}
Locutus
sumber
1

Saya merekomendasikan untuk mengatur elemen menjadi nol selama pencacahan, dan setelah menyelesaikan hapus semua elemen kosong menggunakan metode array filter ().

freele
sumber
1
Itu hanya berfungsi jika tipe yang disimpan adalah opsional. Perhatikan juga bahwa filtermetode ini tidak menghapus, ini menghasilkan array baru.
Antonio
Setuju. Urutan terbalik adalah solusi yang lebih baik.
freele
0

Sebagai tambahan, jika Anda memiliki banyak array dan setiap elemen dalam indeks N dari array A terkait dengan indeks N dari array B, maka Anda masih dapat menggunakan metode untuk membalikkan array yang disebutkan (seperti jawaban sebelumnya). Tapi ingat bahwa saat mengakses dan menghapus elemen dari array lain, tidak perlu membalikkannya.

Like so, (one can copy and paste this on Playground)

var a = ["a", "b", "c", "d"]
var b = [1, 2, 3, 4]
var c = ["!", "@", "#", "$"]

// remove c, 3, #

for (index, ch) in a.enumerated().reversed() {
    print("CH: \(ch). INDEX: \(index) | b: \(b[index]) | c: \(c[index])")
    if ch == "c" {
        a.remove(at: index)
        b.remove(at: index)
        c.remove(at: index)
    }
}

print("-----")
print(a) // ["a", "b", "d"]
print(b) // [1, 2, 4]
print(c) // ["!", "@", "$"]
Glenn Posadas
sumber