Search code examples
iosarraysswiftgenericsequatable

Testing array containment for values defined as Any, but guaranteed to be of the same type.


Building on this question of mine (and the accepted answer), I want to test containment of a value in an array.

The value is stored in a variable defined as of type Any, and the array is defined as [Any].

The actual types of the value stored in the variable and the elements in the array are decided at runtime, but are guaranteed to satisfy the following conditions:

  1. Both types (variable and array elements) coincide, and
  2. They are either one of String, Int or Bool.

So far, I got this code working:

var isContained = false

if let intValue = anyValue as? Int {
    isContained = arrayOfAny.contains({element in return ((element as? Int) == intValue)})
}
else if let stringValue = anyValue as? String {
    isContained = arrayOfAny.contains({element in return ((element as? String) == stringValue)})
}
else if let boolValue = anyValue as? Bool {
    isContained = arrayOfAny.contains({element in return ((element as? Bool) == boolValue)})
}

However, there is a lot of logic duplication and I wish I could make it smarter, perhaps something like this:

isContained = arrayOfAny.contains({element in 
    return ((element as? Equatable) == (anyValue as? Equatable))
})

...but the restrictions on the use of the protocol Equatable stand in the way. Any advice?


Solution

  • I see now what you are trying to do. Here is an example of how you can make it working

    let arrayOfAny:[AnyObject] = [1,7,9,true, "string"]
    func getAny(value:AnyObject) -> [AnyObject]{
        return self.arrayOfAny.filter ({$0 === value})
    }
    

    The above function will return an array of matches which ideally should be a single result or empty array.

    Example:

    self.getAny(1) // [1]
    self.getAny(0) // []
    

    You can also modify it to simply return a Bool

    func getAny(value:AnyObject) -> Bool{
      return self.arrayOfAny.filter ({$0 === value}).count > 0
    }
    

    Example:

    self.getAny(1) // true
    self.getAny(0) // false
    

    Edit:

    As Martin R mentioned this would not always work. Unfortunately I didn't fully tested it before I post this answer. After playing with this for a while I came up with very similar approach to the one NicolasMiari has:

    let arrayOfAny:[AnyObject] = [1,Int.max,9,true, "string"]
    func getAny(anyValue:AnyObject) -> [AnyObject]{
        return self.arrayOfAny.filter ({
             var exist:Bool
             switch anyValue.self{
                case is String: exist = $0 as? String == anyValue as? String
                    break
                case is Int: exist = $0 as? Int == anyValue as? Int
                    break
                case is Bool: exist = $0 as? Bool == anyValue as? Bool
                    break
                default: exist = false
                }
                return exist
         })
    

    }

    The downside of this approach is that the int 1 and true will be returned when calling self.getAny(1) the result will be [1,1] as 1 and true can be successfully casted to both Int and Bool and not as probably intended to just return [1]. In other words if you just have true in your array without having Int 1 you will still get positive result as if it was existing in your array. Same works the other way around as well.