How can I initialize an OrderedDictionary from a regular Dictionary in Swift? - swift

Apples swift-collections package provides an alternative to a Dictionary type which guarantees to keep (not bring) its key-value pairs in order (unlike the regular Dictionary type). But how can I turn an instance of type Dictionary into an instance of type OrderedDictionary for later re-ordering?
For example, how would it work for this dict:
let regularDict: [String: String] = ["key1": "value1", "key2": "value2"]
// => EITHER {key2=>value2, key1=>value1} OR {key1=>value1, key2=>value2}
let orderedDict: OrderedDictionary<String, String> = .init(/* ??? */)
I am aware that orderedDict will have a random order of key-value entries at first, that's fine for me, I just need an OrderedDictionary instance so any future changes to the order I make don't get lost. I just want to know how to initialize it from a Dictionary in a way that is performant and makes sure the keys stay connected to the correct values.

You can use init(uniqueKeys:values:) with the regular dictionaries' keys and values:
let regularDict = ["key1": "value1", "key2": "value2"]
// => EITHER {key2=>value2, key1=>value1} OR {key1=>value1, key2=>value2}
var orderedDict = OrderedDictionary<String, String>(
uniqueKeys: regularDict.keys,
values: regularDict.values
)
// => EITHER {key2=>value2, key1=>value1} OR {key1=>value1, key2=>value2}
This will not magically sort anything though, the resulting orderedDict will have key-value entries of same random order as the regularDict. But any future changes to the order will be persistent. For example, you could order by keys of a type conforming to Comparable like this:
orderedEntries.sort(by: { $0.key < $1.key })
// => {key1=>value1, key2=>value2}

Related

Swift: Dictionary of Dicitionaries, cannot get subscript

I've looked at other subscript issues here and I don't think they match my problem. I have a dictionary of dictionaries - Dictionary[String:Dictionary[String:String]]
In an extension I want to loop through all the values (Dictionary[String:String] and retrieve one of the values.
So I wrote this:
for dictNEO in Array(self.values) {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}
and am getting this error on the last print line: Value of type 'Value' has no subscripts
Here's the first two print lines:
["nominalDist": "\"13.58 ", "approachDate": "\"2020-Feb-01 08:18 ± < 00:01\"", "minimumDist": "\"13.58 ", "diameter": "\"92 m - 210 m\"", "name": "\"(2017 AE5)\""]
Dictionary<String, String>
So I am confused as to why it is telling me it has no subscripts when it sees the type of as a Dictionary.
You have written this as an extension to Dictionary if I understand you correctly and that means that self is generic and defined as Dictionary<Key, Value> and not to you specific type so in your for loop you are looping over an array of [Value].
So you need to typecast Value before accessing it as a dictionary
if let dictionary = dictNEO as? [String: String] {
print(dictNEO["approachDate"])
}
but since it makes little sense to have an extension to Dictionary where you access a specific key it would be better to write it as a function. Since the dictionary is well defined now there is no issue with the last print
func printValuesForSubKey(_ key: String, _ dict: [String: [String: String]]) {
for (dictNEO) in dict.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO[key])
}
}
Note, I don't have an explanation why type(of:) recognises it as [String: String]
The code snippet doesn't work because values property is a collection of collections and with Array(values) you create a collection of collection of collections. In short, instead going down the code goes up and creates new collection level.
Solution with a Higher order function map:
self.values.map { print(type(of: $0)); $0["approachDate"] }
Solution with For-In Loop
for dictNEO in self.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}

Getting Key from Dict as String in Swift

New to coding here.
I have a dict with strings as keys and arrays of integers as values and I'm trying to get, from a given key, the String of that key. Not the value of the key, but the key itself. Now, I would just put an extra String into the array and call that, but my Xcode seems to have a bug where it really doesn't like having mixed type arrays and it doesn't work.
An example looks like this:
var allDict: [String: [Int]] = [:];
allDict.updateValue([1, 2, 3], forKey: "BAGEL"]);
allDict.updateValue([4, 5, 6], forKey: "DONUT"]);
allDict.updateValue([7, 8, 9], forKey: "MACARON"]);
I can get the values of each array quite fine with allDict["DONUT"]![1] //prints 5 for example, but what I want is to get the String of the key.
i.e. I would like to print DONUT using allDict["DONUT"]!
Is this possible? Thank you in advance!
It looks like you know your keys going in, in this example.
Here are a few ways you might recover your keys in a useful way, though:
Say you have a dictionary
var dict: [String: Int] = ...
You could get the array of keys:
let keys = dict.keys // keys is of type [String]
You can iterate over keys and values:
for (key, value) in dict {
...
}
You can merge dictionaries of and choose values from either dictionary when keys collide:
let mergedDict = dict.merge(otherDict) { leftValue, rightValue in
return leftValue
}
Addressing a version of your original question briefly:
Say you have the value for a certain key:
let donutValue = dict["DONUT"]
and somewhere else, where you lo longer have access to the key, you want to recover it from the value somehow. The best you could do is attempt to find the key by searching through the dictionary with the value you have.
var searchResult = dict.first { key, value in
return value == donutValue
}
This assumes the values in your dictionary are Equatable. Otherwise, you have to write some function or logic to figure out whether or not you've found that value in the dictionary corresponding to donutValue.

how to add multiple key value pairs to dictionary Swift

okay, i'm trying to have the user add a key and value pair to a dictionary i created and have it show up in a table view. i do that just fine but i cant seem to figure out how to add another pair. when i go to add another it replaces the last one. id really like to have multiple pairs. can someone help please?
heres my code:
//declaring the dictionary
var cart = [String:String]()
//attempting to add to dictionary
cart[pizzaNameLabel.text!] = formatter.stringFromNumber(total)
This is how dictionary works:
In computer science, an associative array, map, symbol table, or
dictionary is an abstract data type composed of a collection of (key,
value) pairs, such that each possible key appears at most once in the
collection.
So
var cart = [String:String]()
cart["key"] = "one"
cart["key"] = "two"
print(cart)
will print only "key" - "two" part. It seems that you may need an array of tuples instead:
var cart = [(String, String)]()
cart.append(("key", "one"))
cart.append(("key", "two"))
print(cart)
will print both pairs.
From Swift 5.0, you can use KeyValuePairs like ordered dictionary type with multiple keys.
See this Apple Document of KeyValuePairs. ;)
let recordTimes: KeyValuePairs = ["Florence Griffith-Joyner": 10.49,
"Evelyn Ashford": 10.76,
"Evelyn Ashford": 10.79,
"Marlies Gohr": 10.81]
print(recordTimes.first!)

How do I "append" to an immutable dictionary in Swift?

In Scala, the + (k -> v) operator on immutable.Map returns a new immutable.Map with the contents of the original, plus the new key/value pair. Similarly, in C#, ImmutableDictionary.add(k, v) returns a new, updated ImmutableDictionary.
In Swift, however, Dictionary appears only to have the mutating updateValue(v, forKey: k) function and the mutating [k:v] operator.
I thought maybe I could play some trick with flatten(), but no luck:
let updated = [original, [newKey: newValue]].flatten()
gets me
Cannot convert value of type '() -> FlattenCollection<[[String : AnyObject]]>'
to specified type '[String : AnyObject]'
How do I create a new, modified immutable Dictionary from the contents of an existing one?
Update: Based on this answer's note that Swift dictionaries are value types, and this answer's mutable version, I came up with the following extension operator, but I'm not excited about it -- it seems like there must be a cleaner out-of-the-box alternative.
func + <K, V>(left: [K:V], right: [K:V]) -> [K:V] {
var union = left
for (k, v) in right {
union[k] = v
}
return union
}
But maybe the fact (if I understand correctly) that the immutability of Swift dictionaries is a compiler check on let rather than a matter of different implementation classes means this is the best that can be done?
Update #2: As noted in Jules's answer, modifying immutable dictionaries that aren't specifically optimized to share state between copies (as Swift dictionaries aren't) presents performance problems. For my current use case (AttributedString attribute dictionaries, which tend to be fairly small) it may still simplify certain things enough to be worth doing, but until and unless Swift implements a shared-state immutable dictionary it's probably not a good idea in the general case -- which is a good reason not to have it as a built-in feature.
Unfortunately, this is a good question because the answer is "you can't". Not yet, anyway--others agree this should be added, because there's a Swift Evolution proposal for this (and some other missing Dictionary features). It's currently "awaiting review", so you may see a merged() method that's basically your + operator in a future version of Swift!
In the meantime, you can use your solution to append entire dictionaries, or for one value at a time:
extension Dictionary {
func appending(_ key: Key, _ value: Value) -> [Key: Value] {
var result = self
result[key] = value
return result
}
}
There's no built-in way to do this right now. You could write your own using an extension (below).
But keep in mind that this will likely copy the dictionary, because dictionaries are copy-on-write, and you're doing exactly that (making a copy, then mutating it). You can avoid all this by just using a mutable variable in the first place :-)
extension Dictionary {
func updatingValue(_ value: Value, forKey key: Key) -> [Key: Value] {
var result = self
result[key] = value
return result
}
}
let d1 = ["a": 1, "b": 2]
d1 // prints ["b": 2, "a": 1]
let d2 = d1.updatingValue(3, forKey: "c")
d1 // still prints ["b": 2, "a": 1]
d2 // prints ["b": 2, "a": 1, "c": 3]
The most straightforward thing to do is to copy to a variable, modify, then re-assign back to a constant:
var updatable = original
updatable[newKey] = newValue
let updated = updatable
Not pretty, obviously, but it could be wrapped into a function easily enough.
extension Dictionary {
func addingValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
// Could add a guard here to enforce add not update, if needed
var updatable = self
updatable[key] = value
return updatable
}
}
let original = [1 : "One"]
let updated = original.addingValue("Two", forKey: 2)
I don't believe there's a solution other than roll-your-own.
But maybe the fact (if I understand correctly) that the immutability of Swift dictionaries is a compiler check on let
Right, mutability is specified on the storage, that is, the variable, not on the value.
Do not try to update an immutable dictionary unless it has been specifically designed for immutability.
Immutable dictionaries usually use a data structure (such as a red/black tree with immutable nodes than can be shared between instances or similar) that can generate a modified copy without needing to make copies of the entire content, but only a subset (i.e. they have O(log(n)) copy-and-modify operations) but most dictionaries that are designed for a mutable system and then used with an immutable interface do not, so have O(n) copy-and-modify operations. When your dictionary starts to get larger than a few hundred nodes, you'll really notice the performance difference.

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
]