Arrays 按某个属性对数组的元素进行分组

Arrays 按某个属性对数组的元素进行分组,arrays,swift,data-structures,grouping,Arrays,Swift,Data Structures,Grouping,我有一个属性为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

我有一个属性为
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")]]
我想要的是创建数组数组,其中每个数组将包含具有相同日期的对象

我知道,我需要像
.filter
这样的东西来过滤对象,然后
.map
将所有东西添加到数组中


但是如何告诉
.map
我希望每个组都有一个独立的数组,并且必须将该数组添加到“全局”数组中,以及如何告诉
.filter
我希望对象具有相同的日期

抽象一步,您需要的是按某个属性对数组的元素进行分组。您可以让地图为您进行分组,如下所示:

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

拉斐尔的解决方案确实有效。然而,我建议修改解决方案,以支持分组实际上是稳定的这一主张

现在,调用
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
    }
}
现在我们跟踪发现新分组的顺序,我们可以返回一个分组数组,这比仅仅依赖字典的无序
属性更加一致

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")]]

改进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]! }
    }
}

可能已经晚了,但是新的Xcode 9 sdk字典有了新的init方法

init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
结果将是:

["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]

+1到GolenKovkosty回答

init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
输出:

[2017-01-01: [Foo, Bar], 2017-02-01:  [FooBar] ]

使用Swift 5,您可以使用
字典
的初始值设定项将数组的元素按其属性之一分组到字典中。完成后,可以使用
字典
的属性和
数组
初始值设定项从字典创建数组数组


以下示例代码显示了如何按一个属性将数组的元素分组到新的数组中:

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)]
 ]
 */

这是一种通过以下方式执行分组的干净方法:

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
    }
}
类框{
var值:A
初始值(val:A){
self.value=val
}
}
公共扩展序列{
func组(按键:(Iterator.Element)->U)->[U:[Iterator.Element]]{
变量类别:[U:Box]=[:]
自我元素{
让键=键(元素)
如果case nil=categories[key]?.value.append(元素){
类别[键]=框([元素])
}
}
变量结果:[U:[Iterator.Element]]=字典(最小容量:categories.count)
用于类别中的(键、值){
结果[键]=值
}
返回结果
}
}

FWIW,扩展名中的
typealias
应该不是必需的,但是如果没有它,编译器将无法推断
组的类型。()可能重复我已更新此项以支持可选分组键。grouped函数现在接受一个Bool参数,该参数指定是将nil分组到单个组中,还是在找到nil时将其单独分组,后者是默认值(false)。要点很好!这是该功能的自然进展。我已经更新了解决方案,允许使用可选的分组键。此功能在您的解决方案中也很方便。
[2017-01-01: [Foo, Bar], 2017-02-01:  [FooBar] ]
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)]
 ]
 */
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
    }
}