在 Swift 中如何按数组的元素进行分组

假设我有这个密码:

class Stat {
var statEvents : [StatEvents] = []
}


struct StatEvents {
var name: String
var date: String
var hours: Int
}




var currentStat = Stat()


currentStat.statEvents = [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
]


var filteredArray1 : [StatEvents] = []
var filteredArray2 : [StatEvents] = []

我可以多次手动调用下一个函数,以便将2个数组按“同一名称”分组。

filteredArray1 = currentStat.statEvents.filter({$0.name == "dinner"})
filteredArray2 = currentStat.statEvents.filter({$0.name == "lunch"})

问题是我不知道变量的值,在这里是“晚餐”和“午餐”,所以我想按名称自动对 statEvents 数组进行分组,所以我得到的数组与名称不同的数组一样多。

我怎么能这么做?

96737 次浏览

迅捷4:

从 Swift 4开始,这个功能就是 添加到标准库中。你可以这样使用它:

Dictionary(grouping: statEvents, by: { $0.name })
[
"dinner": [
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
],
"lunch": [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1)
]

斯威夫特3:

public extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
var categories: [U: [Iterator.Element]] = [:]
for element in self {
let key = key(element)
if case nil = categories[key]?.append(element) {
categories[key] = [element]
}
}
return categories
}
}

遗憾的是,上面的 append函数复制底层数组,而不是在适当的位置对其进行变异,这样会更好。这会导致经济大幅放缓.您可以通过使用引用类型包装器来解决这个问题:

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

即使您遍历最终的字典两次,这个版本在大多数情况下仍然比原始版本快。

斯威夫特2:

public extension SequenceType {
  

/// Categorises elements of self into a dictionary, with the keys given by keyFunc
  

func categorise<U : Hashable>(@noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
var dict: [U:[Generator.Element]] = [:]
for el in self {
let key = keyFunc(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}

在您的情况下,您可以将 keyFunc返回的“密钥”作为名称:

currentStat.statEvents.categorise { $0.name }
[
dinner: [
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
], lunch: [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1)
]
]

所以您将得到一个 dictionary,其中每个键都是一个名称,每个值都是一个具有该名称的 StatEvents 数组。

Swift 1

func categorise<S : SequenceType, U : Hashable>(seq: S, @noescape keyFunc: S.Generator.Element -> U) -> [U:[S.Generator.Element]] {
var dict: [U:[S.Generator.Element]] = [:]
for el in seq {
let key = keyFunc(el)
dict[key] = (dict[key] ?? []) + [el]
}
return dict
}


categorise(currentStat.statEvents) { $0.name }

结果是:

extension StatEvents : Printable {
var description: String {
return "\(self.name): \(self.date)"
}
}
print(categorise(currentStat.statEvents) { $0.name })
[
dinner: [
dinner: 01-01-2015,
dinner: 01-01-2015,
dinner: 01-01-2015
], lunch: [
lunch: 01-01-2015,
lunch: 01-01-2015
]
]

(飞毛腿是 给你)

学学 “ oisdk”的例子。 将解决方案扩展为基于类名 演示和源代码链接对对象进行分组。

基于类名进行分组的代码片段:

 func categorise<S : SequenceType>(seq: S) -> [String:[S.Generator.Element]] {
var dict: [String:[S.Generator.Element]] = [:]
for el in seq {
//Assigning Class Name as Key
let key = String(el).componentsSeparatedByString(".").last!
//Generating a dictionary based on key-- Class Names
dict[key] = (dict[key] ?? []) + [el]
}
return dict
}
//Grouping the Objects in Array using categorise
let categorised = categorise(currentStat)
print("Grouped Array :: \(categorised)")


//Key from the Array i.e, 0 here is Statt class type
let key_Statt:String = String(currentStat.objectAtIndex(0)).componentsSeparatedByString(".").last!
print("Search Key :: \(key_Statt)")


//Accessing Grouped Object using above class type key
let arr_Statt = categorised[key_Statt]
print("Array Retrieved:: ",arr_Statt)
print("Full Dump of Array::")
dump(arr_Statt)

对于斯威夫特3:

public extension Sequence {
func categorise<U : Hashable>(_ key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
var dict: [U:[Iterator.Element]] = [:]
for el in self {
let key = key(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}

用法:

currentStat.statEvents.categorise { $0.name }
[
dinner: [
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
], lunch: [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1)
]
]

扩展已接受的答案以允许 命令分组:

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

然后它将工作在任何 Tuple:

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

对于 Swift 5,Dictionary有一个名为 init(grouping:by:)的初始化器方法:

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

创建一个新字典,其中键是由给定闭包返回的分组,值是返回每个特定键的元素的数组。


下面的 Playground 代码演示了如何使用 init(grouping:by:)来解决您的问题:

struct StatEvents: CustomStringConvertible {
    

let name: String
let date: String
let hours: Int
    

var description: String {
return "Event: \(name) - \(date) - \(hours)"
}
    

}


let statEvents = [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
]


let dictionary = Dictionary(grouping: statEvents, by: { (element: StatEvents) in
return element.name
})
//let dictionary = Dictionary(grouping: statEvents) { $0.name } // also works
//let dictionary = Dictionary(grouping: statEvents, by: \.name) // also works


print(dictionary)
/*
prints:
[
"dinner": [Event: dinner - 01-01-2015 - 1, Event: dinner - 01-01-2015 - 1],
"lunch": [Event: lunch - 01-01-2015 - 1, Event: lunch - 01-01-2015 - 1]
]
*/

嘿,如果你需要保持秩序,而分组元素而不是散列字典我已经使用元组和保持列表的顺序,而分组。

extension Sequence
{
func zmGroup<U : Hashable>(by: (Element) -> U) -> [(U,[Element])]
{
var groupCategorized: [(U,[Element])] = []
for item in self {
let groupKey = by(item)
guard let index = groupCategorized.firstIndex(where: { $0.0 == groupKey }) else { groupCategorized.append((groupKey, [item])); continue }
groupCategorized[index].1.append(item)
}
return groupCategorized
}
}

下面是我使用 Swift 4 钥匙路径作为组比较器时保持秩序的基于元组的方法:

extension Sequence{


func group<T:Comparable>(by:KeyPath<Element,T>) -> [(key:T,values:[Element])]{


return self.reduce([]){(accumulator, element) in


var accumulator = accumulator
var result :(key:T,values:[Element]) = accumulator.first(where:{ $0.key == element[keyPath:by]}) ?? (key: element[keyPath:by], values:[])
result.values.append(element)
if let index = accumulator.index(where: { $0.key == element[keyPath: by]}){
accumulator.remove(at: index)
}
accumulator.append(result)


return accumulator
}
}
}

如何使用它的例子:

struct Company{
let name : String
let type : String
}


struct Employee{
let name : String
let surname : String
let company: Company
}


let employees : [Employee] = [...]
let companies : [Company] = [...]


employees.group(by: \Employee.company.type) // or
employees.group(by: \Employee.surname) // or
companies.group(by: \Company.type)

Swift 4: 您可以使用 苹果开发者网站中的 Init (分组: by:)

例子 :

let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { $0.first! })
// ["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]

对你来说

   let dictionary = Dictionary(grouping: currentStat.statEvents, by:  { $0.name! })

在 Swift 4中,这个扩展具有最好的性能和帮助链接您的操作员

extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
return Dictionary.init(grouping: self, by: key)
}
}

例如:

struct Asset {
let coin: String
let amount: Int
}


let assets = [
Asset(coin: "BTC", amount: 12),
Asset(coin: "ETH", amount: 15),
Asset(coin: "BTC", amount: 30),
]
let grouped = assets.group(by: { $0.coin })

创建:

[
"ETH": [
Asset(coin: "ETH", amount: 15)
],
"BTC": [
Asset(coin: "BTC", amount: 12),
Asset(coin: "BTC", amount: 30)
]
]

Swift 4

struct Foo {
let fizz: String
let buzz: Int
}


let foos: [Foo] = [Foo(fizz: "a", buzz: 1),
Foo(fizz: "b", buzz: 2),
Foo(fizz: "a", buzz: 3),
]
// use foos.lazy.map instead of foos.map to avoid allocating an
// intermediate Array. We assume the Dictionary simply needs the
// mapped values and not an actual Array
let foosByFizz: [String: Foo] =
Dictionary(foos.lazy.map({ ($0.fizz, $0)},
uniquingKeysWith: { (lhs: Foo, rhs: Foo) in
// Arbitrary business logic to pick a Foo from
// two that have duplicate fizz-es
return lhs.buzz > rhs.buzz ? lhs : rhs
})
// We don't need a uniquing closure for buzz because we know our buzzes are unique
let foosByBuzz: [String: Foo] =
Dictionary(uniqueKeysWithValues: foos.lazy.map({ ($0.buzz, $0)})

这个字典(分组: arr)太简单了!

 func groupArr(arr: [PendingCamera]) {


let groupDic = Dictionary(grouping: arr) { (pendingCamera) -> DateComponents in
print("group arr: \(String(describing: pendingCamera.date))")


let date = Calendar.current.dateComponents([.day, .year, .month], from: (pendingCamera.date)!)


return date
}


var cams = [[PendingCamera]]()


groupDic.keys.forEach { (key) in
print(key)
let values = groupDic[key]
print(values ?? "")


cams.append(values ?? [])
}
print(" cams are \(cams)")


self.groupdArr = cams
}

也可以按 KeyPath分组,如下所示:

public extension Sequence {
func group<Key>(by keyPath: KeyPath<Element, Key>) -> [Key: [Element]] where Key: Hashable {
return Dictionary(grouping: self, by: {
$0[keyPath: keyPath]
})
}
}

使用@duan 的加密例子:

struct Asset {
let coin: String
let amount: Int
}


let assets = [
Asset(coin: "BTC", amount: 12),
Asset(coin: "ETH", amount: 15),
Asset(coin: "BTC", amount: 30),
]

用法如下:

let grouped = assets.group(by: \.coin)

同样的结果:

[
"ETH": [
Asset(coin: "ETH", amount: 15)
],
"BTC": [
Asset(coin: "BTC", amount: 12),
Asset(coin: "BTC", amount: 30)
]
]

用我的方式

extension Array {
func group<T: Hashable>(by key: (_ element: Element) -> T) -> [[Element]] {
var categories: [T: [Element]] = [:]
for element in self {
let key = key(element)
categories[key, default: []].append(element)
}
return categories.values.map { $0 }
}
}

你可以用 reduce

let result = currentStat.statEvents.reduce([String:[StatEvents]](), {
var previous = $0
previous[$1.name] = (previous[$1.name] ?? []) + [$1]
return previous
})


let filteredArray1 = result["lunch"]
let filteredArray2 = result["dinner"]

或者你也可以把它改成

let result = currentStat.statEvents.reduce(["lunch":[StatEvents](), "dinner":[StatEvents]()], {
var previous = $0
if let array = previous[$1.name] {
previous[$1.name] = array + [$1]
}
return previous
})


let filteredArray1 = result["lunch"]
let filteredArray2 = result["dinner"]

只能找到午餐和晚餐

基于 这个,我这样做:

func filterItems() -> Dictionary<Int, [YourDataType]> {
return Dictionary(grouping: yourDataTypeVar, by: { (element: YourDataType) in
return item.name
})
}

可以使用 https://github.com/apple/swift-collections中的 OrderedDictionary创建具有 命令键的字典。

import OrderedCollections


OrderedDictionary(grouping: statEvents, by: \.name)

Dictionary创建带有 无序键的 Dictionary。

Dictionary(grouping: statEvents, by: \.name)