如何在Swift中“追加”不可变字典?

时间:2016-07-13 04:20:12

标签: swift functional-programming immutability

在Scala中,immutable.Map上的+ (k -> v)运算符会返回包含原始内容的新immutable.Map以及新的键/值对。同样,在C#中,ImmutableDictionary.add(k, v)会返回一个新的,更新的ImmutableDictionary

然而,在Swift中,Dictionary似乎只有变异updateValue(v, forKey: k)函数和变异[k:v]运算符。

我想也许我可以用flatten()玩一些技巧,但没有运气:

let updated = [original, [newKey: newValue]].flatten()

让我

Cannot convert value of type '() -> FlattenCollection<[[String : AnyObject]]>' 
to specified type '[String : AnyObject]'

如何从现有内容中创建新的,修改过的不可变Dictionary

更新:根据this answer注意,Swift词典是值类型,this answer的可变版本,我提出了以下扩展运算符,但是我并不为此感到兴奋 - 似乎必须有一个更清洁的开箱即用的替代方案。

func + <K, V>(left: [K:V], right: [K:V]) -> [K:V] {
    var union = left
    for (k, v) in right {
        union[k] = v
    }
    return union
} 

但也许事实(如果我理解正确的话)Swift词典的不变性是对let的编译器检查而不是不同的实现类的问题意味着这是最好的可以做到的?

更新#2:Jules's answer中所述,修改未专门优化以在副本之间共享状态的不可变字典(如Swift字典不是)会出现性能问题。对于我当前的用例(AttributedString属性字典,它往往相当小)它仍然可以简化某些事情,值得做,但除非Swift实现共享状态不可变字典,否则它可能不是很好在一般情况下的想法 - 这是一个不把它作为内置功能的好理由。

4 个答案:

答案 0 :(得分:5)

不幸的是,这是一个很好的问题,因为答案是“你做不到”。还没有,无论如何 - 其他人同意这应该添加,因为有一个Swift Evolution proposal for this (and some other missing Dictionary features)。它目前正在“等待审核”,因此您可能会在未来的Swift版本中看到基本上是merged()运算符的+方法!

与此同时,您可以使用您的解决方案附加整个词典,或一次添加一个值:

extension Dictionary {
    func appending(_ key: Key, _ value: Value) -> [Key: Value] {
        var result = self
        result[key] = value
        return result
    }
}

答案 1 :(得分:2)

现在没有内置的方法可以做到这一点。你可以使用扩展名编写自己的(下面)。

但请记住,这可能会复制字典,因为字典是写时复制的,而你正是这样做的(制作副本,然后改变它)。你可以通过首先使用一个可变变量来避免这一切: - )

extension Dictionary {
    func updatingValue(_ value: Value, forKey key: Key) -> [Key: Value] {
        var result = self
        result[key] = value
        return result
    }
}

let d1 = ["a": 1, "b": 2]
d1  // prints ["b": 2, "a": 1]
let d2 = d1.updatingValue(3, forKey: "c")
d1  // still prints ["b": 2, "a": 1]
d2  // prints ["b": 2, "a": 1, "c": 3]

答案 2 :(得分:2)

最直接的做法是复制到变量,修改,然后重新分配回常量:

var updatable = original
updatable[newKey] = newValue
let updated = updatable

显然不是很漂亮,但它可以很容易地包装到一个函数中。

extension Dictionary { 
    func addingValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> { 
        // Could add a guard here to enforce add not update, if needed 
        var updatable = self
        updatable[key] = value 
        return updatable
    } 
}

let original = [1 : "One"]
let updated = original.addingValue("Two", forKey: 2)

我不相信除了滚动你自己之外还有其他解决方案。

  

但也许事实(如果我理解正确的话)Swift词典的不变性是let上的编译器检查

是的,可变性是在存储上指定的,即变量,而不是

答案 3 :(得分:1)

不要尝试更新不可变字典,除非它是专为不变性而设计的。

不可变字典通常使用数据结构(例如红色/黑色树,其中包含可在节点之间共享的不可变节点或类似节点),可生成修改后的副本,而无需复制整个内容,但仅限于子集(即它们具有O(log(n))复制和修改操作)但是大多数为可变系统设计并且随后与不可变接口一起使用的字典没有,因此具有O(n)复制和修改操作。当你的字典开始大于几百个节点时,你会真正注意到性能差异。