Converting from dictionary to array in swift without for loop

I have some data returned from the server that looks like this:

let returnedFromServer = ["title" : ["abc",  "def",  "ghi"],

                      "time"  : ["1234", "5678", "0123"],

                      "content":["qwerty", "asdfg", "zxcvb"]]

      

I want to convert it to something like this:

let afterTransformation =

[["title" : "abc",
  "time"  : "1234",
 "content": "qwerty"],

["title" : "def",
 "time"  : "5678",
"content": "asdfg"],

["title" : "ghi",
 "time"  : "0123",
"content": "zxcvb"]]

      

My current implementation is this:

var outputArray = [[String : AnyObject]]()

for i in 0..<(returnedFromServer["time"] as [String]).count {

        var singleDict = [String: AnyObject]()

        for attribute in returnedFromServer {

            singleDict[attribute] = returnedFromServer[attribute]?[i]
        }
        outputArray.append(singleDict)
}

      

This works great, but I think it is not a very neat solution. Considering Swift has some neat features like reduce , filter and map , I'm wondering if I can do the same job without explicitly using a loop.

Thanks for any help!

+3


source to share


3 answers


Using ideas and expanding vocabulary

extension Dictionary {
    init(_ pairs: [Element]) {
        self.init()
        for (k, v) in pairs {
            self[k] = v
        }
    }

    func map<OutKey: Hashable, OutValue>(transform: Element -> (OutKey, OutValue)) -> [OutKey: OutValue] {
        return Dictionary<OutKey, OutValue>(Swift.map(self, transform))
    }
}

      

from



this can be done with

let count = returnedFromServer["time"]!.count
let outputArray = (0 ..< count).map {
    idx -> [String: AnyObject] in
    return returnedFromServer.map {
        (key, value) in
        return (key, value[idx])
    }
}

      

+4


source


Martin Rs answer is a good one and you should use it and accept his answer :-) but alternatively think:

In an ideal world, the Swift standard library would have:

  • ability to initialize a dictionary from an array of 2 tuples
  • a Zip3 in addition to Zip2 (i.e. take 3 sequences and concatenate them into a sequence of 3 tuples
  • zipWith implementation (i.e. similar to Zip3, but instead of just combining them in pairs, run the function on given sets to combine them together).

If you have it all, you can write the following:



let pairs = map(returnedFromServer) { (key,value) in map(value) { (key, $0) } }
assert(pairs.count == 3)
let inverted = zipWith(pairs[0],pairs[1],pairs[2]) { [$0] + [$1] + [$2] }
let arrayOfDicts = inverted.map { Dictionary($0) }

      

This can be useful for raw input reliably - it will only generate those items up to the shortest list in the input (as opposed to a solution that takes an invoice from one specific input list). The downside of it is hardcoded to size 3, but this can be fixed with a more general version of zipWith that accepted a sequence of sequences (although if you really wanted your keys to be strings and AnyObject

non-string values youd should get a fancier.

Functions that are difficult to write on your own - while clearly too much effort to write for this one-off use, they are useful in several situations. If you are interested Ive put the complete implementation in this gist .

+2


source


I would create 2 helpers:

ZipArray (similar Zip2

, but works with arbitrary length):

struct ZipArray<S:SequenceType>:SequenceType {
    let _sequences:[S]

    init<SS:SequenceType where SS.Generator.Element == S>(_ base:SS) {
        _sequences =  Array(base)
    }

    func generate() -> ZipArrayGenerator<S.Generator> {
        return ZipArrayGenerator(map(_sequences, { $0.generate()}))
    }
}

struct ZipArrayGenerator<G:GeneratorType>:GeneratorType {
    var generators:[G]
    init(_ base:[G]) {
        generators = base
    }
    mutating func next() -> [G.Element]? {
        var row:[G.Element] = []
        row.reserveCapacity(generators.count)
        for i in 0 ..< generators.count {
            if let e = generators[i].next() {
                row.append(e)
            }
            else {
                return nil
            }
        }
        return row
    }
}

      

Basically, ZipArray

flip the axisfrom Array

of Array

", for example:

[
    ["abc",  "def",  "ghi"],
    ["1234", "5678", "0123"],
    ["qwerty", "asdfg", "zxcvb"]
]

      

in

[
    ["abc", "1234", "qwerty"],
    ["def", "5678", "asdgf"],
    ["ghi", "0123", "zxcvb"]
]

      

Dictionary expansion:

extension Dictionary {
    init<S:SequenceType where S.Generator.Element == Element>(_ pairs:S) {
        self.init()
        var g = pairs.generate()
        while let (k:Key, v:Value) = g.next() {
            self[k] = v
        }
    }
}

      

Then you can:

let returnedFromServer = [
    "title" : ["abc",  "def",  "ghi"],
    "time"  : ["1234", "5678", "0123"],
    "content":["qwerty", "asdfg", "zxcvb"]
]

let outputArray = map(ZipArray(returnedFromServer.values)) {
    Dictionary(Zip2(returnedFromServer.keys, $0))
}

      

+1


source







All Articles