将对象数组映射到 Swift 中的字典
Posted
技术标签:
【中文标题】将对象数组映射到 Swift 中的字典【英文标题】:Map array of objects to Dictionary in Swift 【发布时间】:2016-11-22 03:17:43 【问题描述】:我有一组Person
的对象:
class Person
let name:String
let position:Int
数组是:
let myArray = [p1,p1,p3]
我想将myArray
映射为[position:name]
的字典,经典的解决方案是:
var myDictionary = [Int:String]()
for person in myArray
myDictionary[person.position] = person.name
Swift 有什么优雅的方法可以通过函数式方法map
、flatMap
... 或其他现代 Swift 风格来做到这一点
【问题讨论】:
游戏有点晚了,但是您想要[position:name]
或[position:[name]]
的字典吗?如果您有两个人处于同一位置,则您的字典将只保留循环中遇到的最后一个....我有一个类似的问题,我正在尝试寻找解决方案,但我希望结果像[1: [p1, p3], 2: [p2]]
它们是一个内置函数。见here。基本上是Dictionary(uniqueKeysWithValues: array.map ($0.key, $0) )
【参考方案1】:
基于 KeyPath 的解决方案怎么样?
extension Array
func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value]
reduce(into: [:]) dictionary, element in
let key = element[keyPath: key]
let value = element[keyPath: value]
dictionary[key] = value
这是你使用它的方式:
struct HTTPHeader
let field: String, value: String
let headers = [
HTTPHeader(field: "Accept", value: "application/json"),
HTTPHeader(field: "User-Agent", value: "Safari")
]
headers.dictionary(withKey: \.field, value: \.value) // ["Accept": "application/json", "User-Agent": "Safari"]
【讨论】:
【参考方案2】:extension Array
func toDictionary() -> [Int: Element]
self.enumerated().reduce(into: [Int: Element]()) $0[$1.offset] = $1.element
【讨论】:
【参考方案3】:由于Swift 4
,您可以使用into
版本的reduce
更简洁有效地执行@Tj3n 的方法,它摆脱了临时字典和返回值,因此它更快更容易阅读。
示例代码设置:
struct Person
let name: String
let position: Int
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]
Into
参数传递的是结果类型的空字典:
let myDict = myArray.reduce(into: [Int: String]())
$0[$1.position] = $1.name
直接返回into
传入的类型的字典:
print(myDict) // [2: "c", 0: "h", 4: "b"]
【讨论】:
我有点困惑,为什么它似乎只适用于位置 ($0, $1) 参数而不是我命名它们时。编辑:没关系,编译器可能出错了,因为我仍然试图返回结果。 这个答案中不清楚的是$0
代表什么:它是我们“返回”的inout
字典——虽然不是真的返回,因为修改它相当于返回到期给它inout
-ness。【参考方案4】:
从 Swift 4 开始,您可以非常轻松地做到这一点。有 two new 初始化器从元组序列(键和值对)构建字典。如果保证密钥是唯一的,您可以执行以下操作:
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]
Dictionary(uniqueKeysWithValues: persons.map ($0.position, $0.name) )
=> [1: "Franz", 2: "Heinz", 3: "Hans"]
如果任何键重复,这将失败并出现运行时错误。在这种情况下,您可以使用此版本:
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 1)]
Dictionary(persons.map ($0.position, $0.name) ) _, last in last
=> [1: "Hans", 2: "Heinz"]
这就像你的 for 循环。如果您不想“覆盖”值并坚持第一个映射,则可以使用:
Dictionary(persons.map ($0.position, $0.name) ) first, _ in first
=> [1: "Franz", 2: "Heinz"]
Swift 4.2 添加了 third 初始化器,将序列元素分组到字典中。字典键是由闭包派生的。具有相同键的元素按照与序列中相同的顺序放入数组中。这使您可以实现与上述类似的结果。例如:
Dictionary(grouping: persons, by: $0.position ).mapValues $0.last!
=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]
Dictionary(grouping: persons, by: $0.position ).mapValues $0.first!
=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]
【讨论】:
事实上,根据 docs 'grouping' 初始值设定项会创建以 array 为值的字典(这是 'grouping' 的意思,对吗?),在上述情况下,它将是更像[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]
。不是预期的结果,但如果您愿意,它可以进一步映射到平面字典。
尤里卡!这就是我一直在寻找的机器人!这是完美的,并且大大简化了我的代码。
指向初始化程序的死链接。能否请您更新以使用永久链接?
@MarqueIV 我修复了损坏的链接。不确定您在这种情况下使用 permalink 指的是什么?
永久链接是网站使用的永远不会改变的链接。因此,例如,大多数网站不会像“www.SomeSite.com/events/today/item1.htm”那样每天都在变化,而是会设置第二个“永久链接”,比如“www.SomeSite.com?eventid= 12345' 永远不会改变,因此在链接中使用是安全的。不是每个人都这样做,但大网站的大多数页面都会提供。【参考方案5】:
好吧,map
不是一个很好的例子,因为它和循环一样,你可以使用 reduce
代替,它需要你的每个对象组合并变成单个值:
let myDictionary = myArray.reduce([Int: String]()) (dict, person) -> [Int: String] in
var dict = dict
dict[person.position] = person.name
return dict
//[2: "b", 3: "c", 1: "a"]
在 Swift 4 或更高版本中,请使用以下答案以获得更清晰的语法。
【讨论】:
我喜欢这种方法,但这比简单地创建一个循环来加载字典更有效吗?我担心性能,因为似乎每个项目都必须为更大的字典创建一个新的可变副本...... 其实是一个循环,这种方式是简化的写法,性能和普通循环一样或者好一些 Kendall,请参阅下面的答案,使用 Swift 4 时它可能比循环更快。虽然,我还没有对其进行基准测试以确认这一点。 不要使用这个!!!正如@KendallHelmstetterGelner 指出的那样,这种方法的问题在于每次迭代,它都在复制当前状态下的整个字典,所以在第一个循环中,它正在复制一个单项字典。第二次迭代它正在复制一个双项字典。这是一个巨大的性能消耗!更好的方法是在字典上编写一个方便的 init ,它接受你的数组并在其中添加所有项目。这样,它对于消费者来说仍然是单行的,但它消除了整个分配混乱。另外,您可以根据数组进行预分配。 我认为性能不会成为问题。 Swift 编译器将识别出字典的任何旧副本都不会被使用,甚至可能不会创建它们。记住优化自己代码的第一条规则:“不要这样做”。计算机科学的另一个格言是,高效算法仅在 N 很大时才有用,而 N 几乎永远不会很大。【参考方案6】:也许是这样的?
myArray.forEach( myDictionary[$0.position] = $0.name )
【讨论】:
【参考方案7】:extension Array
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable
var map = [T: Element]()
self.forEach map[block($0)] = $0
return map
【讨论】:
【参考方案8】:这是我一直在使用的
struct Person
let name:String
let position:Int
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]
var peopleByPosition = [Int: Person]()
persons.forEachpeopleByPosition[$0.position] = $0
如果有办法将最后两行合并起来,这样peopleByPosition
可以成为let
,那就太好了。
我们可以对 Array 做一个扩展来做到这一点!
extension Array
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable
var map = [T: Element]()
self.forEach map[block($0)] = $0
return map
那我们就可以了
let peopleByPosition = persons.mapToDict(by: $0.position)
【讨论】:
【参考方案9】:您可以使用 reduce 函数。首先我为 Person 类创建了一个指定的初始化器
class Person
var name:String
var position:Int
init(_ n: String,_ p: Int)
name = n
position = p
后来,我初始化了一个值数组
let myArray = [Person("Bill",1),
Person("Steve", 2),
Person("Woz", 3)]
最后,字典变量有结果:
let dictionary = myArray.reduce([Int: Person]())
(total, person) in
var totalMutable = total
totalMutable.updateValue(person, forKey: total.count)
return totalMutable
【讨论】:
【参考方案10】:您可以为Dictionary
类型编写自定义初始化程序,例如来自元组:
extension Dictionary
public init(keyValuePairs: [(Key, Value)])
self.init()
for pair in keyValuePairs
self[pair.0] = pair.1
然后将map
用于您的Person
数组:
var myDictionary = Dictionary(keyValuePairs: myArray.map($0.position, $0.name))
【讨论】:
以上是关于将对象数组映射到 Swift 中的字典的主要内容,如果未能解决你的问题,请参考以下文章