【问题标题】:how to change value of dictionary of dictionary effectively and dynamically如何有效且动态地改变字典字典的值
【发布时间】:2016-11-02 07:30:17
【问题描述】:

如果我有这样的字典“dic”:

{
    "a": {
           "b": Any
         }
    "c": {
           "d": Any 
         }
}

如果我想改变键“b”的值,我知道我可以这样做:

dic["a"]["b"] = somethingNew

但是如果key path是可变的,如何根据key path改变值呢?有没有这样的api:

dic.updateValueByKeyPath(path: ["a", "b"], updateValue: somethingNew)

或者达到这个目的的想法,谢谢~

【问题讨论】:

  • 其实你做不到dic["a"]["b"] = somethingNew
  • But If the key path is varible 是什么意思?
  • @LucaD'Alberti 我的意思是关键路径可以是 ["a"]["b"] 或 ["c"]["d"],它是一个变量
  • 但是在这种情况下你想改变什么?我不明白:您想更改 ["a"]["b"] 值,然后 keyPath 可能是 ["c"]["d"] ...这背后没有任何逻辑。先找到逻辑再简单实现
  • 也尝试在操场上进行测试。这对于这样的情况特别有用。你会发现dic["a"]["b"] = somethingNew 不会像 vadian 提到的那样工作。

标签: ios swift


【解决方案1】:

我最近回答了一个想要删除(而不是更新)嵌套字典字典中的嵌套值的问答。

我们可以在这里使用类似的方法:使用递归方法,通过反复尝试将(子)字典值转换为 [Key: Any] 字典本身来访问给定的键路径。这里唯一的限制是Key 类型对于嵌套字典中的所有字典都基本相同。

实施

递归“核心”函数updateValue(_:, inDict:, forKeyPath:) 和公共updateValue(_:, forKeyPath:) 方法为[Key] 形成关键路径(例如,["a", "b"] 应用于您的示例):

/* general "key path" extension */
public extension Dictionary {

    public mutating func updateValue(_ value: Value, forKeyPath keyPath: [Key])
        -> Value? {
        let (valInd, newDict) = updateValue(value, inDict: self, 
            forKeyPath: Array(keyPath.reversed()))
        if let dict = newDict as? [Key: Value] { self = dict }
        return valInd
    }

    fileprivate func updateValue(_ value: Value, inDict dict: [Key: Any], 
        forKeyPath keyPath: [Key]) -> (Value?, [Key: Any]) {
        guard let key = keyPath.last else { return (value, dict) }

        var dict = dict

        if keyPath.count > 1, let subDict = dict[key] as? [Key: Any] {
            let (val, newSubDict) = updateValue(value, inDict: subDict, 
                forKeyPath: Array(keyPath.dropLast()))
            dict[key] = newSubDict
            return (val, dict)
        }

        let val = dict.updateValue(value, forKey: key) as? Value
        return (val, dict)
    }
}

对于符合ExpressibleByStringLiteral 的密钥,较少公开的updateValue(_:, forKeyPath:) 方法(使用上面的核心函数); my.key.path 表单上的关键路径(例如,"a.b" 应用于您的示例):

/* String literal specific "key path" extension */
public extension Dictionary where Key: ExpressibleByStringLiteral { 

    public mutating func updateValue(_ value: Value, forKeyPath keyPath: String) 
        -> Value? {
        let keyPathArr = keyPath.components(separatedBy: ".")
            .reversed().flatMap { $0 as? Key }
        if keyPathArr.isEmpty { return self.updateValue(value, forKey: "") }

        let (valInd, newDict) = updateValue(value, 
            inDict: self, forKeyPath:keyPathArr)
        if let dict = newDict as? [Key: Value] { self = dict }
        return valInd
    }
}

示例用法

我们会将上述方法应用于链接线程中的示例。

var dict: [String: Any] = [
    "countries": [
        "japan": [
            "capital": [
                "name": "tokyo",
                "lat": "35.6895",
                "lon": "139.6917"
            ],
            "language": "japanese"
        ]
    ],
    "airports": [
        "germany": ["FRA", "MUC", "HAM", "TXL"]
    ]
]

使用ExpressibleByStringLiteral键路径方法更新现有键值对的值:

if let oldValue = dict.updateValue("nihongo", 
    forKeyPath: "countries.japan.language") {
    print("Removed old value: ", oldValue)
}
else {
    print("Added new key-value pair")
}
print(dict) 
/*  Removed old value:  japanese    

    [
        "countries": [
            "japan": [
                "capital": [
                    "name": "tokyo", 
                    "lon": "139.6917"
                    ], 
                "language": "nihongo"
            ]
        ], 
        "airports": [
            "germany": ["FRA", "MUC", "HAM", "TXL"]
        ]
    ] */

与在给定键路径字典中添加新键值对的方法相同:

if let oldValue = dict.updateValue("asia", 
    forKeyPath: "countries.japan.continent") {
    print("Removed old value: ", oldValue)
}
else {
    print("Added new key-value pair")
}
print(dict) 
/*  Added new key-value pair    

    [
        "countries": [
            "japan": [
                "capital": [
                    "name": "tokyo", 
                    "lon": "139.6917"
                    ], 
                "language": "nihongo",
                "continent": "asia"
            ]
        ], 
        "airports": [
            "germany": ["FRA", "MUC", "HAM", "TXL"]
        ]
    ] */

如果我们使用通用的[Key] 作为关键路径方法而不是上面使用的ExpressibleByStringLiteral,我们将得到与上述示例相同的结果。使用前者,调用将变为:

... = dict.updateValue("nihongo", 
      forKeyPath: ["countries", "japan", "language"]

... = dict.updateValue("asia", 
      forKeyPath: ["countries", "japan", "continent"]

最后请注意,使用[Key] 作为键路径方法调用updateValue 将返回nil,即使是空数组作为参数传递([])。这可能会更改为抛出情况,因为上面的 nil 返回应该告诉我们添加了一个新的键值对(受 stdlib 中的 the updateValue(_:, forKey:) method 启发)。


性能?

上述方法将使用一些(子)字典复制,但除非您使用庞大的字典,否则这应该不是问题。无论如何,在分析器告诉您存在瓶颈之前,无需担心性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-09
    • 1970-01-01
    • 2021-05-07
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多