在没有for循环的情况下,在swift中从字典转换为数组

时间:2014-12-24 11:13:24

标签: arrays swift dictionary functional-programming reduce

我从服务器返回的一些数据如下所示:

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

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

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

我想把它变成这样的东西:

let afterTransformation =

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

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

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

我目前的实施如下:

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)
}

这很好但我认为这不是一个非常优雅的解决方案。鉴于Swift有一些简洁的功能,如 reduce 过滤器 map ,我想知道如果不明确使用循环我是否能做同样的工作。

感谢您的帮助!

3 个答案:

答案 0 :(得分:4)

使用想法和字典扩展

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))
    }
}

您可以通过

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

答案 1 :(得分:2)

Martin R的答案很好,您应该使用它并接受他的答案:-),但作为思考的替代方案:

在理想的世界中,Swift标准库将具有:

  • 从2元组数组中初始化Dictionary的能力
  • 除Zip2之外的Zip3(即取3个序列并将它们连接成3元组序列
  • zipWith的一个实现(即类似于Zip3,但不是将它们组合成对,而是在给定的元组上运行一个函数将它们组合在一起)。

如果你拥有所有这些,你可以写下以下内容:

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) }

这样做的好处是对粗糙的输入具有鲁棒性 - 它只会在输入中生成最短列表中的那些元素(不同于从输入的一个特定列表中获取计数的解决方案)。它的缺点是硬编码为3,但可以通过一个更通用的zipWith版本来修复,它采用了一系列序列(尽管你真的希望你的键是字符串,值是AnyObject不是你必须得到更好的字符串。

这些功能并不是你自己写的那么难 - 尽管为这种一次性的情况写作显然需要付出太多努力,但它们在多种情况下都很有用。如果您有兴趣,我会在this gist中完整实施。

答案 2 :(得分:1)

我会创建2个助手:

ZipArray(类似于Zip2,但可以使用任意长度):

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
    }
}

基本上,ZipArray翻转“Array Array”的,如:

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

为:

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

字典扩展名:

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
        }
    }
}

然后你可以:

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))
}