So I started a project in Swift, and I've come to this problem:
this code works:
var dictionary = ["a":"valueOfA","b":"valueOfB","c":"valueOfC"]
println(dictionary)
dictionary["c"] = "newValOfC"
println(dictionary)
and this doesn't:
var dictionary = [:]
dictionary = ["a":"valueOfA","b":"valueOfB","c":"valueOfC"]
println(dictionary)
dictionary["c"] = "newValOfC"
println(dictionary)
Gives an error:
Playground execution failed: error: <REPL>:35:17: error: cannot assign to the result of this expression
dictionary["c"] = "newValC"
~~~~~~~~~~~~~~~ ^
Notice that this is not a constant value
So why doesn't the line
dictionary = ["a":"valueOfA","b":"valueOfB","c":"valueOfC"]
give an error?
Since the context does not provide enough information to infer the type, you'll need to explicitly name it as a dictionary, otherwise swift assumes it is an NSDictionary (I'm not clear on why though. I assume for better obj-c compatibility):
The following code all works:
// Playground
import UIKit
var str:NSString = "Hello, playground"
var d0 = [:]
var d1: Dictionary = [:]
d0.setValue(UIWebView(), forKey: "asdf")
d1["asdf"] = 1
d1["qwer"] = "qwer"
Okay, I found it, the problem is that by initializing an empty dictionary, the type inference gets a little crazy.
You'll need this code:
var dictionary = Dictionary<String, String>()
instead of
var dictionary = [:]
but that still does not explain why the line
dictionary = ["a":"valueOfA","b":"valueOfB","c":"valueOfC"]
does not give an error
And referring to Swift Language Guide, the
dictionary = [:]
syntax is correct "if the context already provides type information".
The big difference is that
var dictionary = [:]
doesn't give any chance the compiler to infer the right type, whereas
var dictionary = ["a":"valueOfA","b":"valueOfB","c":"valueOfC"]
does.
Actually it looks like the first line produces a __NSDictionaryI instance, whereas the second one produces a Dictionary<String,String as expected.
From The Swift Programming Language book
If the context already provides type information, create an empty dictionary with an empty dictionary literal, which is written as [:] (a colon inside a pair of square brackets):
which means the first line is ambiguous.
The [:] syntax only works if the current context has enough information to be able to infer what the types are. Here are a few ways where the context is known...
As has been mentioned already, the code below works because it can be inferred that the keys and values are strings:
var dictionary = [ "a" : "valueOfA" , "b" : "valueOfB" , "c" : "valueOfC" ]
A dictionary passed into a function has a known context:
func dictionaryFunc(var dictionary : Dictionary<String, String>) {
// Do stuff with the dictionary
}
In both cases, the key/value types for the dictionary are now known, so you could do this to create a new dictionary with the same variable name...
dictionary = [:]
In the first case, if you used the [:] syntax after the declaration you would be erasing the contents of the original dictionary. In the second case, dictionaries get copied when they get passed into functions, so using [:] would only be erasing the copy.
In Swift 2.0 I'm using this:
var dictionary = [String:String]()
dictionary["key1"] = "value1"
Related
Swiftui dictionaries have the feature that the value returned by using key access is always of type "optional". For example, a dictionary that has type String keys and type String values is tricky to access because each returned value is of type optional.
An obvious need is to assign x=myDictionary[key] where you are trying to get the String of the dictionary "value" into the String variable x.
Well this is tricky because the String value is always returned as an Optional String, usually identified as type String?.
So how is it possible to convert the String?-type value returned by the dictionary access into a plain String-type that can be assigned to a plain String-type variable?
I guess the problem is that there is no way to know for sure that there exists a dictionary value for the key. The key used to access the dictionary could be anything so somehow you have to deal with that.
As described in #jnpdx answer to this SO question (How do you assign a String?-type object to a String-type variable?), there are at least three ways to convert a String? to a String:
import SwiftUI
var x: Double? = 6.0
var a = 2.0
if x != nil {
a = x!
}
if let b = x {
a = x!
}
a = x ?? 0.0
Two key concepts:
Check the optional to see if it is nil
if the optional is not equal to nil, then go ahead
In the first method above, "if x != nil" explicitly checks to make sure x is not nil be fore the closure is executed.
In the second method above, "if let a = b" will execute the closure as long as b is not equal to nil.
In the third method above, the "nil-coalescing" operator ?? is employed. If x=nil, then the default value after ?? is assigned to a.
The above code will run in a playground.
Besides the three methods above, there is at least one other method using "guard let" but I am uncertain of the syntax.
I believe that the three above methods also apply to variables other than String? and String.
I am trying to read the "value" field of a String in Kotlin. I am not very familiar with reflection in Kotlin, so I can't get it to work. This is what I have:
var str: String = "Some string"
val field = String::class.java.getDeclaredField("value")
field.isAccessible = true
println(field) // This prints "private final char[] java.lang.String.value"
println(field.get(str)) // This prints [C#66d3c617
When trying to cast char[] to Array, I get this exception:
java.lang.ClassCastException: [C cannot be cast to [Ljava.lang.Character;
What am I doing wrong?
I am not sure what you are trying to achieve but you can try this.
val value = (field.get(str) as ByteArray).toString(Charset.defaultCharset())
println(value)
In my env, the field is a ByteArray so I casted it to a ByteArray and get a printable version. In your case, a simple CharArray should suffice.
can anyone explain why line 1 works to create an empty swift dictionary but line 2 doesn't when i try to create a swift dictionary with int key and tuple of double values .... how should it be done?
var testDic2 = [Int:Double]()
var testDic3 = [Int:(Double,Double)]()
I've tried various combinations in playgrounds and the only version where it doesnt give me a compiler error is as follows
var possibleTips = [Int(): (tipAmt:Double(), total:Double())]
but im not sure this last form is declaring the dictionary as i intend it (ie as per testDict3 above)
The compiler isn't sure how to instantiate the type in your second and third example. Instead, you can declare the type and use an empty dictionary initializer:
var testDic:[Int:(Double,Double)] = [:]
It seems like you can't use named tuples in the shorthand notation of Dictionary. Is that so?
E.g.:
var dt = Dictionary<Int, (x:Double, y:Double)>()
var dtShort = [Int: (Double, Double)]()
var dtShortNamed = [Int: (x:Double, y:Double)]()
The first two lines work, the third triggers an error "Expected member name or constructor call after type name"
Is this correct, or am I missing something?
You are correct that it doesn't seem to work that way in Xcode 6 GM or Xcode 6.1 Beta 2.
It does work if you use a typealias though:
typealias NamedTuple = (x:Double, y:Double)
var dtShortNamed = [Int: NamedTuple]()
But, in that case, you might as well just use your first example:
var dt = Dictionary<Int, (x:Double, y:Double)>()
I am trying to do this:
var dictArray = [String:[String]]()
dictArray["test"] = [String]()
dictArray["test"]! += "hello"
But I am getting the weird error NSString is not a subtype of 'DictionaryIndex<String, [(String)]>'.
I just want to be able to add objects to an array inside a dictionary.
Update: Looks like Apple considers this a "known issue" in Swift, implying it will work as expected eventually. From the Xcode 6 Beta 4 release notes:
...Similarly, you cannot modify the underlying value of a mutable
optional value, either conditionally or within a force-unwrap:
tableView.sortDescriptors! += NSSortDescriptor(key: "creditName", ascending: true)
Workaround: Test the optional value explicitly and then assign the
result back:
if let window = NSApplication.sharedApplication.mainWindow {
window.title = "Currently experiencing problems"
}
tableView.sortDescriptors = tableView.sortDescriptors!
You can only do this
var dictArray = [String:[String]]()
dictArray["test"] = [String]()
var arr = dictArray["test"]!;
arr += "hello"
dictArray["test"] = arr
because dictArray["test"] give you Optional<[String]> which is immutable
6> var test : [String]? = [String]()
test: [String]? = 0 values
7> test += "hello"
<REPL>:7:1: error: '[String]?' is not identical to 'UInt8'
append also won't work due to the same reason, Optional is immutable
3> dictArray["test"]!.append("hello")
<REPL>:3:18: error: '(String, [(String)])' does not have a member named 'append'
dictArray["test"]!.append("hello")
^ ~~~~~~
BTW the error message is horrible...
You may use NSMutableArray instead of [String] as a value type for your dictionary:
var dictArray: [String: NSMutableArray] = [:]
dictArray["test"] = NSMutableArray()
dictArray["test"]!.addObject("hello")
This is still an issue in Swift 3. At least I was able to create method that can handle it for you.
func appendOrCreate(newValue: Any, toArrayAt key: String, in existingDictionary: inout [AnyHashable:Any]) {
var mutableArray = [Any]()
if let array = existingDictionary[key] as? [Any]{
//include existing values in mutableArray before adding new value
for existingValue in array {
mutableArray.append(existingValue)
}
}
//append new value
mutableArray.append(newValue)
//save updated array in original dictionary
existingDictionary[key] = mutableArray
}
The problem is that we want class semantics here but have to use structs. If you put class objects into the dictionary, you get what you want!
So, if you haveĀ¹ to have mutable values, you can wrap them in a class and perform updates with a closure:
class MutableWrapper<T> {
var rawValue: T
init(_ val: T) {
self.rawValue = val
}
func update(_ with: (inout T) -> Void) {
with(&self.rawValue)
}
}
Example:
func foo() {
var dict = [String: MutableWrapper<[String]>]()
dict["bar"] = MutableWrapper(["rum"])
dict["bar"]?.update({$0.append("gin")})
print(dict["bar"]!.rawValue)
// > ["rum", "gin"]
}
For what it's worth, I do not see a way to keep caller and wrapper in sync. Even if we declare init(_ val: inout T) we will end up with a copy in rawValue.
Performance is not necessarily an issue since the compiler optimizes structs heavily. I'd benchmark any mutable solution against what looks like lots of copy-updates in the code.
Since Swift 4.1 you can provide a default value to the subscript which allows you to solve this quite naturally now:
dictArray["test", default: []].append("hello")