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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 06:43:53  来源:igfitidea点击:

How to group by the elements of an array in Swift

iosarraysswiftnsarray

提问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 appendfunction 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 keyFuncbe 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, Dictionaryhas 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.

Here is my tuple based approach for keeping order while using Swift 4 KeyPath'sas group comparator:

这是我在使用 Swift 4 KeyPath作为组比较器时保持顺序的基于元组的方法:

##代码##

Example of how to use it:

如何使用它的示例:

##代码##

回答by ironRoei

Thr Dictionary(grouping: arr) is so easy!

Thr Dictionary(分组:arr)就是这么简单!

##代码##