从 Swift 数组中删除具有重复属性的对象

43

这里的问题涉及从数组中删除重复的对象:

在Swift中从数组中删除重复元素

我需要删除不是自身重复,而是具有特定重复属性(例如id)的对象。


我有一个包含我的Post对象的数组。每个Post都有一个id属性。

除了遍历整个数组以查找重复的Post ID,是否有更有效的方法?

for post1 in posts {
    for post2 in posts {
        if post1.id == post2.id {
            posts.removeObject(post2)
        }
    }
}

2
https://dev59.com/Q18e5IYBdhLWcg3wdqGT - dfrib
谷歌搜索“在Swift中删除数组中的重复对象”的第一个结果指向@dfri的链接... - Cristik
2
@Cristik,你提供的回复并没有回答我的问题,因为我想检查对象中的重复属性,而不是重复的对象。 - Oscar Apeland
@OscarApeland 编辑的问题会进入重新开放队列,这就是为什么我要求您更新标题以更好地反映您正在寻找的内容。顺便说一句,没有人因为您的 Swift 技能而对您大喊大叫。 - Cristik
2
@OscarApeland,那从来不是我的意图,如果你这样理解了,我很抱歉。我只是因为它的标题而将这个问题(在其当前形式下)标记为重复。正如Cristik所写,编辑后的问题可能会再次被打开。下一次,可能要更具体,例如询问如何使用链接方法(“常规”数组的副本)应用于您的对象数组,特别是它们的属性id。最后,我们都在这里互相学习和教育,永远不要害怕提问,我们也从中学习如何提问以及下一次如何提问。 - dfrib
显示剩余4条评论
16个回答

61

我将提出2个解决方案。

这两种方法都需要将Post变成HashableEquatable

使Post遵循Hashable和Equatable协议

在这里,我假设您的Post结构体(或类)具有类型为Stringid属性。

struct Post: Hashable, Equatable {
    let id: String
    var hashValue: Int { get { return id.hashValue } }
}

func ==(left:Post, right:Post) -> Bool {
    return left.id == right.id
}

解决方案1 (失去原始顺序)

要删除重复项,您可以使用Set

let uniquePosts = Array(Set(posts))

解决方案2(保持顺序)

var alreadyThere = Set<Post>()
let uniquePosts = posts.flatMap { (post) -> Post? in
    guard !alreadyThere.contains(post) else { return nil }
    alreadyThere.insert(post)
    return post
}

2
谢谢您的答复,但很遗憾,对我来说顺序很重要,因为我希望帖子按照时间顺序显示。 - Oscar Apeland
@OscarApeland:没问题,我添加了另一个部分(解决方案2),原始顺序得以保留。 - Luca Angeletti
创建Set时,Set会在内部检查是否包含(post)。然后你再次执行相同的操作。我不认为这种方法有任何优势。即使我的答案被down-vote了,符合Hashable并创建Set并不是必要的,最终效果也更低效。请检查我的答案... - user3441734
@user3441734:我没有给你的问题投反对票。我想你是在谈论我的“解决方案2”。我代码中的“alreadyThere.contains”是必须的,因为我需要知道我正在评估的“post”是否已经存在于我正在构建和返回的数组中。如果你想要唯一的值,就不能删除那部分。 - Luca Angeletti
1
我已经使用Swift编程近两年了,只有在您的解释下才理解了Hashable和Equatable :) 我以为这是一种超级函数,我不可能理解。谢谢 - J. Goce
显示剩余6条评论

31

你可以创建一个空数组“uniquePosts”,并循环遍历你的数组“Posts”来向“uniquePosts”添加元素,在每次添加时,您需要检查是否已经添加了该元素或者没有。方法“contains”可以帮助你。

func removeDuplicateElements(posts: [Post]) -> [Post] {
    var uniquePosts = [Post]()
    for post in posts {
        if !uniquePosts.contains(where: {$0.postId == post.postId }) {
            uniquePosts.append(post)
        }
    }
    return uniquePosts
}

3
仅提供代码的答案被认为是低质量的:请确保解释您的代码执行什么操作以及它如何解决问题。 - help-info.de

8

一种保留原始顺序的通用解决方案是:

extension Array {
    func unique(selector:(Element,Element)->Bool) -> Array<Element> {
        return reduce(Array<Element>()){
            if let last = $0.last {
                return selector(last,$1) ? $0 : $0 + [$1]
            } else {
                return [$1]
            }
        }
    }
}

let uniquePosts = posts.unique{$0.id == $1.id }

4

根据Danielvgftv的答案,我们可以利用KeyPaths来重写它,具体如下:

extension Sequence {
    func removingDuplicates<T: Hashable>(withSame keyPath: KeyPath<Element, T>) -> [Element] {
        var seen = Set<T>()
        return filter { element in
            guard seen.insert(element[keyPath: keyPath]).inserted else { return false }
            return true
        }
    }
}

使用方法:

struct Car {
    let id: UUID = UUID()
    let manufacturer: String
    // ... other vars
}

let cars: [Car] = [
    Car(manufacturer: "Toyota"),
    Car(manufacturer: "Tesla"),
    Car(manufacturer: "Toyota"),
]

print(cars.removingDuplicates(withSame: \.manufacturer)) // [Car(manufacturer: "Toyota"), Car(manufacturer: "Tesla")]

4

这个帖子中有一个很好的例子。

以下是一种数组扩展,根据给定的键返回唯一对象列表:

extension Array {
    func unique<T:Hashable>(map: ((Element) -> (T)))  -> [Element] {
        var set = Set<T>() //the unique list kept in a Set for fast retrieval
        var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
        for value in self {
            if !set.contains(map(value)) {
                set.insert(map(value))
                arrayOrdered.append(value)
            }
        }

        return arrayOrdered
    }
}

举个例子,你可以这样做:

let uniquePosts = posts.unique{$0.id ?? ""}

4

我的Swift 5解决方案:

添加扩展:

extension Array where Element: Hashable {

    func removingDuplicates<T: Hashable>(byKey key: (Element) -> T)  -> [Element] {
         var result = [Element]()
         var seen = Set<T>()
         for value in self {
             if seen.insert(key(value)).inserted {
                 result.append(value)
             }
         }
         return result
     }

}

客户端类,像Hashable这样的类非常重要:

struct Client:Hashable {

   let uid :String
   let notifications:Bool

   init(uid:String,dictionary:[String:Any]) {
       self.uid = uid
       self.notifications = dictionary["notificationsStatus"] as? Bool ?? false
   }

   static func == (lhs: Client, rhs: Client) -> Bool {
    return lhs.uid == rhs.uid
   }

}

用途:

arrayClients.removingDuplicates(byKey: { $0.uid })

祝喜爱Swift的朋友们有美好的一天 ♥️


3

我的“纯”Swift解决方案没有符合Set所需的Hashable标准。

struct Post {
    var id: Int
}

let posts = [Post(id: 1),Post(id: 2),Post(id: 1),Post(id: 3),Post(id: 4),Post(id: 2)]

// (1)
var res:[Post] = []
posts.forEach { (p) -> () in
    if !res.contains ({ $0.id == p.id }) {
        res.append(p)
    }
}
print(res) // [Post(id: 1), Post(id: 2), Post(id: 3), Post(id: 4)]

// (2)
let res2 = posts.reduce([]) { (var r, p) -> [Post] in
    if !r.contains ({ $0.id == p.id }) {
        r.append(p)
    }
    return r
}

print(res2) // [Post(id: 1), Post(id: 2), Post(id: 3), Post(id: 4)]

我希望将其封装成函数形式 (如 func unique(posts:[Post])->[Post] ),或许可以作为 Array 的扩展。


3

(更新至Swift 3)

正如我在评论中提到的,您可以使用修改过的Daniel Kroms解决方案,该解决方案位于我们之前标记为重复的帖子中。只需使您的Post对象可哈希(隐式等价于id属性),并实现以下经过修改的(使用Set而不是Dictionary;链接方法中的字典值无论如何都没有用过)版本的Daniel Kromsuniq函数:

func uniq<S: Sequence, E: Hashable>(_ source: S) -> [E] where E == S.Iterator.Element {
    var seen = Set<E>()
    return source.filter { seen.update(with: $0) == nil }
}

struct Post : Hashable {
    var id : Int
    var hashValue : Int { return self.id }
}

func == (lhs: Post, rhs: Post) -> Bool {
    return lhs.id == rhs.id
}

var posts : [Post] = [Post(id: 1), Post(id: 7), Post(id: 2), Post(id: 1), Post(id: 3), Post(id: 5), Post(id: 7), Post(id: 9)]
print(Posts)
/* [Post(id: 1), Post(id: 7), Post(id: 2), Post(id: 1), Post(id: 3), Post(id: 5), Post(id: 7), Post(id: 9)] */


var myUniquePosts = uniq(posts)
print(myUniquePosts)
/* [Post(id: 1), Post(id: 7), Post(id: 2), Post(id: 3), Post(id: 5), Post(id: 9)] */

这将删除重复项,同时保留原始数组的顺序。

作为Sequence扩展的辅助函数uniq

除了使用自由函数之外,我们还可以将uniq实现为受限制的Sequence扩展:

extension Sequence where Iterator.Element: Hashable {
    func uniq() -> [Iterator.Element] {
        var seen = Set<Iterator.Element>()
        return filter { seen.update(with: $0) == nil }
    }
}

struct Post : Hashable {
    var id : Int
    var hashValue : Int { return self.id }
}

func == (lhs: Post, rhs: Post) -> Bool {
    return lhs.id == rhs.id
}

var posts : [Post] = [Post(id: 1), Post(id: 7), Post(id: 2), Post(id: 1), Post(id: 3), Post(id: 5), Post(id: 7), Post(id: 9)]
print(posts)
/* [Post(id: 1), Post(id: 7), Post(id: 2), Post(id: 1), Post(id: 3), Post(id: 5), Post(id: 7), Post(id: 9)] */


var myUniquePosts = posts.uniq()
print(myUniquePosts)
/* [Post(id: 1), Post(id: 7), Post(id: 2), Post(id: 3), Post(id: 5), Post(id: 9)] */

3

这也适用于多维数组:

for (index, element) in arr.enumerated().reversed() {
    if arr.filter({ $0 == element}).count > 1 {
        arr.remove(at: index)
    }
}

3
保留顺序,而不添加额外状态:
func removeDuplicates<T: Equatable>(accumulator: [T], element: T) -> [T] {
    return accumulator.contains(element) ?
        accumulator :
        accumulator + [element]
}

posts.reduce([], removeDuplicates)

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