【问题标题】:How to update a value in a nested dictionary given path fragment in Swift?如何在给定路径片段的 Swift 中更新嵌套字典中的值?
【发布时间】:2019-03-16 15:17:42
【问题描述】:

我有一个嵌套字典和一个包含路径片段的数组。我需要更新那个位置的值。

我可能正在寻找递归函数,而不是 Dictionary 类型等的扩展。

我无法递归执行此操作,因为我复制了 inout 参数。

var dict: [String: Any] = ["channel": ["item": ["title": 1111]]]
var pathFrag = ["channel", "item", "title"]
var val = 123

func addAt(pathFrag: inout [String], val: Int, data: inout [String: Any]) {
    if let first = pathFrag.first {
        if let item = data[first] {
            print(item)
            pathFrag.remove(at: 0)
            if !pathFrag.isEmpty {
                var d: [String: Any] = data[first] as! [String: Any]
                print("e: \(d)")
                return addAt(pathFrag: &pathFrag, string: string, data: &d)
            } else {
                data[first] = val
                print("else: \(data)")  // ["title": 123]
            }
        }
    }
}


addAt(pathFrag: &pathFrag, val: val, data: &dict)
print(dict)

如何将title的值更新为123

【问题讨论】:

  • 最好不要使用inout。现在您可以在返回最终值的辅助函数中进行递归。
  • 当字典很大并且复制会影响性能时,我有点担心。
  • 明白,但你不能一边吃蛋糕一边吃。如果你想要这种递归算法,在我看来,放弃值类型的优势,让你可以清楚地推理并只编写你想要的算法,这似乎是愚蠢的。过早的优化总是,呃,过早的。
  • 另外我要指出的是,这整个概念是 unSwifty 的。使用 Any 是一种难闻的气味。在 Objective-C 中,由于 KVC 和 keypaths,这将是单行的。
  • @matt 如何使用 Objective-C 来做到这一点?我试过var d = dict as NSDictionary; d.setValue(123, forKeyPath: "channel.item.title");,但它给了SIGABRT

标签: swift dictionary inout


【解决方案1】:

这不是你问的,但这里的整个前提是 unSwifty。使用[String:Any] 是一种难闻的气味。它看起来更像是 Objective-C。事实上,这整件事在 Objective-C 中是单行的:

NSMutableDictionary * d1 = [[NSMutableDictionary alloc] initWithDictionary: @{ @"title" : @1111 }];
NSMutableDictionary * d2 = [[NSMutableDictionary alloc] initWithDictionary: @{ @"item" : d1 }];
NSMutableDictionary * d3 = [[NSMutableDictionary alloc] initWithDictionary: @{ @"channel" : d2 }];

好的,这只是为了准备数据。这是一条线:

[d3 setValue:@123 forKeyPath:@"channel.item.title"];

但我会质疑整个嵌套字典的概念;在我看来,您在这里走错了路,您需要退后一步,重新考虑您的数据结构。

【讨论】:

  • 我正在解析 XML 并从中构造一个字典。我认为使用类/结构来表示数据而不是字典会更好。
  • 有XML解析类。我已经在别处解释了如何使用它们。假设您知道预期的结构,您应该尽可能使用嵌套结构。
【解决方案2】:

请注意,var d: [String: Any] = data[first] as! [String: Any] 复制了data[first],而不是像&inout 那样引用它。因此,当调用 addAt(pathFrag: &pathFrag, string: string, data: &d) 时,仅更改了 d。要使此更改反映原始 data[first],您必须将 d 分配回 data[first]

这样做:

var dict: [String: Any] = ["channel": ["item": ["title": 1111]]]
var pathFrag = ["channel", "item", "title"]

func addAt(pathFrag: inout [String], data: inout [String: Any]) {
    if let first = pathFrag.first {
        if let item = data[first] {
            pathFrag.remove(at: 0)
            if !pathFrag.isEmpty {
                var d: [String: Any] = data[first] as! [String: Any]
                addAt(pathFrag: &pathFrag, data: &d)
                data[first] = d
            } else {
                data[first] = 123
            }
        }
    }
}


addAt(pathFrag: &pathFrag, data: &dict)
print(dict)

【讨论】:

  • 谢谢!有没有办法让这个尾递归。赋值data[first] = d 出现在函数调用addAt 之后,这可能导致stackoverflow。
  • 是的,确实,它可以让我递归。但我认为它会在某种程度上破坏通过引用更改data 的目的。您可以在if 块中添加data[first] = addAt(pathFrag: &pathFrag, data: &d),在else 中添加return data[first],并在最外层return data 之外添加return data
  • var d: [String: Any] = data[first] as! [String: Any]pathFrag 中使用新密钥会导致崩溃。例如。 ["channel", "name", "title"]
猜你喜欢
  • 2019-05-18
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
  • 2016-10-05
  • 2021-09-29
  • 2015-09-11
  • 2011-07-02
  • 1970-01-01
相关资源
最近更新 更多