从对象数组中删除匹配的项?

8

我有一个像这样的对象数组:

var myArr = [
  MyObject(name: "Abc", description: "Lorem ipsum 1."),
  MyObject(name: "Def", description: "Lorem ipsum 2."),
  MyObject(name: "Xyz", description: "Lorem ipsum 3.")
]

我知道我可以像这样找到匹配的项: ```html

我知道我可以像这样找到匹配的项:

```
var temp = myArr.filter { $0.name == "Def" }.first

但现在我该如何从原始的myArr中删除它?我希望filter.first可以以某种方式返回索引,这样我就可以使用removeAtIndex。更好的是,我想做这样的事情:

myArr.removeAll { $0.name == "Def" } // Pseudo

有什么想法吗?

2
myArr = myArr.filter { $0.name != "Def" }有什么问题吗? - matt
因为这迫使我创建一个新变量。我想修改现有的 myArr 变量。 - TruMan1
不,它并没有。myArr仍然是myArr。而且这是一个值类型;你不能在原地改变它!即使你编写了一个可变的removeAll方法,你也总是会创建一个新的数组。 - matt
1
这里讨论了一个removeAll()的实现:http://codereview.stackexchange.com/questions/86581/removeallclosure-in-swift。 - Martin R
嗨@matt。在Swift中,我该如何替换一个与另一个对象相同的数组对象为不同的对象,就像我们在Objective C中所做的那样?我无法理解。请帮忙。 - iPeter
@iPeter 请不要在评论中提问。如果你有问题,请提出一个新的“问题(question)”。 - matt
5个回答

25

你需要明确的是,数组是一个结构体,因此它是一个值类型。它不能像类实例一样在原地进行变异。因此,即使你扩展数组以编写可变的removeIf方法,你总是会在幕后创建一个新数组。

因此,在使用filter和逻辑否定闭包条件时不存在任何劣势或广义丧失:

myArr = myArr.filter { $0.name != "Def" }

例如,你可以像这样编写removeIf(译者注:此处原文括号内为“可能”)
extension Array {
    mutating func removeIf(closure:(T -> Bool)) {
        for (var ix = self.count - 1; ix >= 0; ix--) {
            if closure(self[ix]) {
                self.removeAtIndex(ix)
            }
        }
    }
}

然后您可以像这样使用它:

myArr.removeIf {$0.name == "Def"}

但实际上,这是浪费你时间的大头。你在这里做的事情,filter已经做了。从myArr.removeIf语法来看,你似乎在就地修改myArr,但实际上你并没有;你是用另一个数组替换它。实际上,在循环中每次调用removeAtIndex都会创建另一个数组!所以你不妨使用filter,让自己感到愉快。


mutating func removeAtIndex() 真的会创建一个新数组吗? - Martin R
@MartinR 确定。试试这个:var arr = [1,2,3] { didSet { println("did") } }; arr.removeAtIndex(1)didSet 被调用,证明数组已被替换。将变量 arr 视为一个鞋盒;我们从鞋盒中取出了数组并放入了另一个数组。这就是值类型的工作原理。对值类型实例的可变函数调用将该实例替换为新实例。 - matt
@matt:arr[0] = 2也会触发didSet。我不是不相信你,但如果每次对数组元素的赋值都创建一个全新的数组,那么Array在任何严肃的工作中都将变得难以使用(想象一下有1000个方程和1000个未知数的线性方程求解器)。 - Martin R
@MartinR 不,我们可能还不知道元素存储的效率(并且有足够的理由相信确实如此)。它可能是一个新数组,而不创建任何新的_元素_。然而,数组引用本身不能就地发生变化。每次通过任何方式对数组进行变异时,都会替换它本身。 - matt
@MartinR "arr[0] = 2 触发了 didSet。" 没错,这并不是反驳我所说的内容,而是我所说的内容。如果你尝试使用引用类型进行相同类型的操作,你会发现情况并非如此。替换 MyObject 实例的 name 属性时,引用并没有被替换(也没有调用 didSet)。- 你可能需要查看一下我的书中对这个主题的讨论:http://www.apeth.com/swiftBook/ch04.html#SECreferenceTypes - matt
显示剩余6条评论

5

Apple在Swift 4中添加了你想要的功能:

var phrase = "The rain in Spain stays mainly in the plain."
let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
phrase.removeAll(where: { vowels.contains($0) })
// phrase == "Th rn n Spn stys mnly n th pln."

1

如果您想从正在迭代的数组中删除对象,您应该始终向后迭代,否则您将在某个时刻处理不再有效的索引。

var myArr = [
    ["key":"value1"],
    ["key":"value2"],
    ["key":"value3"]
]

for index in stride(from: myArr.count - 1 , to: 0, by: -1){
    let x = myArr[index]
    if x["key"] == "value2"{
        myArr.removeAtIndex(index)
    }
}

1
使用过滤器获取对象,然后循环遍历数组并使用myArr.removeAtIndex(index)来删除每个对象。使用过滤器正是这样做的。要理解发生了什么,请阅读以下内容。Matts的答案是更清晰的方法,因为您正在测试相反的匹配,因此除非它与您的值匹配,否则会保留每个对象。
循环遍历您的临时过滤器数组。
if let index = find(temp, note) {
   myArr.removeAtIndex(index)
}

For-loop是找到索引的唯一方式吗?是否有优雅的闭包可以实现这个功能? - TruMan1
find 只适用于简单数组,而不适用于对象数组,对吧?我无法让 find 在对象数组中正常工作。 - TruMan1
你已经使用过滤器获取了所有的对象,然后可以在循环中使用find来从另一个数组中移除每个对象。 - Mark McCorkle
“filter”是“优雅闭包”的一种(顺便说一下,它也是一个循环)。 - matt
确切地说,可能会有混淆,不知道每次迭代如何使用,以及他是否想要保留原始数组?仅测试负数即可实现“从数组中删除”的效果,如果这是他所需要的。 - Mark McCorkle
“elegant”是find方法,因为filter和else方法会创建新的数组。 - dimpiax

0

这篇文章虽然早已被广泛回答,但是作为对Matt上面回答的一点补充,如果只是要删除一个元素,你也可以使用以下简单方法:

extension Array {
    public func dropFirst(matching: (Element) -> Bool) -> ArraySlice<Element> {
        guard let at = self.firstIndex(where: matching) else { return ArraySlice(self) }
        return prefix(at) + suffix(from: at).dropFirst()
    }
}

一旦找到要删除的元素,就会立即短路的操作

其中:

[1, 2, 3, 4, 5].dropFirst(matching: { $0 == 3 }) // -> [1, 2, 4, 5] (ArraySlice)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接