Lazy dictionaries in Swift?

Context:

When you have a collection of items, one might want to transform these items lazily.

let arrayA = [25, 42, 6879]
let arrayB = arrayA.lazy.map { String($0 * 10) }
print(arrayB[1])  // "420"

Notice that the transformation happens only when needed and is more or less hidden from the callsite.


Is there a way to achieve the same with dictionaries without having to build a custom type?

let dictA = [0: 25, 1: 42, 2: 6879]  // using Int as keys for consistency here.
let dictB = /* magic */
print(dictB[1]) // "420" or Optional("420")


My attempts so far have failed as the resulting `dictB` doesn't have the subscript(Int).

//let dictB = dictA.values.map { ... }
//let dictB = dictA.map { ... }
//let dictB = dictA.lazy.map { ... }



Thank you,

Replies

In the absence of a solution without a custom type, you could do something like this (Swift 3.0.2):


struct DictionaryLazyMap<Key, Value, Transformed> where Key: Hashable {
   
    typealias Source = [Key : Value]
    typealias Transform = (Value) throws -> Transformed
   
    private let source: Source
    private let transform: Transform
   
    init(source: Source, transform: @escaping Transform) {
        self.source = source
        self.transform = transform
    }
   
    // Swift <= 3.0.2 does not support throwing subscripts.
    subscript(key: Key) -> Transformed? {
        return (try? source[key].map(transform)) ?? nil
    }
}


struct DictionaryLazyFlatMap<Key, Value, Transformed> where Key: Hashable {
   
    typealias Source = [Key : Value]
    typealias Transform = (Value) throws -> Transformed?
   
    private let source: Source
    private let transform: Transform
   
    init(source: Source, transform: @escaping Transform) {
        self.source = source
        self.transform = transform
    }
   
    // Swift <= 3.0.2 does not support throwing subscripts.
    subscript(key: Key) -> Transformed? {
        return (try? source[key].flatMap(transform)) ?? nil
    }
}


extension Dictionary {

    func lazyMap<Transformed>(_ transform: @escaping DictionaryLazyMap<Key, Value, Transformed>.Transform) -> DictionaryLazyMap<Key, Value, Transformed> {
        return DictionaryLazyMap(source: self, transform: transform)
    }
   
    func lazyFlatMap<Transformed>(_ transform: @escaping DictionaryLazyFlatMap<Key, Value, Transformed>.Transform) -> DictionaryLazyFlatMap<Key, Value, Transformed> {
        return DictionaryLazyFlatMap(source: self, transform: transform)
    }
}


let dictA = ["a": 25, "b": 42, "c": 6879]
let dictB = dictA.lazyMap { String($0 * 10) }

print(dictB["b"] as Any)    // Optional("420")
print(dictB["x"] as Any)    // nil



Swift 3.0.x doesn't allow a type definition to be nested inside an extension on a generic type, but 3.1 will.


Edit: Added a flatMap.