Swift 2.0 beta Dictionary extension - ios

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

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

Array.map() produces '[T]', not the expected contextual result type '[String: Any?]'

I'm writing an extension to bridge the dictionary values between FirebaseDatabase and Eureka.
private extension Dictionary {
func firebaseFriendlyDictionary() -> [String: Any?] {
return self.map({ (key: String, value: Any?) -> (String, Any?) in
if value is NSDate {
return (key, (value as! NSDate).timeIntervalSince1970)
}
return (key, value)
})
}
}
But I get thrown this error when I try to build:
map produces '[T]', not the expected contextual result type '[String: Any?]'
Your problem lies with the fact, that map always returns an Array, even when applied on a Dictionary. Your error message basically means, that you declared your method as returning a Dicitonary, but the statement inside returns an Array ([T] - means an Array with objects of some type T). In your case, the array returned by map will contain tuples (more about them here). In this case it looks like a key value pair, but its not equivalent to a key-value pair inside a Dictionary. Basically, tuples enable you to return more than one value/object from method. You can think of them as of anonymous structures.
In my opinion, there is no need to use a map to accomplish what you need - the solution provided by Mr. Xcoder is the way to go.
If you really want to use something functional like map, the method you actually want is reduce.
I'll demonstrate. To make things as clear as possible, I think it will help if we separate out the transformation to which your values are being subjected into a function of its own:
func dateToSeconds(_ thing:Any?) -> Any? {
guard let date = thing as? Date else {return thing}
return date.timeIntervalSince1970
}
Okay, so here's our test dictionary:
let d1 : [String:Any?] = ["1":Date(), "2":"two", "3":15, "4":true]
Now we're ready to apply reduce. It passes two parameters into its function. The first is the "accumulator" where we keep building up the ultimate result, which in this case is another dictionary. The second is an element of the original dictionary, represented as a tuple of key-value pairs called key and value:
let d2 = d1.reduce([String:Any?]()) { (dict, tuple) in
var dict = dict
dict[tuple.key] = dateToSeconds(tuple.value)
return dict
}
And when we examine d2, we see that we have got the right answer:
d2 // ["3": {some 15}, "2": {some "two"}, "1": {some 1486228695.557882}, "4": {some true}]
I couldn't figure out how to fix that error, nor have I succeeded achieving the desired result with an extension or a map(), but I have an alternative solution to the problem, using a function:
Declaring Dictionary:
var date = NSDate()
var swiftDict:[String : Any?] = ["1": date, "2": "two", "3": 15, "4": true]
Function:
func firebaseFriendlyDictionary(_ dict: [String : Any?]) -> [String : Any?]{
var Dict = dict
for (key, value) in dict
{
if (value is NSDate){
Dict[key] = (value as! NSDate).timeIntervalSince1970
}
}
return Dict
}
Usage:
swiftDict = firebaseFriendlyDictionary(swiftDict)
Testing:
Assuming that we have the date 2017-02-04 16:42:46 +0000 the output is 1486226566.2349629, which is correct.
Why not mapping the Dictionary? As Losiowaty pointed in his excellent answer, map always returns an Array, in this case an Array of Tuples([T]). In my opinion a map function is not needed in this context, plus it requires more code to accomplish.
Hope this helps!

Why print() is printing my String as an optional?

I have a dictionary and I want to use some of its values as a key for another dictionary:
let key: String = String(dictionary["anotherKey"])
here, dictionary["anotherKey"] is 42 but when I print key in the debugger I see the following:
(lldb) expression print(key)
Optional(42)
How is that possible? To my understanding, the String() constructor does not return an optional (and the compiler does not complain when I write let key: String instead of let key: String?). Can someone explain what's going on here?
As a sidenote, I am currently solving this using the description() method.
This is by design - it is how Swift's Dictionary is implemented:
Swift’s Dictionary type implements its key-value subscripting as a subscript that takes and returns an optional type. [...] The Dictionary type uses an optional subscript type to model the fact that not every key will have a value, and to give a way to delete a value for a key by assigning a nil value for that key. (link to documentation)
You can unwrap the result in an if let construct to get rid of optional, like this:
if let val = dictionary["anotherKey"] {
... // Here, val is not optional
}
If you are certain that the value is there, for example, because you put it into the dictionary a few steps before, you could force unwrapping with the ! operator as well:
let key: String = String(dictionary["anotherKey"]!)
You are misunderstanding the result. The String initializer does not return an optional. It returns the string representation of an optional. It is an non-optional String with value "Optional(42)".
A Swift dictionary always return an Optional.
dictionary["anotherKey"] gives Optional(42), so String(dictionary["anotherKey"]) gives "Optional(42)" exactly as expected (because the Optional type conforms to StringLiteralConvertible, so you get a String representation of the Optional).
You have to unwrap, with if let for example.
if let key = dictionary["anotherKey"] {
// use `key` here
}
This is when the compiler already knows the type of the dictionary value.
If not, for example if the type is AnyObject, you can use as? String:
if let key = dictionary["anotherKey"] as? String {
// use `key` here
}
or as an Int if the AnyObject is actually an Int:
if let key = dictionary["anotherKey"] as? Int {
// use `key` here
}
or use Int() to convert the string number into an integer:
if let stringKey = dictionary["anotherKey"], intKey = Int(stringKey) {
// use `intKey` here
}
You can also avoid force unwrapping by using default for the case that there is no such key in dictionary
var dictionary = ["anotherkey" : 42]
let key: String =
String(dictionary["anotherkey", default: 0])
print(key)

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

Swift Generics with Any

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

Resources