Today I needed to remove redundant elements from a Swift array based on the value of a specific property. After some foraging, I came across this post where Antoine extends Sequence
to handle the more generic case of deduplicating elements that conform to Hashable
.
public extension Sequence where Iterator.Element: Hashable {
func deduplicated() -> [Iterator.Element] {
var seen = Set<Iterator.Element>()
return filter { seen.insert($0).inserted }
}
}
For my use case, I needed a bit more control over how the elements get deduplicated. Since I am only interested in comparing a single property, I resorted to key paths since they lend themselves quite well to this usage.
public extension Sequence {
func deduplicated<T>(
by keyPath: KeyPath<Iterator.Element, T>
) -> [Iterator.Element] where T: Hashable {
var seen = [T: Iterator.Element]()
return filter { element in
let key = element[keyPath: keyPath]
if seen[key] == nil {
seen[key] = element
return true
} else {
return false
}
}
}
}
And here is an excerpt from the code where I am using this extension.
struct Game: Codable, Hashable {
var name: String
var releaseDates: [Date]
/* ... */
}
/*
The API reponse returns a list of non-unique games separated.
by release date, but for this view we only want a single instance.
of each game.
*/
let games: [Game] = /* ... */
let deduplicatedGames = games.deduplicated(by: \.name)
For the name, I chose to not go with unique
because it describes the elements of the sequence rather than the sequence itself. I instead took some hints from Rust’s similar-but-not-quite Vec::dedup
.