Xóa khỏi mảng trong quá trình liệt kê trong Swift?


86

Tôi muốn liệt kê thông qua một mảng trong Swift và xóa một số mục nhất định. Tôi tự hỏi liệu điều này có an toàn để thực hiện không, và nếu không, làm thế nào để đạt được điều này.

Hiện tại, tôi đang làm điều này:

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

Câu trả lời:


72

Trong Swift 2, điều này khá dễ sử dụng enumeratereverse.

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
    a.removeAtIndex(i)
}
print(a)

1
Hoạt động nhưng bộ lọc thực sự là con đường để đi

13
@Mayerz Sai. "Tôi muốn liệt kê thông qua một mảng trong Swift và xóa một số mục nhất định." filtertrả về một mảng mới. Bạn không xóa bất kỳ thứ gì khỏi mảng. Tôi thậm chí sẽ không gọi filtermột bảng kê. Luôn có nhiều cách để lột da mèo.
Johnston

6
nghiêm trọng, xấu của tôi! Pla dont da bất kỳ mèo

56

Bạn có thể xem xét filtercách:

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

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

Tham số của filterchỉ là một bao đóng nhận một thể hiện kiểu mảng (trong trường hợp này String) và trả về a Bool. Khi kết quả là truenó giữ nguyên phần tử, nếu không phần tử sẽ bị lọc ra.


16
Tôi sẽ nói rõ rằng filterkhông cập nhật mảng, nó chỉ trả về một mảng mới
Antonio

Dấu ngoặc đơn nên được xóa; đó là một sự đóng cửa theo sau.
Jessy

@Antonio bạn nói đúng. Quả thực đó là lý do tại sao tôi đăng nó như một giải pháp an toàn hơn. Một giải pháp khác có thể được xem xét cho các mảng lớn.
Matteo Piombo

Hm, như bạn nói điều này trả về một mảng mới. Có thể biến filterphương thức thành một phương thức mutatingsau đó không (vì tôi đã đọc mutatingtừ khóa cho phép các hàm như thế này thay đổi self)?
Gee.E

@ Gee.E chắc chắn bạn có thể thêm bộ lọc tại chỗ làm tiện ích mở rộng khi Arrayđánh dấu nó là mutatingvà tương tự như mã của câu hỏi. Dù sao hãy cân nhắc rằng điều này có thể không phải lúc nào cũng là một lợi thế. Dù sao mỗi khi bạn xóa một đối tượng, mảng của bạn có thể được tổ chức lại trong bộ nhớ. Do đó, nó có thể hiệu quả hơn khi phân bổ một mảng mới và sau đó thực hiện thay thế nguyên tử bằng kết quả của hàm bộ lọc. Trình biên dịch có thể làm tối ưu hơn nữa, tùy thuộc vào mã của bạn.
Matteo Piombo

38

Trong Swift 3 và 4 , điều này sẽ là:

Với những con số, theo câu trả lời của Johnston:

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

Với chuỗi là câu hỏi của 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)

Tuy nhiên, bây giờ trong Swift 4.2 trở lên, thậm chí còn có một cách tốt hơn, nhanh hơn đã được Apple đề xuất trong WWDC2018:

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

Cách mới này có một số ưu điểm:

  1. Nó nhanh hơn triển khai với filter.
  2. Nó không cần phải đảo ngược các mảng.
  3. Nó loại bỏ các mục tại chỗ và do đó nó cập nhật mảng ban đầu thay vì phân bổ và trả về một mảng mới.

nếu mục là một đối tượng và tôi cần phải kiểm tra xem nó, {$0 === Class.self}không làm việc
TomSawyer

14

Khi một phần tử tại một chỉ mục nhất định bị xóa khỏi mảng, tất cả các phần tử tiếp theo sẽ bị thay đổi vị trí (và chỉ mục) của chúng, bởi vì chúng dịch chuyển ngược lại một vị trí.

Vì vậy, cách tốt nhất là điều hướng mảng theo thứ tự ngược lại - và trong trường hợp này, tôi khuyên bạn nên sử dụng vòng lặp for truyền thống:

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

Tuy nhiên, theo ý kiến ​​của tôi, cách tiếp cận tốt nhất là sử dụng filterphương pháp, như được mô tả bởi @perlfly trong câu trả lời của anh ấy.


nhưng rất tiếc là nó đã bị xóa nhanh chóng vào ngày 3
Sergey Brazhnik

4

Không, thay đổi các mảng trong quá trình tách là không an toàn, mã của bạn sẽ bị lỗi.

Nếu bạn chỉ muốn xóa một vài đối tượng, bạn có thể sử dụng filterchức năng này.


3
Điều này không chính xác đối với Swift. Mảng là kiểu giá trị , vì vậy chúng được "sao chép" khi chúng được truyền cho các hàm, được gán cho các biến hoặc được sử dụng trong liệt kê. (Swift triển khai chức năng copy-on-write cho các kiểu giá trị, vì vậy việc sao chép thực tế được giữ ở mức tối thiểu.) Hãy thử cách sau để xác minh: var x = [1, 2, 3, 4, 5]; in (x); var i = 0; for v in x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; print (x)
404compilernotfound

Đúng, bạn đúng, miễn là bạn biết chính xác những gì bạn đang làm. Có lẽ tôi đã không thể hiện rõ ràng phản ứng của mình. Lẽ ra tôi phải nói Có thể nhưng nó không an toàn . Nó không an toàn vì bạn đang thay đổi kích thước vùng chứa và nếu bạn nhập sai mã, ứng dụng của bạn sẽ bị lỗi. Swift là tất cả về cách viết mã an toàn sẽ không bị lỗi bất ngờ trong thời gian chạy. Đó là lý do tại sao sử dụng các hàm lập trình chức năng như filteran toàn hơn . Đây là một ví dụ ngớ ngẩn của tôi:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream

Tôi chỉ muốn phân biệt rằng có thể sửa đổi bộ sưu tập đang được liệt kê trong Swift, trái ngược với hành vi ném ngoại lệ khi làm như vậy khi lặp qua NSArray với tính năng liệt kê nhanh hoặc thậm chí các loại bộ sưu tập của C #. Đó không phải là sự sửa đổi sẽ tạo ra một ngoại lệ ở đây, mà là khả năng quản lý sai các chỉ mục và đi ra ngoài giới hạn (vì chúng đã giảm kích thước). Nhưng tôi chắc chắn đồng ý với bạn rằng thường an toàn hơn và rõ ràng hơn khi sử dụng loại phương thức lập trình chức năng để thao tác các tập hợp. Đặc biệt là trong Swift.
404compilernotfound

2

Tạo một mảng có thể thay đổi để lưu trữ các mục sẽ bị xóa và sau đó, sau khi liệt kê, xóa các mục đó khỏi bản gốc. Hoặc, tạo một bản sao của mảng (không thay đổi), liệt kê mảng đó và loại bỏ các đối tượng (không theo chỉ mục) khỏi bản gốc trong khi liệt kê.


2

Vòng lặp for truyền thống có thể được thay thế bằng vòng lặp while đơn giản, hữu ích nếu bạn cũng cần thực hiện một số thao tác khác trên mỗi phần tử trước khi loại bỏ.

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

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

     index -= 1
}

1

Tôi khuyên bạn nên đặt các phần tử thành nil trong khi liệt kê và sau khi hoàn thành, hãy xóa tất cả các phần tử trống bằng cách sử dụng phương thức mảng filter ().


1
Điều đó chỉ hoạt động nếu kiểu được lưu trữ là tùy chọn. Cũng lưu ý rằng filterphương thức không loại bỏ, nó tạo ra một mảng mới.
Antonio

Đồng ý. Đảo ngược thứ tự là giải pháp tốt hơn.
do

0

Chỉ cần nói thêm, nếu bạn có nhiều mảng và mỗi phần tử trong chỉ số N của mảng A có liên quan đến chỉ số N của mảng B, thì bạn vẫn có thể sử dụng phương pháp đảo ngược mảng đã liệt kê (giống như các câu trả lời trước đây). Nhưng hãy nhớ rằng khi truy cập và xóa các phần tử của các mảng khác, không cần phải đảo ngược chúng.

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) // ["!", "@", "$"]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.