# Mapping Dictionary keys

**URL:** <https://forums.swift.org/t/mapping-dictionary-keys/15342>\
**Category:** Pitches\
**Created:** [August 18, 2018, 1:26am UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342 "2018-08-18T01:26:10Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![aeioliu](https://avatars.discourse-cdn.com/v4/letter/a/9de0a6/32.png) [@aeioliu](https://forums.swift.org/u/aeioliu)\
**Post date:** [August 18, 2018, 1:26am UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/1 "2018-08-18T01:26:10Z")

</div>

It would be nice to have `mapKeys<T>` and `compactMapKeys<T>` functions for dictionaries, just as we now have `mapValues<T>` and `compactMapValues<T>` in Swift 4.0.

The motivation behind this is that there are concrete cases where you may need to modify the keys of a dictionary, leaving values intact, and to do this today requires an unnecessarily complex `reduce` function.

An example of this is working with `NSAttributedString` in `UIKit`.

`Say that we have `[NSAttributedStringKey: Any]`dictionaries encapsulating styles defined in a global constants file that we access regularly to generate attributed strings with the`NSAttributedString(string: String, attributes: [NSAttributedStringKey: Any]?)` initializer.`

When we want to use these dictionaries to, say, set the `defaultTextAttributes` on a `UITextField` instance, we run into the problem that `UITextField.defaultTextAttributes` expects `[String: Any]` as the dictionary type.

To adapt this attributes dictionary today, a possible approach is to use the reduce function:

```swift
var attributes: [NSAttributedStringKey: Any] = [
.font: UIFont.systemFont(ofSize: 15), 
.foregroundColor: UIColor.white
]

attributes.reduce(into: [String: Any](), { result, x in
    result[x.key.rawValue] = x.value
})

```

Granted in this example this is a bit trivial but you can see how the complexity can dramatically increase if we want to transform the key in more elaborate ways.

A simple extension to Dictionary to introduce `map`-type functions could be as simple as:

```swift
extension Dictionary {
    func compactMapKeys<T>(_ transform: ((Key) throws -> T?)) rethrows -> Dictionary<T, Value> {
        return try self.reduce(into: [T: Value](), { (result, x) in
            if let key = try transform(x.key) {
                result[key] = x.value
            }
        })
    }
}

```

yet this simplifies our function to:

```swift
var attributes: [NSAttributedStringKey: Any] = [
    .font: UIFont.systemFont(ofSize: 15), 
	  .foregroundColor: UIColor.white
]

attributes.compactMapKeys { $0.rawValue }

```

Thoughts as to whether or not this is worthwhile addition to the standard library is much appreciated!

---

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [August 18, 2018, 7:09pm UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/2 "2018-08-18T19:09:36Z")

</div>

This is not as strongly motivated as `mapValues`, and as such, does not make an appropriate addition to the standard library IMO.

In designing this API, you've made a decision: colliding keys should be silently coalesced. That isn't always the right decision. Sometimes colliding keys would indicate a programmer error and should trap. Sometimes you might need to do something else like deconflict them, or coalesce their values.

`mapValues` OTOH doesn't have these issues. And while the `mapValues` can be trivially composed, it has the benefit of potential optimization, since the dictionary does not have to rehash/bucket the values. That doesn't apply to `mapKeys`.

p.s. if you find the `reduce` version verbose, you can simplify it:

```swift
attributes.reduce(into: [:]) { result, x in
  result[x.key.rawValue] = x.value
}

// or alternatively, including an assertion for duplicate keys

Dictionary(uniqueKeysWithValues: attributes.map({ 
  (k,v) in (k.rawValue,v) 
}))

```

---

<div class="post-metadata">

**Author:** ![anon9791410](https://avatars.discourse-cdn.com/v4/letter/a/9fc348/32.png) [@anon9791410](https://forums.swift.org/u/anon9791410)\
**Post date:** [September 15, 2020, 3:37am UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/3 "2020-09-15T03:37:40Z")

</div>

I need this a lot. I bet other people do too and they just don't know they do, because they're using for loops instead. Overloads are the way to go!

```swift
public extension Dictionary {
  /// Same values, corresponding to `map`ped keys.
  ///
  /// - Parameter transform: Accepts each key of the dictionary as its parameter
  /// and returns a key for the new dictionary.
  /// - Postcondition: The collection of transformed keys must not contain duplicates.
  func mapKeys<Transformed>(
    _ transform: (Key) throws -> Transformed
  ) rethrows -> [Transformed: Value] {
    .init(
      uniqueKeysWithValues: try map { (try transform($0.key), $0.value) }
    )
  }

  /// Same values, corresponding to `map`ped keys.
  ///
  /// - Parameters:
  /// - transform: Accepts each key of the dictionary as its parameter
  /// and returns a key for the new dictionary.
  /// - combine: A closure that is called with the values for any duplicate
  /// keys that are encountered. The closure returns the desired value for
  /// the final dictionary.
  func mapKeys<Transformed>(
    _ transform: (Key) throws -> Transformed,
    uniquingKeysWith combine: (Value, Value) throws -> Value
  ) rethrows -> [Transformed: Value] {
    try .init(
      map { (try transform($0.key), $0.value) },
      uniquingKeysWith: combine
    )
  }

  /// `compactMap`ped keys, with their values.
  ///
  /// - Parameter transform: Accepts each key of the dictionary as its parameter
  /// and returns a potential key for the new dictionary.
  /// - Postcondition: The collection of transformed keys must not contain duplicates.
  func compactMapKeys<Transformed>(
    _ transform: (Key) throws -> Transformed?
  ) rethrows -> [Transformed: Value] {
    .init(
      uniqueKeysWithValues: try compactMap { key, value in
        try transform(key).map { ($0, value) }
      }
    )
  }
}

```

I haven't found a need for the remaining `compactMapKeys` overload yet, but I think it should be in the standard library anyway.

---

<div class="post-metadata">

**Author:** ![jamescs](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jamescs/32/16533_2.png) [@jamescs](https://forums.swift.org/u/jamescs)\
**Post date:** [April 14, 2021, 6:12am UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/4 "2021-04-14T06:12:28Z")

</div>

This may not be worth including in the standard library, but it's something I've found useful more than a few times. Riffing on Jessy's answer a little bit:

````
extension Dictionary {

    /// Transforms dictionary keys without modifying values.
    /// Deduplicates transformed keys.
    ///
    /// Example:
    /// ```
    /// ["one": 1, "two": 2, "three": 3, "": 4].mapKeys({ $0.first }, uniquingKeysWith: { max($0, $1) })
    /// // [Optional("o"): 1, Optional("t"): 3, nil: 4]
    /// ```
    ///
    /// - Parameters:
    /// - transform: A closure that accepts each key of the dictionary as
    /// its parameter and returns a transformed key of the same or of a different type.
    /// - combine:A closure that is called with the values for any duplicate
    /// keys that are encountered. The closure returns the desired value for
    /// the final dictionary.
    /// - Returns: A dictionary containing the transformed keys and values of this dictionary.
    func mapKeys<T>(_ transform: (Key) throws -> T, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [T: Value] {
        try .init(map { (try transform($0.key), $0.value) }, uniquingKeysWith: combine)
    }

    /// Transforms dictionary keys without modifying values.
    /// Drops (key, value) pairs where the transform results in a nil key.
    /// Deduplicates transformed keys.
    ///
    /// Example:
    /// ```
    /// ["one": 1, "two": 2, "three": 3, "": 4].compactMapKeys({ $0.first }, uniquingKeysWith: { max($0, $1) })
    /// // ["o": 1, "t": 3]
    /// ```
    ///
    /// - Parameters:
    /// - transform: A closure that accepts each key of the dictionary as
    /// its parameter and returns an optional transformed key of the same or of a different type.
    /// - combine: A closure that is called with the values for any duplicate
    /// keys that are encountered. The closure returns the desired value for
    /// the final dictionary.
    /// - Returns: A dictionary containing the non-nil transformed keys and values of this dictionary.
    func compactMapKeys<T>(_ transform: (Key) throws -> T?, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [T: Value] {
        try .init(compactMap { (try transform($0.key), $0.value) as? (T, Value) }, uniquingKeysWith: combine)
    }

}

````

---

<div class="post-metadata">

**Author:** ![BasemEmara](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/basememara/32/21528_2.png) [@BasemEmara](https://forums.swift.org/u/BasemEmara)\
**Post date:** [May 27, 2021, 9:06am UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/5 "2021-05-27T09:06:40Z")

</div>

Thanks for the clean extension. A `mapValues` counterpart makes perfect sense. My case I needed to serialize/deserialize strongly-typed dictionaries with hashable enum keys in and out of `UserDefaults`.

---

<div class="post-metadata">

**Author:** ![grynspan](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/grynspan/32/15315_2.png) [@grynspan](https://forums.swift.org/u/grynspan)\
**Post date:** [May 27, 2021, 4:56pm UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/6 "2021-05-27T16:56:20Z")

</div>

I believe there's room for improvement here over the current API. Right now you'd probably combine `map()`/`compactMap()` with `init(_:uniquingKeysWith:)`:

```swift
let originalDictionary: [Int: Potato] = [...]
let kvPairs = originalDictionary.map { (String(describing: $0.key), $0.value) }
let newDictionary = [String: Potato](kvPairs, uniquingKeysWith: { ... })

```

This pattern could probably be improved. It's complex enough that the "no one-liners" rule doesn't apply (IMHO) and it's not obvious at a glance what the code is supposed to do. I suspect in some cases new API would allow the stdlib to limit hashing overhead, though I haven't thought too much about it.

However I also suspect it means we'd need `mapKeys()` to take an additional `uniquingKeysWith` closure or to provide some other mechanism to ensure keys are unique in the output sequence. At that point, it's no longer really a `map()` operation in the functional programming sense, so I don't know if `mapKeys()` is then the right name for the function.

---

<div class="post-metadata">

**Author:** ![xiongju](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/xiongju/32/22841_2.png) [@xiongju](https://forums.swift.org/u/xiongju)\
**Post date:** [April 13, 2022, 10:49am UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/7 "2022-04-13T10:49:11Z")

</div>

dude this is perfect!

---

<div class="post-metadata">

**Author:** ![Stan\_Smida](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/stan_smida/32/10999_2.png) [@Stan\_Smida](https://forums.swift.org/u/Stan_Smida)\
**Post date:** [August 15, 2022, 12:27pm UTC](https://forums.swift.org/t/mapping-dictionary-keys/15342/9 "2022-08-15T12:27:27Z")

</div>

Could it be added to [swift-algorithms](https://github.com/apple/swift-algorithms) then? Seems like better fit than swift-collections.
