Swift:结合过滤器,映射到字典

时间:2017-10-22 11:33:47

标签: arrays swift sorting dictionary filter

我一直很头疼将两个数组合并为一个。
数组的连接就像是level.Id = rooms.level
感谢所有的想法!
所以,这是我的2个词典数组:

let levels = [
  ["Name":"Floor 1", "Order":0, "Id": 1],
  ["Name":"Floor 2", "Order":1, "Id": 2],
  ["Name":"Outdoor", "Order":2, "Id": 3]]
let rooms = [
  ["Name":"Master bedroom", "Order":2, "Id": 3, "Level": 2],
  ["Name":"Guest bedroom", "Order":1, "Id": 4, "Level": 2],
  ["Name":"Kitchen", "Order":0, "Id": 1, "Level": 1],
  ["Name":"Boiler", "Order":0, "Id": 2, "Level": 3]]

结果应该是这样的,按两个数组中的“Order”值排序:

["Floor 1" : ["Kitchen"],
 "Floor 2" : ["Guest bedroom", "Master bedroom"],
 "Outdoor" : ["Boiler"]] 

这就是我制作这个阵列的方式,但它看起来很难看:

func replaceIdWithName(id : Int)->String {
    return rooms.first(where: { $0["Id"] as? Int == id })!["Name"] as! String
}
func getLevelId(id : Int)->String {
    return (levels.first(where: { $0["Id"] as? Int == rooms.first(where: { $0["Id"] as? Int == id })?["Level"] as? Int })?["Name"]) as! String
}
var arr : [String : [Any]] = Dictionary(grouping: rooms.map { $0["Id"] as! Int }, by: { getLevelId(id: $0) })
arr.forEach{ arr[$0.0] = $0.1.map { replaceIdWithName(id: $0 as! Int) } }

之前,我已经通过这个函数对两个数组进行了排序:

func sortLevels(p1:[String:Any], p2: [String:Any])->Bool {
    let i = p1["Order"] as? Int
    let j = p2["Order"] as? Int
    if i != nil && j != nil {
        return i! < j!
    } else {
        return false
    }
}

2 个答案:

答案 0 :(得分:0)

struct Level {
    let name: String
    let order: Int
    let id: Int
}

struct Room {
    let name: String
    let order: Int
    let id: Int
    let level: Int
}

首先,您需要进行一些映射以便于工作

let mappedLevels = levels.flatMap { level -> Level? in
    guard let name = level["Name"] as? String, let id = level["Id"] as? Int, let order = level["Order"] as? Int else { return nil }

    return Level(name: name, order: order, id: id)
}

这个levelIdMappings稍后将用于更轻松的工作

let levelIdMappings = mappedLevels.reduce(Dictionary<Int, Level>()) { (result, level) -> Dictionary<Int, Level> in
    var mutableResult = result
    mutableResult[level.id] = level

    return mutableResult
}

还做房间映射

let mappedRooms = rooms.flatMap { room -> Room? in
    guard let name = room["Name"] as? String, let id = room["Id"] as? Int, let order = room["Order"] as? Int, let level = room["Level"] as? Int else { return nil }

    return Room(name: name, order: order, id: id, level: level)
}

现在只过滤您感兴趣的关卡并填充levelRoomsMapping(我可以使用reduce执行此操作)

var levelRoomsMapping = Dictionary<Int, [Room]>()

mappedRooms.filter { levelIdMappings.keys.contains($0.level) }.forEach {
    if var rooms = levelRoomsMapping[$0.level] {
        rooms.append($0)
        levelRoomsMapping[$0.level] = rooms
    } else {
        levelRoomsMapping[$0.level] = [$0]
    }
}

现在只过滤您需要的信息,并创建最终字典

let finalResult = levelRoomsMapping.reduce(Dictionary<String,[String]>()) { (dictionary, next) -> Dictionary<String,[String]> in
    guard let key = levelIdMappings[next.key]?.name else { return dictionary }

    var mutableDictionary = dictionary
    mutableDictionary[key] = next.value.map {
        $0.name
    }

    return mutableDictionary
}

您可以将其粘贴到游乐场并打印任何您喜欢的内容以获得更好的洞察力

答案 1 :(得分:0)

直接使用字典数组将成为一种类型的噩梦。但是如果你想要做到这一点,Swift 4的新词典初始化程序可以提供帮助。

以下是一个例子:

let levelNames = [Int:String](uniqueKeysWithValues:levels.map{($0["Id"]! as! Int, $0["Name"] as! String)})
let roomLevels = rooms.map{( levelNames[$0["Level"]! as! Int]! , [$0["Name"]! as! String])}
let levelRooms = [String:[String]](roomLevels, uniquingKeysWith:+)

print(levelRooms)
// ["Floor 1": ["Kitchen"], "Floor 2": ["Master bedroom", "Guest bedroom"], "Outdoor": ["Boiler"]]

levelNames变量将充当从级别Id获取级别名称的索引。

roomLevels变量是一个房间名称数组,其各自的级别名称是使用levelNames变量获得的。请注意,房间名称将作为单个元素数组返回,以使下一步更容易。

levelRooms是使用uniquingKeysWith选项初始化的最终字典,它将组合具有相同键(级别名称)的条目的值(房间名称)。因为房间名称是作为单个元素数组返回的,所以我们可以简单地使用+运算符作为组合函数,它将对数组名称的多元素数组执行数组添加。

[编辑]请注意,您可以隐藏大部分类型转换,同时仍然使用字典数组作为数据结构,方法是创建函数以按键名访问值。

struct Level
{
   static func Id(_ dict:[String:Any])    -> Int    { return dict["Id"]    as? Int    ?? 0 }
   static func Name(_ dict:[String:Any])  -> String { return dict["Name"]  as? String ?? ""}
   static func Order(_ dict:[String:Any]) -> Int    { return dict["Order"] as? Int    ?? 0 }
}

struct Room
{
   static func Id(_ dict:[String:Any])    -> Int    { return dict["Id"]    as? Int    ?? 0 }
   static func Name(_ dict:[String:Any])  -> String { return dict["Name"]  as? String ?? ""}
   static func Level(_ dict:[String:Any]) -> Int    { return dict["Level"] as? Int    ?? 0 }
   static func Order(_ dict:[String:Any]) -> Int    { return dict["Order"] as? Int    ?? 0 }
}

上面的解决方案将更具可读性,您可以获得不在代码中重复字符串文字中的键名称的附加好处

let levelNames = [Int:String](uniqueKeysWithValues:levels.map{(Level.Id($0), Level.Name($0))})
let roomLevels = rooms.map{( levelNames[Room.Level($0)]! , [Room.Name($0)])}
let levelRooms = [String:[String]](roomLevels, uniquingKeysWith:+)