我有一个对象数组,其属性为date
。
我想要的是创建数组数组,其中每个数组将包含具有相同日期的对象。
我理解,我需要类似.filter
的内容来过滤对象,然后.map
将所有内容添加到数组中。
但是如何告诉.map
我希望每个组都有来自过滤对象的单独数组,并且必须将此数组添加到“全局”数组以及如何告诉.filter
我希望对象具有同一天?
答案 0 :(得分:12)
可能会迟到但新的Xcode 9 sdk字典有新的init方法
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
Documentation has simple example what this method does. 我只是在下面发布这个例子:
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { $0.first! })
结果将是:
["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
答案 1 :(得分:9)
改进oriyentel解决方案以允许对任何事物进行有序分组:
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
然后它适用于任何元组,结构或类以及任何属性:
let a = [(grouping: 10, content: "a"),
(grouping: 20, content: "b"),
(grouping: 10, content: "c")]
print(a.group { $0.grouping })
struct GroupInt {
var grouping: Int
var content: String
}
let b = [GroupInt(grouping: 10, content: "a"),
GroupInt(grouping: 20, content: "b"),
GroupInt(grouping: 10, content: "c")]
print(b.group { $0.grouping })
答案 2 :(得分:1)
抽象一步,你想要的是通过某个属性分组数组元素。您可以让地图为您进行分组:
protocol Groupable {
associatedtype GroupingType: Hashable
var grouping: GroupingType { get set }
}
extension Array where Element: Groupable {
typealias GroupingType = Element.GroupingType
func grouped() -> [[Element]] {
var groups = [GroupingType: [Element]]()
for element in self {
if let _ = groups[element.grouping] {
groups[element.grouping]!.append(element)
} else {
groups[element.grouping] = [element]
}
}
return Array<[Element]>(groups.values)
}
}
请注意,此分组是稳定,即组按外观顺序显示,并且在组内部,各个元素的显示顺序与原始数组中的顺序相同。
我将使用整数给出一个例子;应该清楚如何使用T
的任何(可填充)类型,包括Date
。
struct GroupInt: Groupable {
typealias GroupingType = Int
var grouping: Int
var content: String
}
var a = [GroupInt(grouping: 1, content: "a"),
GroupInt(grouping: 2, content: "b") ,
GroupInt(grouping: 1, content: "c")]
print(a.grouped())
// > [[GroupInt(grouping: 2, content: "b")], [GroupInt(grouping: 1, content: "a"), GroupInt(grouping: 1, content: "c")]]
答案 3 :(得分:1)
Rapheal的解决方案确实有效。但是,我建议改变解决方案以支持分组实际上是稳定的主张。
现在看来,调用grouped()
将返回一个分组数组,但后续调用可以返回一个包含不同顺序的数组的数组,尽管每个组的元素都是预期的顺序。
internal protocol Groupable {
associatedtype GroupingType : Hashable
var groupingKey : GroupingType? { get }
}
extension Array where Element : Groupable {
typealias GroupingType = Element.GroupingType
func grouped(nilsAsSingleGroup: Bool = false) -> [[Element]] {
var groups = [Int : [Element]]()
var groupsOrder = [Int]()
let nilGroupingKey = UUID().uuidString.hashValue
var nilGroup = [Element]()
for element in self {
// If it has a grouping key then use it. Otherwise, conditionally make one based on if nils get put in the same bucket or not
var groupingKey = element.groupingKey?.hashValue ?? UUID().uuidString.hashValue
if nilsAsSingleGroup, element.groupingKey == nil { groupingKey = nilGroupingKey }
// Group nils together
if nilsAsSingleGroup, element.groupingKey == nil {
nilGroup.append(element)
continue
}
// Place the element in the right bucket
if let _ = groups[groupingKey] {
groups[groupingKey]!.append(element)
} else {
// New key, track it
groups[groupingKey] = [element]
groupsOrder.append(groupingKey)
}
}
// Build our array of arrays from the dictionary of buckets
var grouped = groupsOrder.flatMap{ groups[$0] }
if nilsAsSingleGroup, !nilGroup.isEmpty { grouped.append(nilGroup) }
return grouped
}
}
现在我们跟踪发现新分组的顺序,我们可以更加一致地返回分组数组,而不仅仅依赖于Dictionary的无序values
属性。
struct GroupableInt: Groupable {
typealias GroupingType = Int
var grouping: Int?
var content: String
}
var a = [GroupableInt(groupingKey: 1, value: "test1"),
GroupableInt(groupingKey: 2, value: "test2"),
GroupableInt(groupingKey: 2, value: "test3"),
GroupableInt(groupingKey: nil, value: "test4"),
GroupableInt(groupingKey: 3, value: "test5"),
GroupableInt(groupingKey: 3, value: "test6"),
GroupableInt(groupingKey: nil, value: "test7")]
print(a.grouped())
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")],[GroupableInt(groupingKey: nil, value: "test7")]]
print(a.grouped(nilsAsSingleGroup: true))
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4"),GroupableInt(groupingKey: nil, value: "test7")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")]]
答案 4 :(得分:1)
+1 GolenKovkosty回答。
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
更多例子:
enum Parity {
case even, odd
init(_ value: Int) {
self = value % 2 == 0 ? .even : .odd
}
}
let parity = Dictionary(grouping: 0 ..< 10 , by: Parity.init )
等于
let parity2 = Dictionary(grouping: 0 ..< 10) { $0 % 2 }
在你的情况下:
struct Person : CustomStringConvertible {
let dateOfBirth : Date
let name :String
var description: String {
return "\(name)"
}
}
extension Date {
init(dateString:String) {
let formatter = DateFormatter()
formatter.timeZone = NSTimeZone.default
formatter.dateFormat = "MM/dd/yyyy"
self = formatter.date(from: dateString)!
}
}
let people = [Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Foo"),
Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Bar"),
Person(dateOfBirth:Date(dateString:"02/01/2017"),name:"FooBar")]
let parityFields = Dictionary(grouping: people) {$0.dateOfBirth}
输出:
[2017-01-01: [Foo, Bar], 2017-02-01: [FooBar] ]
答案 5 :(得分:0)
使用Swift 5,您可以使用Dictionary
的{{3}}初始值设定项将数组元素按其属性之一分组到字典中。完成后,您可以使用Dictionary
的{{3}}属性和Array
init(grouping:by:)
初始化程序从字典创建数组数组。
下面的Playground示例代码展示了如何通过一个属性将数组的元素分组为一个新的数组数组:
import Foundation
struct Purchase: CustomStringConvertible {
let id: Int
let date: Date
var description: String {
return "Purchase #\(id) (\(date))"
}
}
let date1 = Calendar.current.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let date2 = Calendar.current.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let date3 = Calendar.current.date(from: DateComponents(year: 2012, month: 8, day: 15))!
let purchases = [
Purchase(id: 1, date: date1),
Purchase(id: 2, date: date1),
Purchase(id: 3, date: date2),
Purchase(id: 4, date: date3),
Purchase(id: 5, date: date3)
]
let groupingDictionary = Dictionary(grouping: purchases, by: { $0.date })
print(groupingDictionary)
/*
[
2012-08-14 22:00:00 +0000: [Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
2010-11-21 23:00:00 +0000: [Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)],
2015-04-30 22:00:00 +0000: [Purchase #3 (2015-04-30 22:00:00 +0000)]
]
*/
let groupingArray = Array(groupingDictionary.values)
print(groupingArray)
/*
[
[Purchase #3 (2015-04-30 22:00:00 +0000)],
[Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
[Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)]
]
*/
答案 6 :(得分:0)
这是一种干净的分组方式:
let grouped = allRows.group(by: {$0.groupId}) // Dictionary with the key groupId
假设您有一系列联系人,例如:
class ContactPerson {
var groupId:String?
var name:String?
var contactRecords:[PhoneBookEntry] = []
}
要实现此目的,请添加此扩展程序:
class Box<A> {
var value: A
init(_ val: A) {
self.value = val
}
}
public extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U: [Iterator.Element]] {
var categories: [U: Box<[Iterator.Element]>] = [:]
for element in self {
let key = key(element)
if case nil = categories[key]?.value.append(element) {
categories[key] = Box([element])
}
}
var result: [U: [Iterator.Element]] = Dictionary(minimumCapacity: categories.count)
for (key, val) in categories {
result[key] = val.value
}
return result
}
}