首页 文章

如何“附加”到Swift中的不可变字典?

提问于
浏览
10

在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()

grab 我

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

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


Update: 根据this answer的说明,Swift词典是值类型,并且对此并不感兴趣 - 似乎必须有一个更清洁的开箱即用的替代方案 .

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 的编译器检查,而不是不同的实现类的问题意味着这是最好的可以做到的?


Update #2:Jules's answer中所述,修改不可变的字典会导致性能问题 . 对于我当前的用例( AttributedString 属性词典,它往往相当小),它仍然可以简化某些事情,值得做,但除非Swift实现共享状态不可变字典,否则它可能不是一个好主意 . case - 这是不将它作为内置功能的一个很好的理由 .

4 回答

  • 5

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

    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的编译器检查

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

  • 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]
    
  • 1

    不幸的是,这是一个很好的问题,因为答案是"you can't" . 还没有,无论如何 - 其他人同意这应该加上,因为有一个Swift Evolution proposal for this (and some other missing Dictionary features) . 它目前是"awaiting review",所以你可能会看到一个 merged() 方法,它基本上是你未来Swift版本中的 + 运算符!

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

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

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

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

相关问题