Swift Generics with Any - ios

I have been playing with generics and have this function which takes an array and checks if it contains a given value...
func finder<T:Equatable>(array:[T], valueToFind:T) -> Int? {
for(index, value) in enumerate(array) {
if(value == valueToFind) {
return index
}
}
return nil
}
If I use it with this test array, it works perfectly and returns the right index.
var testArray = ["Dog", "Cat", "Mouse", "Tiger"]
finder(testArray, "Cat")
Similarly it works for this test array of numbers:
var testArray = [1, 2]
finder(testArray, 1)
However, When I have a test array like this with strings and numbered, (I assume inferred to be type Any, then the function doesn't work and I get a compiler error:
var testArray = ["Dog", "Cat", "Mouse", "Tiger", 2]
finder(testArray, "Cat")
Cannot convert the expression's type 'Int?' to type 'StringLiteralConvertible'
Why is this?

Any and AnyObject does not conform Equatable so they can not passed.As you have define generic as [T] and it confirms to <T:Equatable> where T repersents specific type so AnyObject and Any is not specific type.
As below code will not show error because T is not conforming to Equatable and it is valid to pass [Any]
func finder<T>(array:[T], valueToFind:T) -> Int? {
for(index, value) in enumerate(array) {
//code
//You can not compare here value == valueToFind .T should be Equatable for comparison
}
return nil
}
var testArray = ["Dog", "Cat", "Mouse", "Tiger",2] //It will refer as Any
finder(testArray, 2) //not have any errors and it is asuming `testArray` as `Any`
You can also see further analysis as T require to be specific type
So you need to give specific type for generics which repersent T.As you are passing T valueToFind as String so array also needs to be type of String as it shows in error of below two statements.If you pass Int than it will show 'IntegerLiteralConvertible' error
finder<T:Equatable>(array:[T], valueToFind:T) //Here both `T` should be refer to same type either String or Int
var testArray = ["Dog", "Cat", "Mouse", "Tiger",2]
finder(testArray, "Cat") //Shows error Cannot convert the expression's type 'Int?' to type 'StringLiteralConvertible'
finder(testArray, 2) //Shows error Cannot convert the expression's type 'Int?' to type 'IntegerLiteralConvertible'
or remove the other parameter now it will show Equatable error as it is converting testArray to Any which does not conform Equatable but in this it shows specific error
func finder<T:Equatable>(array:[T]) -> Int? {
return nil
}
var testArray = ["Dog", "Cat", "Mouse", "Tiger",2]
finder(testArray) //Shows error Cannot convert the expression's type 'Int?' to type 'Equatable'
So in this code it is assuming testArray as Any

Related

Unable to compare array value in swift ios

I'm a fresher in iOS
I'm unable to compare in an if statement.
It looks like:
for i in 0.. <array.count
{
if(array[i] == array[i+1])
{
let removedVal = array.remove(i+1)
}
}
The error shows on the if condition:
Binary operator '==' cannot be applied to two 'Any' operands
I googled it, but I am unable to understand what should I do in my case.
=======================================================================
Atlast able to find a solution.
And it worked for me
if ( ((tempArrayForTeamName[i]) as AnyObject).isEqual(tempArrayForTeamName[i+1] as AnyObject) )
need to compare array index position as Any object
And use .isEqual replace of ==
You have to Filter your Array
var newarray = [Int]()
let dictionary = ["A":0,"B":1,"C":1,"D":1,"E":1,"F":1,"G":1,"H":1,"J":0]
let newDictionary = dictionary.reduce([:]) { result, element -> [String: Int] in
guard element.value != 1 else {
return result
}
var newResult = result
newResult[element.key] = element.value
newarray.append(newResult[element.key]!)
return newResult
}
In Swift : Array is a Generic Structure, NSMutableArray is an Objective-C class[will work in Swift].
A NSMutableArray created is of type Any; an array that can contain heterogenous object(could be String, Int or Bool).
An Array is arbitrarily specilized to contain Any (using as [Any])
eg:
var array:Array = ["ABC", 123, true] as [Any]
var nsMutableArray : NSMutableArray = ["ABC", 123, true]
Generic Parameterization:
Even if there is an option to give generic parameterization(Datatype) to your NSMutableArray in Objective C[remember NSMutableArray in an Objective C class],this generic parameterization in unfortunately ignored/not allowed in Swift.
How to specify the datatype:
In Swift one cannot specify the datatype of a NSMutableArray.It would give a compilation error: Cannot specialize non-generic type NSMutableArray.
But one can always specify the datatype of Array(Swift structure) as say: Array<String>.
eg: var array:Array<String> = ["Tom", "Jerry", "Spike"]
Your code has another problem, consider your array has 3 items then i=2, and you are trying to access index 3 (i+1). And program will crash.
Crash point (array[i] == array[i+1])
Please declare specific types array for example
let myArray:[String] = ["a", "b", "c", "d"]

Putting two generic Arrays into one Swift Dictionary with Generics

I'm trying to put two different generic types into a collection. In this example there are two arrays, one containing Ints and the other Strings.
let intArray = Array<Int>()
let stringArray = Array<String>()
let dict = [1:intArray, "one": stringArray]
The error reads Type of expression is ambiguous without more context.
So I tried specifying the Dictionary's type
let dict: [Hashable: Any] = [1:intArray, "one": stringArray]
This leads to two errors.
Using 'Hashable' as a concrete type conforming to protocol 'Hashable' is not supported.
Protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
Adding import Foundation and using NSDictionary as the type works fine.
let dict: NSDictionary = [1:intArray, "one": stringArray]
But this should be possible in pure Swift too without using Foundation. What is the type the Dictionary has to have?
edit: This apparently has more to do with the type of the keys. They have to be of the same type, not just conform to Hashable.
let dict: [Int:Any] = [1:intArray, 2: stringArray]
This works. But is it possible to make the type of the value more precise? [Int:Array<Any>] does not work.
Elaborating on the answer from #RobNapier, here is a similar approach that uses enum for both, keys and values of the dictionary:
enum Key: Equatable, Hashable {
case IntKey(Int)
case StringKey(String)
var hashValue: Int {
switch self {
case .IntKey(let value) : return 0.hashValue ^ value.hashValue
case .StringKey(let value) : return 1.hashValue ^ value.hashValue
}
}
init(_ value: Int) { self = .IntKey(value) }
init(_ value: String) { self = .StringKey(value) }
}
func == (lhs: Key, rhs: Key) -> Bool {
switch (lhs, rhs) {
case (.IntKey(let lhsValue), .IntKey(let rhsValue)) : return lhsValue == rhsValue
case (.StringKey(let lhsValue), .StringKey(let rhsValue)) : return lhsValue == rhsValue
default: return false
}
}
enum Value {
case IntValue(Int)
case StringValue(String)
init(_ value: Int) { self = .IntValue(value) }
init(_ value: String) { self = .StringValue(value) }
}
var dict = [Key: Value]()
dict[Key(1)] = Value("One")
dict[Key(2)] = Value(2)
dict[Key("Three")] = Value("Three")
dict[Key("Four")] = Value(4)
What is the type the Dictionary has to have?
You may try:
let dict: [NSObject: Any] = [1: intArray, "one": stringArray]
The statement let dict: [Hashable: Any] = ... does not compile, because the type of the key of a Dictionary must be a concrete type conforming to Hashable, e.g. Int, String, etc. Hashable is not a concrete type.
The above suggestion works, because 1. NSObject is a concrete type (where you can instantiate objects from), 2. NSObject is a Hashable, and 3. because instances of subclasses of NSObjects will work here as well, and 4. the compiler can initialise NSObject subclasses from string and number literals.
If you don't like NSObject as the type of the key, you may create your own class or struct.
Note that your first attempt let dict = [1:intArray, "one": stringArray] works if you include Foundation; yielding an NSDictionary (so no need to explicitly state this type).
The reason why we can have these kinds of, apparently, generic dictionaries when using Foundation is the implicit type conversion performed (behind the hood) by the compiler when bridging Swift native types to Foundation.
let intArray : [Int] = [10, 20, 30]
let stringArray : [String] = ["foo", "baz", "bar"]
let dict = [1:intArray, "onx": stringArray]
print(dict.dynamicType)
for e in dict {
print(e.dynamicType, e.key.dynamicType, e.value.dynamicType)
}
/* __NSDictionaryI
(AnyObject, AnyObject) __NSCFString _SwiftDeferredNSArray
(AnyObject, AnyObject) __NSCFNumber _SwiftDeferredNSArray */
The keys as well as values in dict above are wrapped in type AnyObject; which can hold only reference (class) type objects; the compiler implicitly performs conversion of value types Int/String to Foundation reference types __NSCFNumber/__NSCFString. This is still an NSDictionary though; e.g. AnyObject itself does not conform to Hashable, so it can't be used as a key in a native Swift dictionary.
If you wish to create Swift-native "generic-key" dictionary, I'd suggest you create a wrapper (say a structure) that conforms to Hashable and that wraps over the underlying (various) key type(s). See e.g. (the somewhat outdated) thread
How to create Dictionary that can hold anything in Key? or all the possible type it capable to hold

Is Float, Double, Int an AnyObject?

I read inline documentation of Swift and I am bit confused.
1) Any is a protocol that all types implicitly conform.
2) AnyObject is a protocol to which all classes implicitly conform.
3) Int, Float, Double are structs
Here is a sample code:
import UIKit
func passAnyObject(param: AnyObject) {
print(param)
}
class MyClass {}
struct MyStruct {}
let a: Int = 1
let b = 2.0
let c = NSObject()
let d = MyClass()
let e = MyStruct()
passAnyObject(a)
passAnyObject(b)
passAnyObject(c)
passAnyObject(d)
//passAnyObject(e) // Argument type 'MyStruct' does not conform to expected type 'AnyObject'
if a is AnyObject { // b, d, e is also AnyObject
print("\(a.dynamicType) is AnyObject")
}
What I don't understand is why Int, Double, Float are AnyObjects? Why compiler doesn't say anything? Those types are declared as structs. Struct MyStruct cannot be passed to the method on the top because it does not conform to AnyObject.
Could you help me understand why Int, Double and Float are AnyObject or why compiler thinks they are?
Because you have Foundation imported, Int, Double, and Float get converted to NSNumber when passed to a function taking an AnyObject. Type String gets converted to NSString. This is done to make life easier when calling Cocoa and Cocoa Touch based interfaces. If you remove import UIKit (or import Cocoa for OS X), you will see:
error: argument type 'Int' does not conform to expected type 'AnyObject'
when you call
passAnyObject(a)
This implicit conversion of value types to objects is described here.
Update for Swift 3 (Xcode 8 beta 6):
Passing an Int, Double, String, or Bool to a parameter of type AnyObject now results in an error such as Argument of type 'Int' does not conform to expected type 'AnyObject'.
With Swift 3, implicit type conversion has been removed. It is now necessary to cast Int, Double, String and Bool with as AnyObject in order to pass it to a parameter of type AnyObject:
let a = 1
passAnyObject(a as AnyObject)
Good find! UIKit actually converts them to NSNumber - also mentioned by #vacawama. The reason for this is, sometimes you're working with code that returns or uses AnyObject, this object could then be cast (as!) as an Int or other "structs".
class Test {
static func test() {
let anyObjectsValues: [AnyObject] = [1, "Two", 3, "Four"] as [AnyObject]
anyObjectsValues.forEach { (value) in
switch value {
case is Int:
print("\(value) is an Int!")
case is String:
print("\(value) is a String!")
default:
print("\(value) is some other type!")
}
}
}
}
I have not imported UIKit or Foundation frameworks. Why compiler is not giving any error? Even it printing the result.
Output:
1 is an Int!
Two is a String!
3 is an Int!
Four is a String!
Does anybody have an idea?

Swift 2.0 beta Dictionary extension

Trying to create an immutable Dictionary. The idea is to have immutable arrays of keys and values each and then pass them to a
Dictionary constructor: let dict = Dictionary(aStringArray, aSameLengthDoubleArray)
The following code however gives a compile time error.
extension Dictionary {
init<T:Hashable,U>(keys: [T], values: [U]) {
self.init()
for (index, key) in keys.enumerate() {
self[key] = values[index]
}
}
}
Error:
error:
cannot subscript a value of type 'Dictionary' with an
index of type 'T' self[key] = values[index]
Can someone throw some light on this?
If you know that Dictionary already has typealiases for its associated types key and values. The keys should be of type Key and value should be of type Value. Just like you would have Element type in Array. The above thing you could achieve simply using Key and Value as so,
extension Dictionary {
init(keys: [Key], values: [Value]) {
self.init()
for (index, key) in keys.enumerate() {
self[key] = values[index]
}
}
}
let a = Dictionary(keys: [1, 2, 3, 4, 5], values: ["Michael", "Jack", "Kurt", "Jim", "Stewart"])

Shouldn't an optional be inclusive to a non-optional type in Swift?

Updated: with full code
I have this code:
struct Set<T : Hashable>: Sequence {
var items: Dictionary<Int, T> = [:]
func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
func generate() -> TypeGenerator<T> {
return TypeGenerator ( Slice<T>( items.values ) )
}
}
and I get the error:
Could not find an overload for 'subscript' that accepts the supplied arguments.
for the line:
items[newValue.hashValue] = newValue
As far as I understand it's because newValue's type is T rather than T?, meaning that it's not optional. This is because the Dictionary's subscript for accessing the key/value pair is defined as
subscript (key: KeyType) -> ValueType?
meaning that it can only take an optional as the value. And in my case newValue is not an optional after validating it's not nil.
But isn't an optional inclusive of non-optionals? Isn't a type's optional is the type + nil?
Why would something that can take everything + nil reject a type that can't be nil?
Small clarification: the reason I check that o is not nil is to be able to call its hashValue which is not accessible directly from the optional or the unwrapped optional (o!.hashValue throws a compile error).
I also can't replace the assignment line with
items[newValue.hashValue] = o
because it has validated that o is not an optional worthy of assignment even though it does not allow access to it's hashValue property.
The dictionary is not defined to store an optional value. It is just the assignment operator that accepts an optional value because giving it nil will remove the whole key from the dictionary.
The problem you are having is that you are trying to mutate your property items in a non-mutating method. You need to define your method as mutating:
mutating func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
There is certainly no problem assigning an optional variable to a non-optional value:
var optionalString : String? = "Hello, World"
In the same way, it is perfectly valid to assign a dictionary's key to a non-optional value:
var items : [Int:String] = [:]
items[10] = "Hello, World"
You can then assign the key to nil to remove the key entirely from the dictionary:
items[10] = nil
Also, I think you have a fundamental misunderstanding of what a hashValue is and how to use it. You should not pass the value of hashValue to a dictionary as a key. The dictionary calls hashValue on the value you provide, so you are having the dictionary take the hashValue of the hashValue.
There is no guarantee that a hashValue will be different from all other the hashValues of different values. In other words, the hash value of "A" can be the same hash value as "B". The dictionary is sophisticated enough to handle that situation and still give you the right value for a particular key, but your code is not handling that.
You can indeed store a non-optional value in a Dictionary and in general you can pass an object of type T whenever a T? is expected.
The real problem is that you haven't told the compiler that T is Hashable.
You have to put a type constraint on T, using T: Hashable. You can do this at a class level (I suppose this method lives inside a generic class), like so
class Foo<T: Hashable> {
var items: Dictionary<Int, T> = [:]
func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
}

Resources