Mapping a dictionary from an array

A thing that I use a lot to reduce the computation cycles when working with arrays is define an HashTable as a Dictionary from my array. To do this I usually define an extension of Array in my codebases like this one:

    /// returns a dictionary with mapped keys and values based on transformation block.
    /// multiple items with duplicated keys will be overridden, only the last processed will be mantained.
    ///
    /// - Parameters:
    ///   - transformBlock: launched for each Element of the array, returns a couple of Key and Value
    /// - Returns: a dictionary with a couple of Key: Value for each Element of the array
    func keyMap<Key: Hashable, Value>(transformBlock: (Element) -> (key: Key, value: Value)) -> [Key: Value] {
        var dictionary: [Key: Value] = [:]
        forEach { element in
            let map = transformBlock(element)
            dictionary[map.key] = map.value
        }
        return dictionary
    }

Could this one be a valid proposal or I'm just doing something wrong?

That would be

let list: [Int] = [1, 2, 3, 4]
let d = Dictionary(uniqueKeysWithValues: list.lazy.map { ($0, "\($0)") })

By using Array.lazy we can skip creating a temporary key/value pair array, correct?

7 Likes

This could be a viable solution, but i don't know if is better about performances, (don't know how Dictionary(uniqueKeysWithValues: Sequence) works under the hood).

I usually use this to compare multiple arrays of items,
where you need to recreate the same group of items updating some of them:

    func updateItems(newItems: [Item]) {
        let map = newItems.keyMap(transformBlock: { return (key: $0.id, value: $0) })
        self.referenceItems = internalItems.map { map[$0.id] ?? $0 }
    }

or define the map of changes from the array A to the same array shuffled,
so you need to know the positions of those items in both the arrays...

   struct Change {
        var old: Int
        var new: Int
    }
    func changes(newItems: [Item]) -> [Change] {
        let map = newItems.enumerated().keyMap { (offset, element) -> (key: Item, value: Int) in
            return (key: element, value: offset)
        }
        var changes: [Change] = []
        self.internalItems.enumerated().forEach { (offset, element) in
            if let newPosition = map[element], newPosition != offset {
                changes.append(Change(old: offset, new: newPosition))
            }
        }
        return changes
    }

Looks like it works pretty much like your own implementation.

I've definetly learned something new today,
Thank You

3 Likes

I always just use a reduce to achieve this purpose. I dont know how performant it is compared to any of the above solutions. But this has always been the easiest way.

struct User: Identifiable {
    var id: Int
    var name: String
}

let users = [User(id: 1, name: "Bob"), User(id: 2, name: "Lina"), User(id: 3, name: "Joe")]

let dictionary: [AnyHashable: User] = users.reduce(into: [:]) { dictionary, user in
     dictionary[user.id] = user
}

if let Joe = dictionary[3] {
    print("Retrieved Joe")
}
2 Likes