Adding array elements to a dictionary

I am doing this now:

struct Element {
	let id: Int
	...
}

var elements: [Int : Element] = ...
var addedElements: [Element] = ...

addedElements.forEach { element in
	elements[element.id] = element
}
-- or --
for element in addedElements {
	elements[element.id] = element
}

Is there a better way of adding array elements to a dictionary?

The for-in statement allows for future alterations like awaiting things.
The forEach might be harder for the compiler to optimize if things get refactored beyond just the example.

There is another alternative approach could take something like:
elements.merge(addedElements.map { ($0.id, $0) }) { first, second in first }
But that seems much less readable to me.

In the end it really boils down to personal preference in your code; for an almost all apps it really shouldn't matter at all. Personally I like the for-in syntax for stuff like this and forEach syntax when I am passing a function in and not using the closure.

1 Like

The standard library is missing some niceties that help with this chore.

elements.merge(
  addedElements.keyed(by: \.id),
  uniquingKeysWith: PickValue.overwrite
)

Ideally, that would just be .overwrite, but you can't extend compound types.

public extension Sequence {
  /// Transform this sequence into key-value pairs.
  @inlinable func keyed<Key: Hashable>(
    by key: (Element) throws -> Key
  ) rethrows -> some Sequence<KeyValuePairs<Key, Element>.Element> {
    try lazy.map { (try key($0), $0) }
  }
}
import struct OrderedCollections.OrderedDictionary

public protocol DictionaryProtocol {
  associatedtype Key
  associatedtype Value
  typealias Element = (key: Key, value: Value)

  mutating func merge(
    _: some Sequence<(Key, Value)>,
    uniquingKeysWith: (Value, Value) throws -> Value
  ) rethrows
}

extension Dictionary: DictionaryProtocol { }
extension OrderedDictionary: DictionaryProtocol { }

public extension DictionaryProtocol {
  /// `merge`, with labeled tuples.
  ///
  /// - Parameter pairs: Either `KeyValuePairs<Key, Value.Element>`
  ///   or a `Sequence` with the same element type as that.
  @inlinable mutating func merge(
    _ pairs: some Sequence<Element>,
    uniquingKeysWith combine: (Value, Value) throws -> Value
  ) rethrows {
    try merge(pairs.lazy.map(removeLabels), uniquingKeysWith: combine)
  }
}
/// Remove the labels from a tuple.
/// - Parameter tuple: A tuple that may have at least one label.
@inlinable public func removeLabels<_0, _1>(_ tuple: (_0, _1)) -> (_0, _1) {
  tuple
}
/// Return an unmodified value when uniquing `Dictionary` keys.
public enum PickValue<Value> { }

public extension PickValue {
  /// Keep the original value.
  static var keep: (Value, Value) -> Value {
    { original, _ in original }
  }

  /// Overwrite the original value.
  static var overwrite: (Value, Value) -> Value {
    { $1 }
  }
}
1 Like