I am trying to write a simple Array extension that provides a 'distinct' method. Here is what I have so far:
extension Array {
func distinct() -> T[] {
var rtn = T[]()
for x in self {
var containsItem = contains(rtn, x)
if !containsItem {
rtn.append(x)
}
}
return rtn
}
}
The problem is that the 'contains' statement fails as follows:
Could not find an overload for 'contains' that accepts the supplied arguments
I am pretty sure the type constraints are correct. Any ideas?
The elements in an array
don't have to be Equatable
, i.e. they don't have be comparable with ==
.
That means you can't write that function for all possible Arrays. And Swift doesn't allow you to extend just a subset of Arrays.
That means you should write it as a separate function (and that's probably why contains
isn't a method, either).
let array = ["a", "b", "c", "a"]
func distinct<T: Equatable>(array: [T]) -> [T] {
var rtn = [T]()
for x in array {
var containsItem = contains(rtn, x)
if !containsItem {
rtn.append(x)
}
}
return rtn
}
distinct(array) // ["a", "b", "c"]
Swift 2 supports restricting extensions to a subset of protocol implementations, so the following is now allowed:
let array = ["a", "b", "c", "a"]
extension SequenceType where Generator.Element: Comparable {
func distinct() -> [Generator.Element] {
var rtn: [Generator.Element] = []
for x in self {
if !rtn.contains(x) {
rtn.append(x)
}
}
return rtn
}
}
array.distinct() // ["a", "b", "c"]
Note how apple added SequenceType.contains
using the same syntax.
Finally found out how to do it:
extension Array {
func contains<T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
func distinct<T : Equatable>(_: T) -> T[] {
var rtn = T[]()
for x in self {
if !rtn.contains(x as T) {
rtn += x as T
}
}
return rtn
}
}
And usage/testing:
let a = [ 0, 1, 2, 3, 4, 5, 6, 1, 2, 3 ]
a.contains(0)
a.contains(99)
a.distinct(0)
Unfortunately, I can't figure out a way to do it without having to specify an argument which is subsequently ignored. The only reason it's there is to invoke the correct form of distinct. The major advantage of this approach for distinct
seems to be that it's not dumping a common term like distinct
into the global namespace. For the contains case it does seem more natural.
Another solution is to use the find(Array:[T], obj:T) function. It will return an optional Int, so what you could do is
if let foundResult = find(arr, obj) as Int
{
//obj is contained in arr
} else
{
//obj is not contained in arr.
}
As of Swift 2, this can be achieved with a protocol extension method,
e.g. on all types conforming to SequenceType
where the sequence
elements conform to Equatable
:
extension SequenceType where Generator.Element : Equatable {
func distinct() -> [Generator.Element] {
var rtn : [Generator.Element] = []
for elem in self {
if !rtn.contains(elem) {
rtn.append(elem)
}
}
return rtn
}
}
Example:
let items = [1, 2, 3, 2, 3, 4]
let unique = items.distinct()
print(unique) // [1, 2, 3, 4]
If the elements are further restricted to be Hashable
then you
can take advantage of the Set
type:
extension SequenceType where Generator.Element : Hashable {
func distinct() -> [Generator.Element] {
return Array(Set(self))
}
}
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