I've not really had time to work on any of this for a while (mostly down to personal time constraints), so in an effort to not let this die completely here is the latest implementation version that me and Zach (@anon31136981 he deleted his account?) put together a couple of months back. If anyone has the time to push this forwards then let me know and I will be happy to help.
extension RangeReplaceableCollection where Self: MutableCollection {
/// Removes any duplicate elements where the equality is based on
/// a value other than the element itself.
///
/// Use this method to remove duplicate elements where their equality
/// is based on the value returned by the closure. The order of the remaining
/// elements is preserved.
///
/// This example removes objects based on the equality of one of the
/// properties of the object.
///
/// struct Animal {
/// let kind: String
/// let name: String
/// }
///
/// let animals = [
/// Animal(kind: "Dog", name: "Fido"),
/// Animal(kind: "Cat", name: "Tibbles"),
/// Animal(kind: "Lion", name: "Simba"),
/// Animal(kind: "Dog", name: "Spot"),
/// ]
///
/// animals.removeDuplicates(by: { $0.kind })
///
/// // animals == [
/// // Animal(kind: "Dog", name: "Fido"),
/// // Animal(kind: "Cat", name: "Tibbles"),
/// // Animal(kind: "Lion", name: "Simba")
/// // ]
///
/// - Parameter distinctValue: A closure that takes an element of the
/// sequence as its argument and returns a value that conforms to
/// `Hashable` which will in turn be used to compare if an object
/// is a duplicate.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
mutating func removeDuplicates<T: Hashable>(by distinctValue: (Element) -> T) {
var seen: Set<T> = []
removeAll(where: { element in
let value = distinctValue(element)
return !seen.insert(value).inserted
})
}
/// Removes any duplicate elements where the equality is based on
/// a value other than the element itself.
///
/// Use this method to remove duplicate elements where their equality
/// is based on the value returned by the closure. The order of the remaining
/// elements is preserved.
///
/// This example removes objects based on the equality of one of the
/// properties of the object.
///
/// struct Animal {
/// let kind: String
/// let name: String
/// }
///
/// let animals = [
/// Animal(kind: "Dog", name: "Fido"),
/// Animal(kind: "Cat", name: "Tibbles"),
/// Animal(kind: "Lion", name: "Simba"),
/// Animal(kind: "Dog", name: "Spot"),
/// ]
///
/// animals.removeDuplicates(by: { $0.kind })
///
/// // animals == [
/// // Animal(kind: "Dog", name: "Fido"),
/// // Animal(kind: "Cat", name: "Tibbles"),
/// // Animal(kind: "Lion", name: "Simba")
/// // ]
///
/// - Parameter distinctValue: A closure that takes an element of the
/// sequence as its argument and returns a value that conforms to
/// `Equatable` which will in turn be used to compare if an object
/// is a duplicate.
///
/// - Complexity: O(*n2*), where *n* is the length of the collection.
mutating func removeDuplicates<T: Equatable>(by distinctValue: (Element) -> T) {
var result = ContiguousArray<Element>()
var distinctElements = ContiguousArray<T>()
for element in self {
let distinctElement = distinctValue(element)
guard !distinctElements.contains(distinctElement) else {
continue
}
distinctElements.append(distinctElement)
result.append(element)
}
self = Self(result)
}
}
extension RangeReplaceableCollection {
/// Removes any duplicate elements where the equality is based on
/// a value other than the element itself.
///
/// Use this method to remove duplicate elements where their equality
/// is based on the value returned by the closure. The order of the remaining
/// elements is preserved.
/// This example removes objects based on the equality of one of the
/// properties of the object.
///
/// struct Animal {
/// let kind: String
/// let name: String
/// }
///
/// let animals = [
/// Animal(kind: "Dog", name: "Fido"),
/// Animal(kind: "Cat", name: "Tibbles"),
/// Animal(kind: "Lion", name: "Simba"),
/// Animal(kind: "Dog", name: "Spot"),
/// ]
///
/// let uniqueAnimals = animals.removingDuplicates(by: { $0.kind })
///
/// // uniqueAnimals == [
/// // Animal(kind: "Dog", name: "Fido"),
/// // Animal(kind: "Cat", name: "Tibbles"),
/// // Animal(kind: "Lion", name: "Simba")
/// // ]
///
/// - Parameter distinctValue: A closure that takes an element of the
/// sequence as its argument and returns a value that conforms to
/// `Hashable` which will in turn be used to compare if an object
/// is a duplicate.
///
/// - Returns: A new collection with all duplicates removed.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
func removingDuplicates<T: Hashable>(by distinctValue: (Element) -> T) -> Self {
var copy = self
var seen: Set<T> = []
copy.removeAll(where: { element in
let value = distinctValue(element)
return !seen.insert(value).inserted
})
return copy
}
/// Returns a new collection of the same type with all duplicate elements
/// removed based on a value other than the element itself.
///
/// Use this method to create a new collection with all duplicate elements
/// removed based on the value returned by the closure. The order of the
/// remaining elements is preserved.
///
/// This example removes objects based on the equality of one of the
/// properties of the object.
///
/// struct Animal {
/// let kind: String
/// let name: String
/// }
///
/// let animals = [
/// Animal(kind: "Dog", name: "Fido"),
/// Animal(kind: "Cat", name: "Tibbles"),
/// Animal(kind: "Lion", name: "Simba"),
/// Animal(kind: "Dog", name: "Spot"),
/// ]
///
/// let uniqueAnimals = animals.removingDuplicates(by: { $0.kind })
///
/// // uniqueAnimals == [
/// // Animal(kind: "Dog", name: "Fido"),
/// // Animal(kind: "Cat", name: "Tibbles"),
/// // Animal(kind: "Lion", name: "Simba")
/// // ]
///
/// - Parameter distinctValue: A closure that takes an element of the
/// sequence as its argument and returns a value that conforms to
/// `Equatable` which will in turn be used to compare if an object
/// is a duplicate.
///
/// - Returns: A new collection with all duplicates removed.
///
/// - Complexity: O(*n2*), where *n* is the length of the collection.
func removingDuplicates<T: Equatable>(by distinctValue: (Element) -> T) -> Self {
var result = ContiguousArray<Element>()
var distinctElements = ContiguousArray<T>()
for element in self {
let distinctElement = distinctValue(element)
guard !distinctElements.contains(distinctElement) else {
continue
}
distinctElements.append(distinctElement)
result.append(element)
}
return Self(result)
}
}
extension RangeReplaceableCollection where Self: MutableCollection, Element: Hashable {
/// Removes any duplicate elements.
///
/// Use this method to remove duplicate elements. The order of the
/// remaining elements is preserved.
/// This example removes duplicate numbers from an array.
///
/// let numbers = [1, 2, 3, 4, 5, 4, 1, 2, 6]
/// numbers.removeDuplicates()
/// // numbers == [1, 2, 3, 4, 5, 6]
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
mutating func removeDuplicates() {
removeDuplicates { $0 }
}
}
extension RangeReplaceableCollection where Element: Hashable {
/// Returns a new collection of the same type with all duplicate elements
/// removed.
///
/// Use this method to create a new collection with all duplicate elements
/// removed. The order of the remaining elements is preserved.
///
/// This example takes an array of numbers and creates a new array from it
/// with all duplicates removed.
///
/// let numbers = [1, 2, 3, 4, 5, 4, 1, 2, 6]
/// let uniqueNumbers = numbers.removingDuplicates()
/// // uniqueNumbers == [1, 2, 3, 4, 5, 6]
///
/// - Returns: A new collection with all duplicates removed.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
func removingDuplicates() -> Self {
return self.removingDuplicates { $0 }
}
}
extension RangeReplaceableCollection where Self: MutableCollection, Element: Equatable {
/// Removes any duplicate elements.
///
/// Use this method to remove duplicate elements. The order of the
/// remaining elements is preserved.
/// This example removes duplicate numbers from an array.
///
/// let numbers = [1, 2, 3, 4, 5, 4, 1, 2, 6]
/// numbers.removeDuplicates()
/// // numbers == [1, 2, 3, 4, 5, 6]
///
/// - Complexity: O(*n2*), where *n* is the length of the collection.
mutating func removeDuplicates() {
removeDuplicates { $0 }
}
}
extension RangeReplaceableCollection where Element: Equatable {
/// Returns a new collection of the same type with all duplicate elements
/// removed.
///
/// Use this method to create a new collection with all duplicate elements
/// removed. The order of the remaining elements is preserved.
///
/// This example takes an array of numbers and creates a new array from it
/// with all duplicates removed.
///
/// let numbers = [1, 2, 3, 4, 5, 4, 1, 2, 6]
/// let uniqueNumbers = numbers.removingDuplicates()
/// // uniqueNumbers == [1, 2, 3, 4, 5, 6]
///
/// - Returns: A new collection with all duplicates removed.
///
/// - Note: For reduced complexity, ensure `Element` conforms to `Hashable`.
///
/// - Complexity: O(*n2*), where *n* is the length of the collection.
func removingDuplicates() -> Self {
return self.removingDuplicates { $0 }
}
}
There are specialised versions for using both Equatable
and Hashable
which include a bunch of feedback from this thread plus a bunch more research from our side on how it would best fit into the standard library.