1

I have this structure: [String: [String: Double]]()

Specifically, something like that: var dictionaries = ["GF": ["ET": 4.62, "EO": 21.0],"FD": ["EE": 80.95, "DE": 0.4]]

How can I easily access and modify nested dictionaries?

EXAMPLE UPDATED: I want to append "TT": 6 at FD and later I want to append another dictionary inside the array. At the end I'll print the results.

for (key,value) in dictionaries {

    // if array contains FD, add the record to FD
    if key.contains("FD") {
        dictionaries["FD"]!["TT"] = 6
    }
    else {
    // if array doesn't contain FD, add FD and add the record to it
        dictionaries = dictionaries+["FD"]["TT"] = 6 // <-- I know that it's wrong but I want to achieve this result in this case.
    }
}

Result of print will be:

GF -> ET - 4.62, EO - 21.0
FD -> EE - 80.95, DE - 0.4, TT - 6

MISSION: I need to append new dictionary records like in the example above, update existing ones in a simple and straightforward way, loop easily through records to read the values and print them out.

Can anyone help me? Never had the chance to manage dictionaries in Swift since now.

  • 1
    Possible duplicate of [How to insert values into a nested Swift Dictionary](http://stackoverflow.com/questions/24554643/how-to-insert-values-into-a-nested-swift-dictionary) – rmaddy Jan 10 '17 at 02:44
  • Show us what you have tried. – Daniel T. Jan 10 '17 at 02:45
  • 1
    It may just be me, but I do not see any `Arrays` being used here, You have a `Dictionary` of `Dictionaries`. Both layers can be accessed using keys that you defined to be `String`. If I am right, the answer I posted will be useful to you, otherwise you will need to clarify about the use of `Arrays` before I can provide an accurate answer – Ben Ong Jan 10 '17 at 03:25
  • This is just nested dictionary. Array of dictionary will look something like this `[[String:Double]]` – ebby94 Jan 10 '17 at 03:32
  • Updated to better explain my mission. – talesfromnowhere Jan 10 '17 at 21:01

2 Answers2

0

It'll be much simpler if you have the key "FD" you can use

dictionaries["FD"]!["TT"] = 6

to add a new value ["TT":6] or modify existing value of TT to 6. Note that the ! between ["FD"]!["TT"] assumes that ["FD"] exists regardless. You need to check if ["FD"] exists otherwise. Like:

if dictionaries["FD"] != nil {
    dictionaries["FD"]!["TT"] = 6
} else {
    dictionaries["FD"] = ["TT" : 6]
}

If you need to look for the key you will have to run the entire dictionary like you already tried, but dictionaries support fast enumeration for key and values like

for (key,value) in dictionaries {
    if key.contains("FD") { //or any other checks you need to identify your key
        value["TT"] = 6
    }
}
Ben Ong
  • 913
  • 1
  • 10
  • 25
  • Updated my question to better explain my mission. If I put your example in Playground, it says "Cannot assign through subscript: 'value' is a 'let' constant" – talesfromnowhere Jan 10 '17 at 21:06
  • hm... interesting, I did my codes in playground before putting them in my answer too, can I check the version of swift you are using? Maybe a recent patch of swift allowed this? Mine is 3.0.1 btw. – Ben Ong Jan 11 '17 at 00:39
  • I've got v. 3.0.2 of Swift – talesfromnowhere Jan 11 '17 at 01:32
  • Well, then it should not be the case, also I just read up on [Dictionaries](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html) , it seems like my answer is how `Dictionaries` are suppose to work in swift. If it generates a compile error for you, consider @Raphael answer, might work instead. Might want to report to Apple about a bug in swift too – Ben Ong Jan 11 '17 at 01:39
0

It is not clear what exactly you need but the exercise amused me, so I came up with this solution: we extend Dictionary so that it provides convenience methods if it is a nested dictionary.

First, because of Swift idiosyncrasies, we have to create a dummy protocol to "mark" Dictionary¹:

protocol DictionaryProtocol {
    associatedtype Key: Hashable
    associatedtype Value

    subscript(key: Key) -> Value? { get set }
    var keys: LazyMapCollection<[Key : Value], Key> { get }
}

extension Dictionary: DictionaryProtocol {}

Basically, just copy-paste² all declarations you need later from Dictionary to DictionaryProtocol.

Then, you can happily extend away. For instance, add a two-parameter subscript:

extension Dictionary where Value: DictionaryProtocol {
    typealias K1 = Key
    typealias K2 = Value.Key
    typealias V  = Value.Value

    subscript(k1: K1, k2: K2) -> V? {
        get {
            return self[k1]?[k2]
        }
        set {
            if self[k1] == nil {
                self.updateValue([K2: V]() as! Value, forKey: k1)
            }

            self[k1]![k2] = newValue
        }
    }
}

Or an alternative pretty-print³:

extension Dictionary where Value: DictionaryProtocol {
    func pretty() -> String {
        return self.keys.map { k1 in
            let row = self[k1]!.keys.map { k2 in
                return "\(k2) - \(self[k1]![k2]!)"
            }.joined(separator: ", ")

            return "\(k1) -> \(row)"
        }.joined(separator: "\n")
    }
}

You can also create a type alias for this special dictionary:

typealias D2Dictionary<K: Hashable, V> = Dictionary<K, Dictionary<K, V>>

Going back to the example in your question:

var dictionary = D2Dictionary<String, Double>()
dictionary["GF", "ET"] = 4.62
dictionary["GF", "EO"] = 21.0
dictionary["FD", "EE"] = 80.95
dictionary["FD", "DE"] = 0.4
dictionary["FD", "TT"] = 6

print(dictionary.pretty())

// > GF -> ET - 4.62, EO - 21.0
// > FD -> EE - 80.95, DE - 0.4, TT - 6.0

  1. Background: Only protocols can be used in type bounds on extension conditions.
  2. Make sure to get the types right. If we write var keys: [Key] { get }, for instance, the compiler dies with a seg fault.
  3. Unfortunately, extension Dictionary: CustomStringConvertible where Value: DictionaryProtocol { ... } is not allowed, for whatever reason.
Community
  • 1
  • 1
Raphael
  • 9,779
  • 5
  • 63
  • 94
  • I like your solution but it's quite technical. Can you explain better the steps with some comments? I edited my question to explain better what I need to achieve! Thank you very much! – talesfromnowhere Jan 10 '17 at 21:16
  • @talesfromnowhere As far as I can tell, both your examples are covered by my solution. I absolutely expect you to want to extend it, by the way! What, specifically, don't you understand? – Raphael Jan 10 '17 at 21:47