2016-07-13 58 views
10

在斯卡拉,+ (k -> v)運營商immutable.Map返回一個新的immutable.Map與原來的內容,再加上新的鍵/值對。同樣,在C#中,ImmutableDictionary.add(k, v)返回一個新的更新的ImmutableDictionary如何「附加」到Swift中的不可變字典?

但是,在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的注意,斯威夫特的字典是值類型,並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 
} 

但也許事實(如果我理解正確)是斯威夫特字典的不變性是let編譯器檢查,而不是不同的實現類的問題意味着這是可以做的最好?


更新#2:Jules's answer指出,修改未具體優化以拷貝之間共享狀態不變的詞典(如夫特字典都沒有)呈現性能問題。對於我目前的用例(AttributedString屬性字典,它往往很小),它仍然可以簡化某些足以值得做的事情,但直到Swift實現共享狀態不可變字典之前,它可能不是一個好主意案例 - 這是一個不把它作爲內置功能的好理由。

+1

看起來像某人正在建立一個圖書館來做到這一點https://github.com/tLewisII/ImStructures(免責聲明:我沒有審查過它。) –

+0

* Swift詞典的不變性是編譯器檢查[。 ..]不同的實現類*這是錯誤的。字典是作爲結構實現的,意味着它們(基本上)是通過複製傳遞的。存儲在'let'變量中的任何結構值都是不可變的,因此您不能在其上調用'mutating'方法,因爲它們會更改值。如果將一個字典(任何結構)賦值給一個'var'變量,它就會被複制!然後你有一個新的內存位置不同的字典。 – idmean

回答

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 
    } 
} 
+0

鏈接到提案的好主意,但合併並不完全是OP之後的內容。 – jtbandes

+1

不完全是這樣,但它接近他用flatten()方法所做的嘗試:它會允許'let newDictionary = oldDictionary.merged([new:stuff])',這對我來說已經足夠了。 (有趣的是,它也會允許他現有的'flatten()'技巧)。 – andyvn22

+0

@jtbandes這不是,但是因爲它和'+(元組:K)一樣簡潔 - 對於單個鍵值對,這可能是夠好的,特別是如果實現足夠聰明的話可以是O(log n)。 –

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

最直接的事情逃避這一切是複製到一個變量,修改,然後重新分配回一個常數:

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) 

我不相信有一個解決方案,而不是滾動你自己的。

但也許事實(如果我理解正確)是斯威夫特字典的不變性是let

右鍵,可變性在存儲規定,也就是可變編譯器檢查,而不是在的值

1

不要嘗試更新一個不變的詞典,除非它已經爲永恆而設計。

不可變字典通常使用數據結構(例如具有不可更改節點的紅/黑樹,而不是實例之間可共享的節點或類似結構),可以生成修改的副本,而無需製作整個內容的副本,子集(即它們具有O(log(n))複製和修改操作),但大多數爲可變系統設計的字典,然後與不可變接口一起使用的字典不會,所以O(n)複製和修改操作。當您的字典開始大於幾百個節點時,您會真正注意到性能差異。

+0

在這種特殊情況下,我正在查看_n_«100,但警告被注意到。從其他評論/答案,聽起來好像Swift不可變字典正是這個問題。 –