Ilias Karim
Ilias Karim

Reputation: 5397

Swift: Remap value keys to keys with dictionray values in dictionary of dictionaries

Given,

let input = [
    "A": ["X": 1, "Y": 2, "Z": 3],
    "B": ["X": 7, "Y": 8, "Z": 9],
]

how do you produce,

[
    "X": ["A": 1, "B": 7],
    "Y": ["A": 2, "B": 8],
    "Z": ["A": 3, "B": 9],
]

using functional programming?

Upvotes: 0

Views: 116

Answers (2)

idz
idz

Reputation: 12988

You could do something like this:

let input = ["A": ["X": 1, "Y": 2, "Z": 3], "B": ["X": 7, "Y": 8, "Z": 9]]

let output = input.flatMap { okv in
    okv.value.map { ikv in
        (ikv.key, okv.key, ikv.value)
        }
    }
    .reduce(into: [String:[(String,Int)]]()) { accum, value in
        accum[value.0] = (accum[value.0] ?? []) + [(value.1,value.2)]
    }
    .reduce(into: [String:[String:Int]]()) { accum, value in
        accum[value.key] = value.value.reduce(into: [String:Int]()) {  iaccum, ivalue in
            iaccum[ivalue.0] = ivalue.1
        }
    }

This works by first expanding the nested dictionaries into an array of tuples [("X", "A", 1), ("Y", "A", 2), ... ] and then reduces that intermediate array back into a dictionary in two stages: first as a dictionary of arrays and then as a dictionary of dictionaries.

In fact, you can do a little bit better if you take advantage of default dictionary values:

let result = input.flatMap { okv in
    okv.value.map { ikv in
        (ikv.key, okv.key, ikv.value)
        }
    }
    .reduce(into: [String:[String:Int]]()) { accum,value in
        accum[value.0, default: [String:Int]()][value.1] = value.2
    }

Upvotes: 4

Tushar Sharma
Tushar Sharma

Reputation: 2882

I cam up with something like below. I found this problem much easier to understand using imperative approach, rather than using functional programming.

Note-: I haven’t covered edge cases.

class Test{
    var outputDict:[String:[String:Int]] = [:]
    
    func test(){
        let input = ["A": ["X": 1, "Y": 2, "Z": 3], "B": ["X": 7, "Y": 8, "Z": 9]]
        
        for (key,val) in input{
            var newDict:[String:Int] = [:]
            for (key1,val1) in val{
                generateInternalDict(&newDict, val1, key, key1)
            }
        }
        print(outputDict)
    }
    
    func generateInternalDict(_ newDict:inout [String:Int],_ value:Int,_ keyOuter:String,_ keyInner:String){
        if outputDict[keyInner] == nil{
            newDict[keyOuter] = value
            outputDict[keyInner] = newDict
        }else{
            var getDictForKey = outputDict[keyInner]
            getDictForKey![keyOuter] = value
            outputDict[keyInner] = getDictForKey
        }
    }

}

Output-:

["X": ["B": 7, "A": 1], "Z": ["B": 9, "A": 3], "Y": ["B": 8, "A": 2]]

Upvotes: 2

Related Questions