Swift cannot append to subscript? - swift

I can't seem to use .append() on a subscript.
For example, here's the array:
var arrayTest = [
"test": 8,
"test2": 4,
"anotherarry": [
"test4": 9
]
]
I am able to do this:
arrayTest.append(["test3": 3])
But I can't append to the array inside arrayTest. This is what I'm trying:
arrayTest["anotherarray"].append(["finaltest": 2])

First note: your variable arrayTest is a dictionary of type [String: NSObject], not an array. Similarly the value for the key anotherarray is also a dictionary.
Second note: you are setting the key anotherarry and retrieving the key anotherarray which would be nil in this example.
I'm also not sure how you are able to call append() on arrayTest since it is a dictionary and doesn't have that method.
But the key issue with what you are trying to do is that dictionaries and arrays are value types and are copied when passed around, rather than referenced. When you subscript arrayTest to get anotherarray, you are getting a copy of the value, not a reference to the value inside the dictionary.
If you want to modify something directly inside an array or dictionary (as opposed to replacing it), that something must be a reference type (a class). Here's an example of how your code could be accomplished:
var arrayTest = [
"test": 8,
"test2": 4,
"anotherarray": ([
"test4": 9
] as NSMutableDictionary)
]
(arrayTest["anotherarray"] as? NSMutableDictionary)?["test5"] = 10
Note that this code forces "anotherarray" to explicitly be an NSMutableDictionary (a class type from Objective-C) instead of defaulting to a Swift dictionary (a value type). That's what makes it possible to modify it from outside the dictionary, since it is now being passed as a reference and not copied.
Further Note:
As pointed out in the comments, using NSMutableDictionary is not something I personally recommend and isn't a pure Swift solution, it's just the way to arrive at a working example with the fewest changes to your code.
Your other options would include replacing the anotherarray value entirely with a modified copy instead of trying to subscript it directly, or if it's important for you to be able to chain your subscripts, you could create a class wrapper around a Swift dictionary like this:
class DictionaryReference<Key:Hashable, Value> : DictionaryLiteralConvertible, CustomStringConvertible {
private var dictionary = [Key : Value]()
var description: String {
return String(dictionary)
}
subscript (key:Key) -> Value? {
get {
return dictionary[key]
}
set {
dictionary[key] = newValue
}
}
required init(dictionaryLiteral elements: (Key, Value)...) {
for (key, value) in elements {
dictionary[key] = value
}
}
}
Then you would use it similarly to the NSMutableDictionary example:
var arrayTest = [
"test": 8,
"test2": 4,
"anotherarray": ([
"test4": 9
] as DictionaryReference<String, Int>)
]
(arrayTest["anotherarray"] as? DictionaryReference<String, Int>)?["test5"] = 10

For example, here's the array:
Nope, arrayTest is NOT an array. It's a dictionary.
I am able to do this...
No you're not. There is no such append method into a dictionary.
The problem
So it looks like you have a dictionary like this
var dict: [String:Any] = [
"test": 8,
"test2": 4,
"anotherdict": ["test4": 9]
]
You want to change the array inside the key anotherdict (yes I renamed your key) in order to add the following key/value pair
"finaltest": 2
Here's the code
if var anotherdict = dict["anotherdict"] as? [String:Int] {
anotherdict["finaltest"] = 2
dict["anotherdict"] = anotherdict
}
Result
[
"test2": 4,
"test": 8,
"anotherdict": ["test4": 9, "finaltest": 2]
]

Related

Is it possible to make array reference immutable, but array content mutable?

In Java, we can make an array reference immutable, and array content mutable, by using final keyword
Java
final int[] array = {1, 2, 3};
// Ok. Array content mutable.
array[0] = 9;
// Compiler error. Array reference immutable.
array = new int[]{4, 5, 6};
In Swift, they take one step further. Using let keyword, will make both array reference, and array content immutable.
Swift
let array = [1, 2, 3]
// Compiler error. Array content immutable.
array[0] = 9
// Compiler error. Array reference immutable.
array = [4, 5, 6]
In Swift, is it possible to make array reference immutable, but array content mutable?
The answer for your question is "yes" and "no", depends on what you have.
If you decide to declare a simple "let" constant, you can't modify it.
Why ? Because it prevents you to side effects (and you have some optimization).
For example if you just want to browse a list and print values, you don't modify the list.
myArray = [1,2,3]
for element in myArray {
print(element)
}
Why it can be cool ? Now if you know that you don't want to modify your list, it prevents you to use functions that can modify your list. It will save your time and avoid some behavior that you don't expect.
If you declare a var and you don't modify the value, Swift will tell you too.
Moreover, the concept of immutable in Swift is interesting if you use a struct or a class.
Imagine you have this structure and this class:
struct TestStruct {
var myInt: Int
init(myInt: Int) {
self.myInt = myInt
}
}
struct TestClass {
var myInt: Int
init(myInt: Int) {
self.myInt = myInt
}
}
In this structure you have myIntwhich is a var. What happens if you try to declare a TestStructure and a TestClass object with a let constant ?
let testStruct = Test(myInt: 3)
// Cannot assign to property: 'test' is a 'let' constant
test.myInt = 5
let testClass = Test(myInt: 3)
// It works
test.myInt = 5
In a struct, the let is propagated for every field, which is not the case for a class.
Using let keyword, will make both array reference, and array content immutable.
This isn't correct. There is no "array reference" here. An array is a value, just like an integer is a value. There is no "array reference." Variables can be let or var, but that doesn't change the nature of their value. You wouldn't say that var n = 4 made "4" mutable. Similarly, var ns = [1,2,3] doesn't make [1,2,3] mutable. It just means you can change what ns refers to. Calling ns.append(5) is just like n += 1. In each case they assign a new value. They don't mutate the old value.
As an implementation and optimization detail, it is possible that the underlying array storage that was used for ns will be mutated and used for the new ns value. But this is invisible to the caller. For example:
var array = [1,2] {
didSet { print("\(oldValue) -> \(array)") }
}
array.append(1)
array = [1,2,1]
// [1, 2] -> [1, 2, 1]
// [1, 2, 1] -> [1, 2, 1]
There's no deep difference between the append and the assignment. They are both assignments. And notice that setting the value to the same value is still just an assignment.
I'm harping on this because you can't just translate over a Java approach and have it work if your Java code relies on shared mutable state (where one part of the program modifies an array and others are supposed to have their reference update). But if your Java works that way, I recommend improving your Java to reduce its reliance on that. As long as you generally just pass values and return values, then it'll work exactly the same in Swift as in Java.
If you still need this kind of mutable array, then you can build one fairly easily by wrapping an Array in a class:
final class ArrayRef<Element>: MutableCollection, ExpressibleByArrayLiteral {
private var elements: [Element] = []
init(arrayLiteral elements: Element...) {
self.elements = elements
}
var startIndex: Int { elements.startIndex }
var endIndex: Int { elements.endIndex }
func index(after i: Int) -> Int { elements.index(after: i) }
subscript(position: Int) -> Element {
get { elements[position] }
set { elements[position] = newValue }
}
}
let array: ArrayRef = [1, 2, 3]
// Ok. "Array" content mutable.
array[0] = 9
// Compiler error. "Array" is immutable.
array = [4, 5, 6]
(This is a very simple and unoptimized implementation. With more work you can make it more efficient and improve the interface.)
But I don't particularly recommend this unless you really need it. There's a reason it doesn't exist in stdlib.

Check whethere an array contain element

I wonder why following not working:
var tmp = [AnyObject] ()
for (key, value) in dictionary{
if (tmp.contains(value)){
}
tmp.append(value as AnyObject)
}
Look like there is no method .contain for an Array. it force me to use some other method tmp.contains(where: <#T##(AnyObject) throws -> Bool#>)
But i dont want to. I simply want to check whether an array contain that specific object!
I recommend switching from AnyObject to AnyHashable(of course if that's possible), because AnyHashable is Equatable. Your code would look like this:
var tmp = [AnyHashable]()
var dictionary: [Int: Any] = [1: "Hello1", 2: "Hello2", 3: "Hello3", 4: false, 5: 289 ]
for (key, value) in dictionary{
if tmp.contains(value as! AnyHashable){
continue
}
tmp.append(value as! AnyHashable)
}
Hope it helps!
That's because AnyObject is not an Equatable object. If you have an AnyObject array, you have to do the comparisons yourself. If you used an array of Strings or a custom class for instance you could use this contains() method of Sequence

How to add a extra values for the keys on dictionaries?

For the following variable
var dict2 = ["key1" : "value1", "key2" : [ "value1" , "value2" ]]
How to add a third value for the second key of the following dictionary?
If I can reformulate your dict2 declaration slightly:
var dict2 = ["key1" : ["value1"], "key2" : [ "value1" , "value2" ]]
then you can append an extra item to key2 like this:
dict2["key2"]?.append("value3")
However, you will probably need to be careful to check that key2 was already present. Otherwise, the above statement will do nothing. In which case you can write:
if dict2["key2"]?.append("value3") == nil {
dict2["key2"] = ["value3"]
}
Why did I change the original declaration? With the version I gave, dict2 will be of type [String:[String]]. But with your version, what Swift is doing is declaring a much more loosely typed [String:NSObject]. This compiles, but behaves very differently (contents will have reference not value semantics, you will have to do type checks and casts frequently etc), and is probably best avoided.
#AirspeedVelocity provides a working solution, but requiring a small change to the way the data is defined - but that is what I would do myself, if possible.
However if you have to stick with the original data format, you can use this code:
var dict2: [String : AnyObject] = ["key1" : "value1", "key2" : [ "value1" , "value2" ]]
var array = dict2["key2"] as? [String]
array?.append("value3")
dict2["key2"] = array
First we make explicit the dict2 type, a dictionary using strings as keys and AnyObject as values.
Next we extract the value for the key2 key, and attempt to cast to an array of strings - note that this returns an optional, so the type of array is [String]?
In the next line we add a new element to the array - note that if array is nil, the optional chaining expression evaluates to nil, and nothing happens
In the last line, we set the new array value back to the corresponding key - this step is required because the array is a value type, so when we extract it from the dictionary, we actually get a copy of it - so any update made on it won't be applied to the original array.

Array as a dictionary value in swift language

I have the following swift dictionary
var List = [
2543 : [ "book", "pen" ],
2876 : [ "school", "house"]
]
How can i access the array values ?
println(List[2543][0])
The above code gives error "could not find member subscript"
and it should print "book"
Note that subscript returns an optional. We have to force unwrapping:
println(list[2543]![0])
Or use optional chaining
println(list[2543]?[0])
println(list[2543]![0])
Remember, dictionary subscript returns an Optional, not an array or whatever is inside the dictionary value.
Just try with following code:
var dic = List[0];
println("values \(dic)")
OR
println(list[2543]![0])

How do I put different types in a dictionary in the Swift Language?

Swift only allows a dictionary to contain a single type.
Here's the definition that is taken from the Swift book:
A dictionary is a container that stores multiple values of the same type
[...]
They differ from Objective-C’s NSDictionary and NSMutableDictionary classes, which can use any kind of object as their keys and values and do not provide any information about the nature of these objects.
If that’s the case then how are we going to create nested dictionaries?
Imagine we have a plist that holds String, Array and Dictionary items in it . If I’m allowed to hold only the same of type of items (either string, array etc.) then how am I going to use different types of items stored in the plist?
How do I put different types in the same dictionary in Swift?
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary<String, Any>()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
EDIT:
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
As has been suggested, you can use the Any type to represent a plist dictionary's values. But then how do you work with the data? Cast every value any time you look it up from the dictionary? That's really messy. A better, more type-safe way to model a plist would be to take advantage of Swift's enums, also known as algebraic data types or discriminated unions. They let you specify exactly what types are permitted in the dictionary and avoid ever having to cast. Here's an implementation, explained:
// An atomic (i.e. non-collection) data type in a plist.
enum PListNode {
case PLN_String(String)
case PLN_Integer(Int)
case PLN_Float(Double)
case PLN_Bool(Bool)
case PLN_Date(CFDate)
case PLN_Data(CFData)
}
At the most atomic level, only the above data types may be stored in a plist. Each 'node' in the plist can ultimately can only be one of these types. So we create an enum which lets us specify this.
// A value that can be stored in a plist Dictionary's key-value pair.
enum PListValue {
case PLV_Node(PListNode)
case PLV_Array(PListNode[])
case PLV_Dictionary(Dictionary<String, Box<PListValue>>)
}
typealias PList = Dictionary<String, Box<PListValue>>
A plist is basically a dictionary of key-value pairs, and each value can be either an atomic (i.e. non-collection) value; or it can be an array of atomic values; or it can be a dictionary of string-plist value pairs. The above enum expresses these constraints, and the typealias gives the plist type an easy-to-remember name.
Given the above types, we can completely express any given plist in a type-safe way, e.g.:
// Example translated from
// https://developer.apple.com/library/Mac/documentation/Darwin/Reference/ManPages/man5/plist.5.html
let myPlist: PList = [
"Year Of Birth": Box(PLV_Node(PLN_Integer(1965)))
, "Pets Names": Box(PLV_Array([]))
, "Picture": Box(PLV_Node(PLN_Data(...)))
, "City of Birth": Box(PLV_Node(PLN_String("Springfield")))
, "Name": Box(PLV_Node(PLN_String("John Doe")))
, "Kids Names": Box(
PLV_Array([PLN_String("John"), PLN_String("Kyra")])
)
]
What it means to be type-safe here is that you can process any given plist using a switch statement and cover all possibilities without the need for any casting. You're eliminating a whole class of potential runtime errors. E.g.:
// See https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-XID_189 for explanation
switch myPlist["Year Of Birth"] {
case Box(.PLV_Node(let plvNodeValue)):
...
case Box(.PLV_Array(let plvArrayValue)):
...
case Box(.PLV_Dictionary(let plvDictionaryValue)):
...
}
Note that it's necessary to wrap up recursive data structures in a 'box' (a pointer to the actual value) to keep their sizes finite.
NSObject works for my case while "Any" does not
var d:Dictionary<String,NSObject> = [:]
d["key1"] = "ddd"
d["key2"] = 111 //OK
NSLog("%#", d) //OK
var d2:Dictionary = Dictionary<String,Any>()
d2["key1"] = "ddd"
d2["key2"] = 111
NSLog("%#", d2) //I got error here
Use NSMutableDictionary like this :
var dictInfo : NSMutableDictionary = [ "lang_key": "1"]
dictInfo["food_type"] = lbl_TypeOfFood.text
dictInfo["search_text"] = txt_Search.text
dictInfo["date"] = lbl_Date.text
dictInfo["opening_hours"] = lbl_OpeningHours.text
hope this will work fine .
Use: Dictionary<String, AnyObject>
var dict: Dictionary<String, AnyObject> = [
"number": 1,
"string": "Hello",
]
NSMutableDictionary to Dictionary works like a charm and will allow you to put different types in a Dictionary in the Swift Language:
let nsMutableDictionary = NSMutableDictionary()
nsMutableDictionary[NSFontAttributeName] = UIFont(name: "HelveticaNeue", size: 12.0)!
nsMutableDictionary[NSForegroundColorAttributeName] = UIColor.redColor()
let dictionary: Dictionary<NSObject, AnyObject> = nsMutableDictionary
self.attributedPlaceholder = NSAttributedString(string: textParam, attributes: dictionary)
let dictionary : Dictionary = [
"key": "value",
"key2": 2,
"key3": NSString(),
2: "test",
]
One can specify types which restricts the dictionary
let dictionary : Dictionary<String, String> = [
"key": "value",
"key2": 2, // This errors
]