ios 如何在 Swift 中按数组的元素分组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31220002/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to group by the elements of an array in Swift
提问by Ruben
Let's say that I have this code:
假设我有这个代码:
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] = []
I could call as many times manually the next function in order to have 2 arrays grouped by "same name".
我可以手动多次调用下一个函数,以便将 2 个数组按“同名”分组。
filteredArray1 = currentStat.statEvents.filter({Dictionary(grouping: statEvents, by: { 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
}
}
.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)
]
.name == "dinner"})
filteredArray2 = currentStat.statEvents.filter({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
}
}
.name == "lunch"})
The problem is that I won't know the variable value, in this case "dinner" and "lunch", so I would like to group this array of statEvents automatically by name, so I get as many arrays as the name gets different.
问题是我不知道变量值,在这种情况下是“晚餐”和“午餐”,所以我想按名称自动对这个 statEvents 数组进行分组,所以我得到尽可能多的数组,因为名称不同。
How could I do that?
我怎么能那样做?
回答by oisdk
Swift 4:
斯威夫特 4:
Since Swift 4, this functionality has been added to the standard library. You can use it like so:
自 Swift 4 起,此功能已添加到标准库中。你可以像这样使用它:
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
}
}
Swift 3:
斯威夫特 3:
currentStat.statEvents.categorise { 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) { extension StatEvents : Printable {
var description: String {
return "\(self.name): \(self.date)"
}
}
print(categorise(currentStat.statEvents) { init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
.name })
[
dinner: [
dinner: 01-01-2015,
dinner: 01-01-2015,
dinner: 01-01-2015
], lunch: [
lunch: 01-01-2015,
lunch: 01-01-2015
]
]
.name }
.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)
]
]
Unfortunately, the append
function above copies the underlying array, instead of mutating it in place, which would be preferable. This causes a pretty big slowdown. You can get around the problem by using a reference type wrapper:
不幸的是,append
上面的函数复制了底层数组,而不是就地改变它,这会更可取。这会导致相当大的放缓。您可以通过使用引用类型包装器来解决这个问题:
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) { let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { let dictionary = Dictionary(grouping: currentStat.statEvents, by: { 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
}
}
.name! })
.first! })
// ["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
.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]
]
*/
Even though you traverse the final dictionary twice, this version is still faster than the original in most cases.
即使您两次遍历最终字典,在大多数情况下,此版本仍然比原始字典快。
Swift 2:
斯威夫特 2:
currentStat.statEvents.categorise { extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
return Dictionary.init(grouping: self, by: key)
}
}
.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)
]
]
In your case, you could have the "keys" returned by keyFunc
be the names:
在您的情况下,您可以通过keyFunc
名称返回“键” :
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: { [
"ETH": [
Asset(coin: "ETH", amount: 15)
],
"BTC": [
Asset(coin: "BTC", amount: 12),
Asset(coin: "BTC", amount: 30)
]
]
.coin })
So you'll get a dictionary, where every key is a name, and every value is an array of the StatEvents with that name.
所以你会得到一个字典,其中每个键都是一个名称,每个值都是具有该名称的 StatEvents 数组。
Swift 1
斯威夫特 1
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({ (public extension Sequence {
func group<Key>(by keyPath: KeyPath<Element, Key>) -> [Key: [Element]] where Key: Hashable {
return Dictionary(grouping: self, by: {
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),
]
[keyPath: keyPath]
})
}
}
.fizz, let grouped = assets.group(by: \.coin)
)},
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({ ([
"ETH": [
Asset(coin: "ETH", amount: 15)
],
"BTC": [
Asset(coin: "BTC", amount: 12),
Asset(coin: "BTC", amount: 30)
]
]
.buzz, 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.index(where: { 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:{ 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)
.key == element[keyPath:by]}) ?? (key: element[keyPath:by], values:[])
result.values.append(element)
if let index = accumulator.index(where: { 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
}
.key == element[keyPath: by]}){
accumulator.remove(at: index)
}
accumulator.append(result)
return accumulator
}
}
}
.0 == groupKey }) else { groupCategorized.append((groupKey, [item])); continue }
groupCategorized[index].1.append(item)
}
return groupCategorized
}
}
)})
Which gives the output:
这给出了输出:
##代码##(The swiftstub is here)
(swiftstub 在这里)
回答by Imanou Petit
With Swift 5, Dictionary
has an initializer method called init(grouping:by:)
. init(grouping:by:)
has the following declaration:
在 Swift 5 中,Dictionary
有一个名为init(grouping:by:)
. init(grouping:by:)
有以下声明:
Creates a new dictionary where the keys are the groupings returned by the given closure and the values are arrays of the elements that returned each specific key.
创建一个新字典,其中键是给定闭包返回的分组,值是返回每个特定键的元素的数组。
The following Playground code shows how to use init(grouping:by:)
in order to solve your problem:
以下 Playground 代码显示了如何使用init(grouping:by:)
以解决您的问题:
回答by Mihuilk
Swift 4: you can use init(grouping:by:)from apple developer site
斯威夫特4:你可以使用的init(分组:由:)从 苹果开发者网站
Example:
示例:
##代码##So in your case
所以在你的情况下
##代码##回答by Michal Zaborowski
For Swift 3:
对于 Swift 3:
##代码##Usage:
用法:
##代码##回答by duan
In Swift 4, this extension has the best performance and help chain your operators
在 Swift 4 中,此扩展具有最佳性能并有助于链接您的运算符
##代码##Example:
例子:
##代码##creates:
创建:
##代码##回答by Heath Borders
Swift 4
斯威夫特 4
##代码##回答by Sajjon
You can also group by KeyPath
, like this:
您还可以按 分组KeyPath
,如下所示:
Using @duan's crypto example:
使用@duan 的加密示例:
##代码##Then usage looks like this:
然后用法如下所示:
##代码##Yielding the same result:
产生相同的结果:
##代码##回答by Suat KARAKUSOGLU
Hey if you need to keep order while grouping elements instead of hash dictionary i have used tuples and kept the order of the list while grouping.
嘿,如果你需要在对元素进行分组而不是哈希字典时保持顺序,我已经使用了元组并在分组时保持列表的顺序。
##代码##回答by Zell B.
回答by ironRoei
Thr Dictionary(grouping: arr) is so easy!
Thr Dictionary(分组:arr)就是这么简单!
##代码##