Как "добавить" к неизменяемому словарю в Swift?

В Scala оператор + (k -> v) на immutable.Map возвращает новый immutable.Map с содержимым оригинала, плюс новая пара ключей/значений. Аналогично, в С# ImmutableDictionary.add(k, v) возвращает новый, обновленный ImmutableDictionary.

В Swift, однако, Dictionary появляется только с функцией mutating 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 из содержимого существующего?


Обновление: На основе этого ответа обратите внимание, что словари 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 answer, изменение неизменяемых словарей, которые специально не оптимизированы для совместного использования состояний между копиями (как Swift словари не представляют) проблемы производительности. Для моего текущего варианта использования (AttributedString словари атрибутов, которые имеют тенденцию быть довольно небольшими), он все же может упростить некоторые вещи, которые можно сделать достойными, но до тех пор, пока Swift не будет применять неизменный словарь с разделяемым состоянием, это, вероятно, не очень хорошая идея в общий случай - это хорошая причина не иметь его как встроенную функцию.

Ответ 1

К сожалению, это хороший вопрос, потому что ответ "вы не можете". Пока еще нет, другие согласны с тем, что это должно быть добавлено, потому что есть предложение Swift Evolution для этого (и некоторые другие отсутствующие функции Dictionary), Он в настоящее время "ожидает рассмотрения", поэтому вы можете увидеть метод merged(), который в основном ваш оператор + в будущей версии Swift!

Тем временем вы можете использовать свое решение для добавления целых словарей или для одного значения за раз:

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

Ответ 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]

Ответ 3

Самая простая вещь - скопировать в переменную, изменить, а затем снова назначить константу:

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

Правильно, изменчивость указана в хранилище, то есть в переменной, а не на значении.

Ответ 4

Не пытайтесь обновить неизменяемый словарь, если он специально не предназначен для неизменности.

Неизменяемые словари обычно используют структуру данных (например, красное/черное дерево с неизменяемыми узлами, чем могут быть разделены между экземплярами или похожими), которые могут генерировать измененную копию без необходимости делать копии всего содержимого, но только подмножество (т.е. они выполняют операции копирования и изменения O (log (n))), но большинство словарей, которые предназначены для изменяемой системы, а затем используются с неизменяемым интерфейсом, нет, поэтому имеют операции O (n) copy-and-modify, Когда ваш словарь начнет получать больше нескольких сотен узлов, вы действительно заметите разницу в производительности.