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.

5
  • 1
    Possible duplicate of How to insert values into a nested Swift Dictionary Commented Jan 10, 2017 at 2:44
  • Show us what you have tried. Commented Jan 10, 2017 at 2: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 Commented Jan 10, 2017 at 3:25
  • This is just nested dictionary. Array of dictionary will look something like this [[String:Double]] Commented Jan 10, 2017 at 3:32
  • Updated to better explain my mission. Commented Jan 10, 2017 at 21:01

2 Answers 2

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.
Sign up to request clarification or add additional context in comments.

2 Comments

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 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?
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
    }
}

4 Comments

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"
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.
I've got v. 3.0.2 of Swift
Well, then it should not be the case, also I just read up on Dictionaries , 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.