Swift contains extension for Array

13.6k views Asked by At

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

5

There are 5 answers

4
atermenji On BEST ANSWER

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)
0
nschum On

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 {
        ...
    }
}
0
Christopher Pickslay On

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.

0
Dareon On

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
    }
}
1
Kevin Wood On

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
    }
}