Removing duplicates to get unique values out of an array can be a common task to perform. Languages like Ruby have built-in methods like uniq
but in Swift, we have to create such methods on our own. The standard library does not provide an easy method to do this.
There’s a lot of ways to achieve the same result and each of them has their own pros and cons. Let’s go over them and see which methods work best for your use-case.
Using a Set to remove duplicates by default
Before we start diving into extensions to remove the duplicates from an array, it’s good to look into Sets in Swift. A Set,
by default, contains unique values and can achieve the results you’re aiming for.
Go for a Set
if:
- Order is not important
- You like to have unique elements by default
let arrayOfElements: [Int] = [1, 1, 1, 2, 2, 2, 3, 3, 3]
let setOfElements: Set<Int> = [1, 1, 1, 2, 2, 2, 3, 3, 3]
print(arrayOfElements) // prints: [1, 1, 1, 2, 2, 2, 3, 3, 3]
print(setOfElements) // prints: [2, 3, 1]
This is more of a code design decision where you decide to work with a Set
versus an array. Sets have the benefit of being more performant as well, which makes them a good candidate to solve the uniqueness problem. You can read more about the differences between a Set
and an Array
in my blog post Array vs Set: Fundamentals in Swift explained.
Without going too much into detail on the differences, it’s good to know that a Set is not maintaining the order. If keeping the order is essential in your case, you may not want to opt for a Set
. You could go for an NSOrderedSet
, but this class does not give you type completion, and you’ll have to work with Any
instances.
Removing duplicate elements from an array with an extension
When the order of the elements is essential, we can continue working with an array and retrieve the unique values by utilizing a custom extension.
Go for an Array
if:
- Order is important
- You can’t easily switch over to a
Set
We need to create an extension to filter out duplicate elements. It’s essential to keep performance in mind when designing that extension, as we could quickly end up with a quadratic time complexity of (N²)
. In short, this means that the more elements you have, the more the performance will decrease.
The following piece of code relies on the Hashable
protocol to match elements and has a linear time complexity of o(N)
. This means that 3 elements require 3 iterations, 10 elements require 10 iterations, and so on.
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var seen: Set<Iterator.Element> = []
return filter { seen.insert($0).inserted }
}
}
print(array.unique()) // prints: [1, 2, 3]
Let’s break down this unique()
method:
- We create a
Set
to track seen objects - A filter is used to iterate over all objects
- the
insert(_:)
method returns a tuple including an inserted boolean which is set totrue
if the object was inserted andfalse
if not - The inserted boolean value is used to filter out duplicates from our array
The final result is an array with the same order but without duplicate elements. The only downside is that your elements need to conform to the Hashable
protocol, but this shouldn’t be that big of a problem. Many types in the standard library already conform to the protocol, like Strings, integers, and Boolean values. The Hashable
protocol is used to determine whether an element is equal to an existing object and, therefore, is required to get the unique values. However, there is a way to filter elements without adhering to this protocol.
Removing duplicate elements by any hashable value
There are scenarios where you want to remove duplicate elements based on an object’s value. Often times, this value already conforms to Hashable
, removing the need to make your custom type conform to the protocol.
The extension for this looks as follows:
extension Sequence {
func unique<T: Hashable>(by keyForValue: (Iterator.Element) throws -> T) rethrows -> [Iterator.Element] {
var seen: Set<T> = []
return try filter { try seen.insert(keyForValue($0)).inserted }
}
}
Kudos to Mathijs Kadijk for sharing this!
To better explain this usecase, I’d like to introduce the following sample data:
struct Employee: CustomDebugStringConvertible {
let name: String
let title: String
var debugDescription: String {
"\(name) - \(title)"
}
}
let employees: [Employee] = [
Employee(name: "Antoine", title: "Swift Developer"), // duplicate title
Employee(name: "Dorian", title: "Swift Developer"), // duplicate title
Employee(name: "Ralph", title: "Head of Sales"),
Employee(name: "Niek", title: "Frontend Developer"),
Employee(name: "Kristy", title: "Head of Marketing")
]
If we were to use our original unique
method, the result would be:
let regularUnique = employees.unique()
print(regularUnique)
/// Prints:
/// [
/// Antoine - Swift Developer,
/// Dorian - Swift Developer,
/// Ralph - Head of Sales,
/// Niek - Frontend Developer,
/// Kristy - Head of Marketing
/// ]
All values are unique if you look at all members’ properties together. However, if we wanted to filter on unique job titles, we could now benefit from the new extension:
let uniqueByTitle = employees.unique(by: { $0.title })
print(uniqueByTitle)
/// Prints:
/// [
/// Antoine - Swift Developer,
/// Ralph - Head of Sales,
/// Niek - Frontend Developer,
/// Kristy - Head of Marketing
/// ]
This could be useful in cases where you only want to display a unique set of data based on a specific property.
Making use of the Swift Algorithms Package
While this article aims to deliver a simple extension to remove duplicates, it could be that you’re already making use of the Swift Algorithms Package. This open-sourced package by Apple provides a uniqued()
method that can check the guide for here.
It does require you to add the whole package, which might not always be what you want. However, it’s a well-maintained package developed by Apple that comes with many more useful functionalities.
Conclusion
When you like to get the unique elements of a collection you can go for a Set
or an extension on an Array
.
- If order is not important, go for a
Set
- Keep the order and fetch the unique elements with an extension on an array
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!