如何在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] = []
我可以多次手动调用下一个函数,以便有两个数组按“同名”分组。
filteredArray1 = currentStat.statEvents.filter({$0.name == "dinner"}) filteredArray2 = currentStat.statEvents.filter({$0.name == "lunch"})
问题是我不知道variables的值,在这种情况下,“晚餐”和“午餐”,所以我想按名称自动将这个statEvents数组,所以我得到的名称变得不同的数组。
我怎么能这样做?
Swift 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
函数复制了底层数组,而不是将其改变,这将是可取的。 这导致相当大的放缓 。 您可以通过使用引用types包装来解决问题:
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 } }
即使你遍历最后的字典两次,在大多数情况下,这个版本仍然比原来的更快。
Swift 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
返回的“keys”作为名字:
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的数组。
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 ] ]
(这个swiftstub在这里 )
对于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) ] ]
使用Swift 4, Dictionary
有一个名为init(grouping:by:)
的初始化方法init(grouping:by:)
。 init(grouping:by:)
具有以下声明:
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
创build一个新的字典,其中键是给定闭包返回的分组,值是返回每个特定键的元素的数组。
下面的游乐场代码显示了如何使用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 predicate = { (element: StatEvents) in return element.name } let dictionary = Dictionary(grouping: statEvents, by: predicate) 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] ] */
Swift 4:你可以使用苹果开发者网站的 init(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! })
扩展接受的答案以允许有序分组:
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 })
以及任何结构或类 :
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 })
嘿,如果你需要保持秩序,而不是哈希字典分组元素,我已经使用元组,并保持列表的顺序,而分组。
extension Sequence { func zmGroup<U : Hashable>(by: (Element) -> U) -> [(U,[Element])] { var groupCategorized: [(U,[Element])] = [] for item in self { let groupKey = by(item) if var foundGroup = groupCategorized.filter({ $0.0 == groupKey }).first{ foundGroup.1.append(item) }else{ groupCategorized.append((groupKey, [item])) } } return groupCategorized } }
下面是我使用Swift 4 KeyPath作为组比较器时保持顺序的基于元组的方法:
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)
从“oisdk”的例子中走出一片叶子。 将解决scheme扩展到基于类名称Demo&Source Code链接的组对象。
基于类名称的分组代码片段:
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 ie, 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)