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

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)

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.


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.

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.


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.


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.


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
}

1

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


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) // ["!", "@", "$"]
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.