如何有效,动态地改变字典词典的价值

时间:2016-11-02 07:30:17

标签: ios swift

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

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

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

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

但是如果密钥路径是可变的,我如何根据密钥路径更改值?有这样的api:

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

或者实现这个目的的想法,谢谢〜

1 个答案:

答案 0 :(得分:2)

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

我们可以在这里使用类似的方法:使用递归方法,通过反复尝试将(子)字典值转换为[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)
    }
}

对于符合updateValue(_:, forKeyPath:)的密钥,不太通用的公共ExpressibleByStringLiteral方法(使用上面的核心函数);表单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"]

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

<强>性能吗

上述方法将使用一些(子)字典复制,但除非您使用大字典,否则这不应成为问题。无论如何,在探查器告诉你有瓶颈之前,无需担心性能。