在Swift枚举期间从数组中删除?


86

我想枚举Swift中的数组,并删除某些项目。我想知道这样做是否安全,如果不能,我应该如何实现这一目标。

目前,我会这样做:

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

Answers:


72

在Swift 2中,使用enumerate和非常容易reverse

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

1

13
@Mayerz错误。“我想通过Swift中的数组进行枚举,并删除某些项目。” filter返回一个新数组。您没有从阵列中删除任何内容。我什至不要求filter枚举。总是有不止一种为猫皮的方法。
约翰斯顿

6
严厉,我不好!

56

您可以考虑以下filter方法:

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

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

的参数filter只是一个采用数组类型实例(在这种情况下String)并返回的闭包Bool。结果为true保留元素时,否则将滤除元素。


16
我会明确表示filter不会更新数组,它只会返回一个新数组
Antonio

括号应删除;那是一个封闭的结尾。
Jessy 2015年

@安东尼奥,你是对的。实际上,这就是为什么我将其发布为更安全的解决方案。对于大型阵列,可以考虑使用其他解决方案。
Matteo Piombo

嗯,正如您所说,这将返回一个新数组。是否有可能将filter方法合并为mutating一个方法(因为我已经阅读了该mutating关键字,而使像这样的函数可以更改self)?
Gee.E 2015年

@ Gee.E当然,您可以添加一个就地过滤器作为将其Array标记为mutating并类似于问题代码的扩展。无论如何,这可能并不总是一个优势。无论如何,每次删除对象时,阵列都可能会在内存中重新组织。因此,分配新数组然后使用filter函数的结果进行原子替换可能会更有效。取决于您的代码,编译器可以进行更多优化。
Matteo Piombo 2015年

38

Swift 3和Swift 4中,这将是:

根据约翰斯顿的答案,有数字:

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
   a.remove(at: i)
}
print(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)

但是,现在在Swift 4.2或更高版本中, Apple在WWDC2018中推荐了一种更好,更快的方法

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

这种新方法具有以下优点:

  1. 它比使用的实现更快filter
  2. 它消除了反转数组的需要。
  3. 它会就地删除项目,因此它会更新原始数组,而不是分配并返回新数组。

如果项目是一个对象并且我需要检查它,该{$0 === Class.self}怎么

14

当将某个索引处的元素从数组中删除时,所有后续元素的位置(和索引)都会改变,因为它们向后移了一个位置。

因此,最好的方法是以相反的顺序浏览数组-在这种情况下,我建议使用传统的for循环:

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

但是我认为最好的方法是使用filter@perlfly在其回答中描述的方法。


但不幸的是,它已被迅速删除3
Sergey Brazhnik,

4

不,在枚举过程中更改数组并不安全,您的代码将崩溃。

如果只想删除几个对象,则可以使用该filter功能。


3
这对于Swift来说是不正确的。数组是类型,因此在将它们传递给函数,分配给变量或用于枚举时会被“复制”。(Swift实现了针对值类型的写时复制功能,因此实际复制保持在最低限度。)请尝试以下操作进行验证:var x = [1、2、3、4、5];打印(x); var i = 0; 对于v in x {if(v%2 == 0){x.remove(at:i)} else {i + = 1}}; print(x)
404compilernotfound

是的,只要您完全知道自己在做什么,那您就是对的。也许我没有明确表达我的回应。我应该说有可能,但这并不安全。这是不安全的,因为您正在更改容器的大小,并且如果您在代码中输入错误,应用程序将崩溃。Swift的全部目的是编写安全的代码,这些代码在运行时不会意外崩溃。这就是为什么使用functionnal编程功能,例如filter安全的。这是我一个愚蠢的例子:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream'Apr

我只是想区分一下,可以修改Swift中枚举的集合,这与在使用快速枚举甚至C#的集合类型遍历NSArray时这样做的异常抛出行为相反。不是在这里引发异常的修改,而是错误管理索引和超出范围的可能性(因为它们减小了大小)。但是我绝对同意您的观点,使用功能性编程类型的方法来操纵集合通常更安全,更清晰。特别是在Swift中。
404compilernotfound

2

创建一个可变数组来存储要删除的项目,然后在枚举后从原始项目中删除这些项目。或者,创建数组的副本(不可变),进行枚举,并在枚举时从原始对象中删除对象(不按索引)。


2

传统的for循环可以替换为简单的while循环,如果您还需要在删除之前对每个元素执行一些其他操作,则很有用。

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

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

     index -= 1
}

1

我建议在枚举期间将元素设置为nil,并在完成后使用数组filter()方法删除所有空元素。


1
仅当存储的类型是可选的时才有效。还要注意,该filter方法不会删除,它会生成一个新数组。
安东尼奥

同意。倒序是更好的解决方案。
2015年

0

只需添加一下,如果您有多个数组,并且数组A的索引N中的每个元素都与数组B的索引N相关,那么您仍然可以使用反转枚举数组的方法(就像过去的答案一样)。但是请记住,在访问和删除其他数组的元素时,无需反转它们。

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) // ["!", "@", "$"]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.