Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to use contains within a Swift Array extension

Tags:

swift

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?

like image 679
ColinE Avatar asked Jun 06 '14 21:06

ColinE


4 Answers

Swift 1.x

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"]

Update for Swift 2/Xcode 7 (Beta)

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.

like image 93
nschum Avatar answered Nov 12 '22 12:11

nschum


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.

like image 41
David Berry Avatar answered Nov 12 '22 12:11

David Berry


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.
}
like image 34
Ethan Avatar answered Nov 12 '22 11:11

Ethan


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))
    }
}
like image 2
Martin R Avatar answered Nov 12 '22 11:11

Martin R