【问题标题】:Swift: combine filter, map to dictionarySwift:组合过滤器,映射到字典
【发布时间】:2017-10-22 11:33:46
【问题描述】:

我一直为将两个数组合二为一而头疼。
数组的连接就像levels.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
    }
}

【问题讨论】:

  • 你试过什么?本网站并非旨在成为“请为我的家庭作业编码”的地方。
  • 我已经尝试了数百个选项,但没有一个选项能完全按照我需要的方式工作。我无意为我编写代码。但由于这是我在这里的第一篇文章,我可能会错过我不得不发布我愚蠢的尝试......

标签: arrays swift sorting dictionary filter


【解决方案1】:

直接使用字典数组将是一场类型转换的噩梦。但是,如果您仍然想这样做,Swift 4 的新 Dictionary 初始化程序可以提供帮助。

这是一个例子:

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:+)

【讨论】:

  • 这太棒了。我无法想象处理两个普通数组需要这么多代码,但是你写的东西给了我很好的开始。非常感谢。
【解决方案2】:
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)
}

这一层IdMappings 稍后会使用,以便于工作

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
}

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

【讨论】:

  • 当然可能会以更短的方式完成,但我想它的可读性会降低
  • 非常感谢!我已经完成了,我将在下面展示它,但它看起来很丑:)
  • 完全没有,我忘了排序(没发现)。无论如何,如果您像我一样将数组映射到结构,您可能会得到更漂亮的代码。此外,更容易适应密钥的更改,因为您只需在一个地方进行更改。另外,尽可能不要使用 Any,如果可以使用 [String] 就使用它。
猜你喜欢
  • 2020-10-11
  • 1970-01-01
  • 2018-07-18
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 2016-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多