Comment grouper par les éléments d'un tableau dans Swift

Disons que j'ai ce code:

class Stat { var statEvents : [StatEvents] = [] } struct StatEvents { var name: Ssortingng var date: Ssortingng 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] = [] 

Je pourrais appeler autant de fois manuellement la fonction suivante afin d'avoir 2 arrays regroupés par "même nom".

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

Le problème est que je ne connaîtrai pas la valeur de la variable, dans ce cas "dîner" et "déjeuner", donc je voudrais regrouper automatiquement ce tableau de statEvents par nom, donc j'obtiens autant de arrays que le nom devient différent.

Comment pourrais-je faire ça?

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

    Malheureusement, la fonction append ci-dessus copy le tableau sous-jacent, au lieu de le muter en place, ce qui serait préférable. Cela provoque un gros ralentissement . Vous pouvez contourner le problème en utilisant un wrapper de type de reference:

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

    Même si vous parcourez le dictionary final deux fois, cette version est toujours plus rapide que l'original dans la plupart des cas.

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

    Dans votre cas, vous pourriez avoir les "keys" returnnées par keyFunc soit les noms:

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

    Ainsi, vous obtiendrez un dictionary, où chaque key est un nom, et chaque valeur est un tableau des StatEvents avec ce nom.

    La version Swift 1 serait:

     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 } 

    Ce qui donne la sortie:

     extension StatEvents : Printable { var description: Ssortingng { 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 ] ] 

    (Le swiftstub est ici )

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

    Usage:

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

    Avec Swift 4, Dictionary a une méthode d'initialisation appelée init(grouping:by:) . init(grouping:by:) a la déclaration suivante:

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

    Crée un nouveau dictionary dans lequel les keys sont les regroupements renvoyés par la fermeture donnée et les valeurs sont des arrays des éléments qui ont renvoyé chaque key spécifique.


    Le code Playground suivant montre comment utiliser init(grouping:by:) pour résoudre votre problème:

     struct StatEvents: CustomSsortingngConvertible { let name: Ssortingng let date: Ssortingng let hours: Int var description: Ssortingng { 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: vous pouvez utiliser init (grouping: by 🙂 depuis le site Apple Developer

    Exemple :

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

    Donc dans votre cas

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

    S'étendant sur une réponse acceptée pour permettre un regroupement ordonné :

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

    Ensuite, cela fonctionnera sur n'importe quel tuple :

     let a = [(grouping: 10, content: "a"), (grouping: 20, content: "b"), (grouping: 10, content: "c")] print(a.group { $0.grouping }) 

    Ainsi que toute structure ou class :

     struct GroupInt { var grouping: Int var content: Ssortingng } let b = [GroupInt(grouping: 10, content: "a"), GroupInt(grouping: 20, content: "b"), GroupInt(grouping: 10, content: "c")] print(b.group { $0.grouping }) 

    Hey si vous avez besoin de garder l'ordre en groupant des éléments à la place du dictionary de hachage, j'ai utilisé des tuples et j'ai gardé l'ordre de la list pendant le groupement.

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

    Voici mon approche en tuple pour garder l'ordre en utilisant Swift 4 KeyPath's comme comparateur de groupe:

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

    Exemple de comment l'utiliser:

     struct Company{ let name : Ssortingng let type : Ssortingng } struct Employee{ let name : Ssortingng let surname : Ssortingng 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) 

    Prendre une feuille sur l' exemple "oisdk" . Extension de la solution aux objects de groupe basés sur le lien de code de démonstration et de source de nom de class.

    Extrait de code pour le regroupement basé sur le nom de la class:

      func categorise<S : SequenceType>(seq: S) -> [Ssortingng:[S.Generator.Element]] { var dict: [Ssortingng:[S.Generator.Element]] = [:] for el in seq { //Assigning Class Name as Key let key = Ssortingng(el).componentsSeparatedBySsortingng(".").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:Ssortingng = Ssortingng(currentStat.objectAtIndex(0)).componentsSeparatedBySsortingng(".").last! print("Search Key :: \(key_Statt)") //Accessing Grouped Object using above class type key let arr_Statt = categorised[key_Statt] print("Array Resortingeved:: ",arr_Statt) print("Full Dump of Array::") dump(arr_Statt)