4

I'm trying to add an extension method in Array like so:

extension Array {
    func contains(obj: T) -> Bool {
        let filtered = self.filter {$0 == obj}
        return filtered.count > 0
    }
}

But self.filter {$0 == obj} don't work. Compiler error:

could not find an overload for '==' that accepts the supplied arguments

mopsled
  • 8,445
  • 1
  • 38
  • 40
Gralex
  • 4,285
  • 7
  • 26
  • 47

5 Answers5

32

you don't actually need to write an extension, you can use the global func contains from the Swift library:

contains([1,2,3], 1)
atermenji
  • 1,338
  • 8
  • 19
  • This surely is the way to go :) – sachadso Sep 16 '14 at 16:09
  • This should be the correct answer, easy and already existing method. – Antoine Jan 13 '15 at 13:27
  • This is definitely the way to go, but personally I still think it's worth putting in an extension so you can call someArray.contains(someValue) – ToddH Jan 28 '15 at 18:23
  • The problem with the global function is that neither the sequence nor the element can be optionals. If either/both are, you need to check for nil and force-unwrap them. A contains function on Array that handles optional arguments would be much cleaner. – Christopher Pickslay Jan 29 '15 at 23:40
13

Swift 1.x

As I mentioned in the comments, there is a contains function. But to answer the question of how to write an extension and what the compiler error means:

The elements in the array can't necessarily be compared with ==. You need to make sure the parameter is Equatable and you need to make sure the array element is of the same type.

extension Array {
    func contains<T : Equatable>(obj: T) -> Bool {
        let filtered = self.filter {$0 as? T == obj}
        return filtered.count > 0
    }
}

Swift 2/Xcode 7 (Beta)

Swift 2 includes SequenceType.contains, which is exactly what you were trying to create.

This is made possible by a Swift syntax that allows restricting methods to certain (e.g. Equatable) type arguments. It looks like this:

extension SequenceType where Generator.Element: Equatable {
    func contains(element: Self.Generator.Element) -> Bool {
        ...
    }
}
nschum
  • 15,322
  • 5
  • 58
  • 56
1

I found that the built-in contains doesn't work with reference types. I needed this and solved it with the code below. I'm pasting it here because somebody else might be confused about contains() like I was.

extension Array {
    func containsReference(obj: AnyObject) -> Bool {
        for ownedItem in self {
            if let ownedObject: AnyObject = ownedItem as? AnyObject {
                if (ownedObject === obj) {
                    return true
                }
            }
        }

        return false
    }
} 
Kevin Wood
  • 143
  • 10
1

This works with Swift 2.1 for reference types pretty good.

extension SequenceType where Generator.Element: AnyObject {
    func contains(obj: Self.Generator.Element?) -> Bool {
        if obj != nil {
            for item in self {
                if item === obj {
                    return true
                }
            }
        }
        return false
    }
}

For value types you can add this:

extension SequenceType where Generator.Element: Equatable {
    func contains(val: Self.Generator.Element?) -> Bool {
        if val != nil {
            for item in self {
                if item == val {
                    return true
                }
            }
        }
        return false
    }
}
Dareon
  • 374
  • 3
  • 13
0

Not perfect, but this version built on nschum's answer supports optional arguments (though not arrays with optional types) as well:

extension Array {

    private func typeIsOptional() -> Bool {
        return reflect(self[0]).disposition == .Optional
    }

    func contains<U : Equatable>(obj: U) -> Bool {
        if isEmpty {
            return false
        }

        if (typeIsOptional()) {
            NSException(name:"Not supported", reason: "Optional Array types not supported", userInfo: nil).raise()
        }

        // cast type of array to type of argument to make it equatable
        for item in self.map({ $0 as? U }) {
            if item == obj {
                return true
            }
        }

        return false
    }

    // without this version, contains("foo" as String?) won't compile
    func contains<U : Equatable>(obj: U?) -> Bool {
        if isEmpty {
            return false
        }

        if (typeIsOptional()) {
            NSException(name:"Not supported", reason: "Optional Array types not supported", userInfo: nil).raise()
        }

        return obj != nil && contains(obj!)
    }

}

If you have an array of optionals, you can get a copy of it with non-optionals (nil arguments removed) with this global function thanks to jtbandes:

func unwrapOptionals<T>(a: [T?]) -> [T] {
    return a.filter { $0 != nil }.map { $0! }
}

Usage:

 1>     func unwrapOptionals<T>(a: [T?]) -> [T] {
  2.         return a.filter { $0 != nil }.map { $0! }
  3.     }
  4>
  5> let foo = ["foo" as String?]
foo: [String?] = 1 value {
  [0] = "foo"
}
  6> let bar = unwrapOptionals(foo)
bar: [String] = 1 value {
  [0] = "foo"
}

For good measure, add one that just returns the array if its type is not optional. This way you avoid runtime errors if you call unwrapOptionals() on a non-optional array:

func unwrapOptionals<T>(a: [T]) -> [T] {
    return a
}

Note you might think you could just call unwrapOptionals inside func contains<U : Equatable>(obj: U?). However, that doesn't work, because the Element type in the Array extension is just a type--it doesn't "know" it's an optional type. So if you call unwrapOptionals, the second version will be invoked, and you'll just get the array full of optionals back.

Community
  • 1
  • 1
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92