How do I cast from a heterogeneous array in Swift 4? - swift4

let data = ["1", "2", 3, 4, 5];
var m: Int = data[3] as! Int;
error: MyPlayground.playground:238:12: error: heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
let data = ["1", "2", 3, 4, 5];
^~~~~~~~~~~~~~~~~~~ as [Any]

Did you try doing what the error suggests?
let data: [Any] = ["1", "2", 3, 4, 5]; // Added explicit type annotation
var m: Int = data[3] as! Int;

Related

No exact matches in call to initializer when using initializer syntax with dictionaries

Creating an array using either literal syntax or initializer syntax works.
var arr1: Array<Int> = [3, 4, 5]
var arr2 = Array<Int>([3, 4, 5])
However, when using the initializer syntax with a dictionary returns an error.
var dict1: Dictionary<Int, String> = [1: "aaa", 2: "bbb"]
var dict2 = Dictionary<Int, String>([1: "aaa", 2: "bbb"]) // error
var dict3 = [Int: String]([1: "aaa", 2: "bbb"]) // error
Not including the value in the initializer syntax and then assigning it to the dictionary after solves the issue.
var dict4 = Dictionary<Int, String>()
dict4 = [1: "aaa", 2: "bbb"]
My question is does using the initializer syntax with a value for a dictionary return an error?
Xcode Version 14.0.1
Dictionaries have to know how to deal with non-unique values.
The closest initializer to what you're looking for is:
Dictionary<Int, String>(uniqueKeysWithValues: [1: "aaa", 2: "bbb"])
That will give its own new error:
error: initializer 'init(uniqueKeysWithValues:)' requires the types '(key: Int, value: String)' and '(Int, String)' be equivalent
So you'd need to do a map to remove the the key: and value: tuple labels:
Dictionary<Int, String>(uniqueKeysWithValues: [1: "aaa", 2: "bbb"].map { ($0.key, $0.value) })
Which is pretty clunky. What exactly are you trying to achieve?

Element type changes in type array Any

How to change the type to Int for an element in an array of type Any
Waited for it to work but no
var arr: Any = [9, 3, "7", "3"]
var arrInt = arr.map{ $0 as! Int } // Value of type 'Any' has no member 'map'
If you want to convert your values to Int you will need to try to cast your element to Int and in case it fail you need to cast from Any to String and initialize a new integer from it:
let arr: [Any] = [9, 3, "7", "3"]
let arrInt = arr.compactMap { $0 as? Int ?? Int($0 as? String ?? "") }
arrInt // [9, 3, 7, 3]
If you really want to declare your object as Any you will need to cast it to array before trying to iterate its elements:
let arr: Any = [9, 3, "7", "3"]
let arrInt = (arr as? [Any])?.compactMap { $0 as? Int ?? Int($0 as? String ?? "") } ?? []
arrInt // [9, 3, 7, 3]
Leo's chain of ?? nil coalescing operators is clever, but a little hard to follow. A longer way to write the same sort of conversion that I think is clearer:
var arr: [Any] = [9, 3, "7", "3", 1.34, "Lorem ipsum"]
let newArray: [Int] = arr.compactMap {
switch $0 {
case let anInt as Int:
return anInt
case let string as String:
return Int(string)
default:
return nil
}
}
print(newArray)
prints [9, 3, 7, 3]

Strange behaviour filtering Swift Array by Type

Can someone explain to me why the following code seems to fail to differentiate between numeric types?
extension Array {
func filterByType<T>(type: T.Type) -> [T] {
var r = [T]()
for case let m as T in self {
r += [m]
}
return r
}
func filterByType2<T>(type: T.Type) -> [T] {
var r = [T]()
for m in self {
if m is T {
r += [m as! T]
}
}
return r
}
}
let objects = [1, "2", 3, "4", 5.1, [1, 2]]
typealias IntArray = [Int]
objects.filterByType(String.self) // ["2", "4"] - as expected
objects.filterByType(IntArray.self) // [[1, 2]] - as expected
objects.filterByType(Double.self) // [1, 3, 5.1] - ok, but surprised 1 & 3 aren't Ints
objects.filterByType(Int.self) // [1, 3, 5] - why?
objects.filterByType2(String.self) // ["2", "4"] - as expected
objects.filterByType2(IntArray.self) // [[1, 2]] - as expected
objects.filterByType2(Double.self) // [1, 3, 5.1] - ok, but surprised 1 & 3 aren't Ints
objects.filterByType2(Int.self) // [1, 3, 5] - why?
Without any type annotation, the type of the heterogenous array
let objects = [1, "2", 3, "4", 5.1, [1, 2]]
is inferred as [NSObject]. In particular, the numbers are converted
to NSNumber objects. A (conditional) cast from NSNumber to Double
or Int always succeeds and uses the doubleValue or integerValue
method.
This can be seen with the following simple example:
let n1 = NSNumber(integer: 123)
if let x = n1 as? Double { // warning: conditional cast from 'NSNumber' to 'Double' always succeeds
print(x) // 123.0
}
let n2 = NSNumber(double: 12.8)
if let x = n2 as? Int { // warning: conditional cast from 'NSNumber' to 'Double' always succeeds
print(x) // 12
}
See also
the "Numbers" section in "Working with Cocoa Data Types" in the "Using Swift with Cocoa and Objective-C" documentation,
the "Value Conversions" section in the NSNumber class reference.
The objects array is inferred to be of type NSObject and the numbers are of type NSNumber which can be casted to Int, Double and even Bool (probably unexpected).
So in order to get the desired behavior you should explicitly declare the type of your array as [Any]
As improvement for your algorithm I would suggest to use flatMap:
extension Array {
func filterByType<T>(type: T.Type) -> [T] {
return self.flatMap{ $0 as? T }
}
}
I believe that the following two lines in filterByType and filterByType2 are casting the Int and Double values. The String and [Int] types give the expected results because these cannot be cast.
for case let m as T in self {
// ...
r += [m as! T]
1, 3, 5.1 can be cast as Double or Int.

Storing different types of value in Array in Swift

In Swift Programming Language, it says "An array stores multiple values of the same type in an ordered list." But I have found that you can store multiple types of values in the array. Is the description incorrect?
e.g.
var test = ["a", "b", true, "hi", 1]
From REPL
xcrun swift
1> import Foundation
2> var test = ["a", "b", true, "hi", 1]
test: __NSArrayI = #"5 objects" {
[0] = "a"
[1] = "b"
[2] =
[3] = "hi"
[4] = (long)1
}
3>
you can see test is NSArray, which is kind of AnyObject[] or NSObject[]
What happening is that Foundation provides the ability to convert number and boolean into NSNumber. Compiler will perform the conversion whenever required to make code compile.
So they now have common type of NSObject and therefore inferred as NSArray
Your code doesn't compile in REPL without import Foundation.
var test = ["a", "b", true, "hi", 1]
<REPL>:1:12: error: cannot convert the expression's type 'Array' to type 'ArrayLiteralConvertible'
var test:Array = ["a", "b", true, "hi", 1]
<REPL>:4:18: error: cannot convert the expression's type 'Array' to type 'ExtendedGraphemeClusterLiteralConvertible'
but you can do this
var test : Any[] = ["a", "b", true, "hi", 1]
Because they have a common type, which is Any.
Note: AnyObject[] won't work without import Foundation.
var test:AnyObject[] = ["a", "b", true, "hi", 1]
<REPL>:2:24: error: type 'Bool' does not conform to protocol 'AnyObject'
To initialize an Array with arbitrary types, just use
var arbitraryArray = [Any]().
AnyObject is a type and you can create an array that holds those, which (as the class name implies) means it can hold any type of object. NSArrays aren't type-bound and when you create an array with mixed types, it will generate an NSArray instead of an Array. I wouldn't rely on this, however, since it could change in the future (AnyObject[] is automatically bridged with NSArray).
You can try this in a playground (note: dynamicType returns "(Metatype)" and I wasn't sure how to pull out the actually type so I relied on the compiler error):
var x = [ 1, 2, "a" ]
x.dynamicType.description() // -> __NSArrayI
var y = [ 1, 2 ]
y.dynamicType.description() // -> Error: Array<Int>.Type does not have a member named 'description'.
var z: AnyObject[] = [ 1, 2, "a" ]
z.dynamicType.description() // -> Error: Array<AnyObject>.Type does not have a member named 'description'.
In Swift 3 you can use :
var testArray = ["a",true,3,"b"] as [Any]
Instead you can also use a struct in your class:
struct Something {
let a: String
let b: String?
let boolValue: Bool
let value: Int
init(a: String, b: String? = nil, boolValue: Bool, value: Int) {
self.a = a
self.b = b
self.boolValue = boolValue
self.value = value
}
}
The description is correct, an Array stores multiple values of the same type. The key is that one value has multiple types. That is, for example, a String has types of String and Any; an instance of a class Ellipse : Shape has types of Ellipse, Shape, AnyObject and Any.
14> class Foo {}
15> class Bar : Foo {}
16> var ar1 : Array<Any> = [1, "abc", Foo(), Bar()]
ar1: Any[] = size=4 {
[0] = <read memory from 0x7fa68a4e67b0 failed (0 of 8 bytes read)>
[1] = { ... }
[2] = {}
[3] = { ... }
}
17> ar1[0]
$R5: Int = <read memory from 0x7fa68a51e3c0 failed (0 of 8 bytes read)>
18> ar1[1]
$R6: String = { ... }
19> ar1[2]
$R7: Foo = {}
20> ar1[3]
$R8: Bar = {
lldb_expr_14.Foo = {}
}
21> ar1[0] as Int
$R9: Int = 1
Worked and Tested on Swift 5
You can explicitly state the data type to any. The Any type represents values of any type.
Type Casting
var test = ["a", "b", true, "hi", 1] as [Any]
Type explicit
var test: [Any] = ["a", "b", true, "hi", 1]

How to include optional in array in Swift

I want to insert an optional in an array as follows
let arr: AnyObject[] = [1, 2, nil, 4, 5]
The following expression throws a compile error saying
Cannot convert the expression's type 'AnyObject[]' to type 'AnyObject'
How can I made this work? I need optional or nil value in the array even though I know I shouldn't.
If you're just going to be inserting either Ints or nils, use
Swift < 2.0
var arr: Int?[] = [1, 2, nil, 4, 5]
Swift >= 2.0
var arr: [Int?] = [1, 2, nil, 4, 5]
Swift 3x
var arr: Array<Int?> = [1, 2, nil, 4, 5]
This can be done for any type (not just Int; that is if you want an array to hold a specific type but allow it to be empty in some indices, like say a carton of eggs.)
Just like this:
let arr: AnyObject?[] = [1, 2, nil, 4, 5]
it makes the Array of type AnyObject?
You can't use AnyObject:
18> let ar : AnyObject?[] = [1, 2, nil, 4, 5]
<REPL>:18:25: error: cannot convert the expression's type 'AnyObject?[]' to type 'AnyObject?'
let ar : AnyObject?[] = [1, 2, nil, 4, 5]
^~~~~~~~~~~~~~~~~
But you can use Any:
18> let ar : Any?[] = [1, 2, nil, 4, 5]
ar: Any?[] = size=5 {
[0] = Some {
Some = <read memory from 0x7f8d4b841dc0 failed (0 of 8 bytes read)>
}
[1] = Some {
Some = <read memory from 0x7f8d50da03c0 failed (0 of 8 bytes read)>
}
[2] = Some {
Some = nil
}
[3] = Some {
Some = <read memory from 0x7f8d4be77160 failed (0 of 8 bytes read)>
}
[4] = Some {
Some = <read memory from 0x7f8d4be88480 failed (0 of 8 bytes read)>
}
}
The Apple documentation makes this clear:
“Swift provides two special type aliases for working with non-specific
types:
o AnyObject can represent an instance of any class type.
o Any can represent an instance of any type at all, apart from function types.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.
https://itun.es/us/jEUH0.l
It appears that Int and probably other primitive types are not subtypes of a class type and thus AnyObject won't work.
let ar: AnyObject[] = [1,2,nil,3] is illegal because nil is not convertible to AnyObject.
let a: AnyObject?[] = [1,3,nil,2] or let a: Int?[] = [1,3,nil,2] works.
If you need an Objective-C bridgeable Array use
let a: AnyObject[] = [1, 2, NSNull(), 4, 5]
It's a cheat, but if you're using the nil as a sentinel it might be advisable to just use Tuples, one element being a Bool to rep the validity of the second.
var arr = [(true, 1), (true, 2) , (false, 0) , (true, 4),(true,5)]