【问题标题】:How to group by the elements of an array in Swift如何在 Swift 中按数组的元素进行分组
【发布时间】:2015-09-22 01:46:58
【问题描述】:

假设我有这个代码:

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 数组进行分组,所以我得到的数组与名称一样多变得不一样了。

我该怎么做?

【问题讨论】:

标签: ios arrays swift nsarray


【解决方案1】:

我的方式

extension Array {
    func group<T: Hashable>(by key: (_ element: Element) -> T) -> [[Element]] {
        var categories: [T: [Element]] = [:]
        var groups = [[Element]]()
        for element in self {
            let key = key(element)
            if case nil = categories[key]?.append(element) {
                categories[key] = [element]
            }
        }
        categories.keys.forEach { key in
            if let group = categories[key] {
                groups.append(group)
            }
        }
        return groups
    }
}

【讨论】:

    【解决方案2】:

    在 Swift 5 中,Dictionary 有一个名为 init(grouping:by:) 的初始化方法。 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]
    ]
    */
    

    【讨论】:

    • 好一个,你能否也包括它也可以写成let dictionary = Dictionary(grouping: statEvents) { $0.name } - 语法糖涂层
    • 这应该是从 swift 4 开始的答案 - 苹果完全支持,并希望具有高性能。
    • 还要注意谓词中返回的非可选键,否则你会看到错误:"type of expression is ambiguous without more context..."
    • @user1046037 Swift 5.2 Dictionary(grouping: statEvents, by: \.name)
    【解决方案3】:

    斯威夫特 4:

    自 Swift 4 以来,此功能一直是 added to the standard library。你可以这样使用它:

    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 函数复制了底层数组,而不是在原地改变它,这将是更可取的。 This causes a pretty big slowdown。你可以通过使用引用类型包装器来解决这个问题:

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

    所以你会得到一个字典,其中每个键都是一个名称,每个值都是具有该名称的 StatEvents 数组。

    斯威夫特 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
      ]
    ]
    

    (swiftstub 是here

    【讨论】:

    • 非常感谢@oisdk!你知道是否有一种方法可以访问所创建的字典值的索引吗?我的意思是,我知道如何获取键和值,但我想获取这些字典的索引“0”、“1”、“2”...
    • 因此,如果您想说出字典中的三个“晚餐”值,您可以选择dict[key],(在我的第一个示例中,它是ans["dinner"])。如果您想要这三个事物本身的索引,则类似于enumerate(ans["dinner"]),或者,如果您想通过索引访问,您可以这样做:ans["dinner"]?[0],这将将存储在dinner 下的数组的第一个元素返回给您。
    • 它总是给我零回报
    • 这是一个解决方案的良好开端,但也有一些不足之处。此处使用模式匹配 (if case) 是不必要的,但更重要的是,使用 dict[key]?.append) 附加到存储在字典中的内容会导致每次都发生副本。见rosslebeau.com/2016/…
    • 斯威夫特 5.2 Dictionary(grouping: currentStat.statEvents, by: \.name)
    【解决方案4】:

    您也可以按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)
        ]
    ]
    

    【讨论】:

    • 您可以传递谓词而不是键路径 func grouped&lt;Key: Hashable&gt;(by keyForValue: (Element) -&gt; Key) -&gt; [Key: [Element]] { .init(grouping: self, by: keyForValue) } 这将允许您调用 assets.grouped(by: \.coin)assets.grouped { $0.coin }
    【解决方案5】:

    Thr Dictionary(grouping: 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
    }
    

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 你能写一个使用例子吗?
      • @duan 是否可以忽略大小写,例如 BTC 和 btc 应该算作相同...
      • @MoinShirazi assets.group(by: { $0.coin.uppercased() }),但最好先映射然后分组
      【解决方案7】:

      斯威夫特 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)})
      

      【讨论】:

        【解决方案8】:

        嘿,如果您需要在分组元素而不是哈希字典时保持顺序,我使用元组并在分组时保持列表的顺序。

        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: { $0.0 == groupKey }) else { groupCategorized.append((groupKey, [item])); continue }
                   groupCategorized[index].1.append(item)
               }
               return groupCategorized
           }
        }
        

        【讨论】:

          【解决方案9】:

          Swift 4:你可以使用来自apple developer siteinit(grouping: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! })
          

          【讨论】:

          • 这也适用于 keypath:let dictionary = Dictionary(grouping: currentStat.statEvents, by: \.name)
          【解决方案10】:

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

          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)
          

          【讨论】:

            【解决方案11】:

            扩展接受的答案以允许有序分组:

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

            以及任何 structclass

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

            【讨论】:

              【解决方案12】:

              对于 Swift 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)
                ]
              ]
              

              【讨论】:

              • 非常感谢您提供一个使用示例 :) 谢谢!
              • 这是一个使用示例:yourArray.categorise(currentStat.statEvents) { $0.name }。该函数将返回 Dictionary>
              【解决方案13】:

              "oisdk" example 中取出一片叶子。 将解决方案扩展到基于类名Demo & Source Code link 对对象进行分组。

              根据类名进行分组的代码sn-p:

               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)
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-08-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-02-24
                • 2012-03-24
                • 2017-03-18
                相关资源
                最近更新 更多