The question here involves removing duplicate objects from an array:
Removing duplicate elements from an array in Swift
I instead need to remove objects that are not themselves duplicates, but have specific duplicate properties such as id
.
I have an array containing my Post
objects. Every Post
has an id
property.
Is there a more effective way to find duplicate Post ID's in my array than
for post1 in posts {
for post2 in posts {
if post1.id == post2.id {
posts.removeObject(post2)
}
}
}
By converting the array to a Set , all duplicate values are automatically dropped from the array. We can then convert the set back to an array to get an array with all duplicate values removed. Because sets don't enforce any ordering, you might lose the original order of your array.
We can remove duplicate element in an array by 2 ways: using temporary array or using separate index. To remove the duplicate element from array, the array must be in sorted order. If array is not sorted, you can sort it by calling Arrays. sort(arr) method.
A set is a collection of unordered unique values. Therefore, it cannot contain a duplicate element.
I am going to suggest 2 solutions.
Both approaches will need Post
to be Hashable
and Equatable
Here I am assuming your Post
struct (or class) has an id
property of type String
.
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 }
To remove duplicated you can use a Set
let uniquePosts = Array(Set(posts))
var alreadyThere = Set<Post>() let uniquePosts = posts.flatMap { (post) -> Post? in guard !alreadyThere.contains(post) else { return nil } alreadyThere.insert(post) return post }
You can create an empty array "uniquePosts", and loop through your array "Posts" to append elements to "uniquePosts" and every time you append you have to check if you already append the element or you didn't. The method "contains" can help you.
func removeDuplicateElements(post: [Post]) -> [Post] { var uniquePosts = [Post]() for post in posts { if !uniquePosts.contains(where: {$0.postId == post.postId }) { uniquePosts.append(post) } } return uniquePosts }
A generic solution which preserves the original order is:
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 }
my 'pure' Swift solutions without Post conformance to Hashable (required by Set )
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)]
I prefer (1) encapsulated into function (aka func unique(posts:[Post])->[Post]
), maybe an extension Array ....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With