Disons que j'ai ce 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] = []
Je pourrais appeler autant de fois manuellement la fonction suivante afin d'avoir 2 tableaux 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 connais pas la valeur de la variable, dans ce cas "dîner" et "déjeuner", je voudrais donc regrouper ce tableau de statEvents automatiquement par nom, donc j'obtiens autant de tableaux que le nom devient différent.
Comment pourrais-je faire ça?
Dictionary
init(grouping:by:)
initialiseur.Réponses:
Swift 4:
Depuis Swift 4, cette fonctionnalité a été ajoutée à la bibliothèque standard . Vous pouvez l'utiliser comme ceci:
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) ]
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
append
fonction ci-dessus copie le tableau sous-jacent, au lieu de le mettre en place, ce qui serait préférable. Cela provoque un ralentissement assez important . Vous pouvez contourner le problème en utilisant un wrapper de type de référence: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 dictionnaire 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 "clés" retournées par
keyFunc
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) ] ]
Vous obtiendrez donc un dictionnaire, où chaque clé est un nom, et chaque valeur est un tableau des StatEvents avec ce nom.
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 }
Ce qui donne la sortie:
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 ] ]
(Le swiftstub est ici )
la source
dict[key]
, (dans mon premier exemple ce seraitans["dinner"]
). Si vous vouliez les indices des trois choses eux-mêmes, ce serait quelque chose commeenumerate(ans["dinner"])
, ou, si vous vouliez accéder via les index, vous pourriez le faire comme:,ans["dinner"]?[0]
qui vous renverrait le premier élément du tableau stocké sousdinner
.if case
) n'est pas nécessaire, mais plus important encore, l'ajout à un fichier stocké dans un dictionnairedict[key]?.append)
entraîne une copie à chaque fois. Voir rosslebeau.com/2016/…Avec Swift 5,
Dictionary
a une méthode d'initialisation appeléeinit(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
Le code Playground suivant montre comment utiliser
init(grouping:by:)
pour résoudre votre problème: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] ] */
la source
let dictionary = Dictionary(grouping: statEvents) { $0.name }
- Syntaxe Sugar coatingDictionary(grouping: statEvents, by: \.name)
Swift 4: vous pouvez utiliser init (grouping: by :) à partir du site des développeurs Apple
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 ton cas
let dictionary = Dictionary(grouping: currentStat.statEvents, by: { $0.name! })
la source
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) ] ]
la source
Dans Swift 4, cette extension a les meilleures performances et aide à enchaîner vos opérateurs
extension Sequence { func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] { return Dictionary.init(grouping: self, by: key) } }
Exemple:
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 })
crée:
[ "ETH": [ Asset(coin: "ETH", amount: 15) ], "BTC": [ Asset(coin: "BTC", amount: 12), Asset(coin: "BTC", amount: 30) ] ]
la source
assets.group(by: { $0.coin.uppercased() })
, mais il vaut mieux cartographier que le groupeVous pouvez également grouper par
KeyPath
, comme ceci:public extension Sequence { func group<Key>(by keyPath: KeyPath<Element, Key>) -> [Key: [Element]] where Key: Hashable { return Dictionary(grouping: self, by: { $0[keyPath: keyPath] }) } }
En utilisant l'exemple de crypto de @ 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), ]
Ensuite, l'utilisation ressemble à ceci:
let grouped = assets.group(by: \.coin)
Donnant le même résultat:
[ "ETH": [ Asset(coin: "ETH", amount: 15) ], "BTC": [ Asset(coin: "BTC", amount: 12), Asset(coin: "BTC", amount: 30) ] ]
la source
func grouped<Key: Hashable>(by keyForValue: (Element) -> Key) -> [Key: [Element]] {
.init(grouping: self, by: keyForValue)
}
cela vous permettrait d'appelerassets.grouped(by: \.coin)
ouassets.grouped { $0.coin }
Swift 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)})
la source
Extension de la réponse acceptée pour permettre le 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 classe :
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 })
la source
Voici mon approche basée sur les tuple pour garder l'ordre tout en utilisant Swift 4 KeyPath 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 d'utilisation:
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)
la source
Hé, si vous devez garder l'ordre tout en regroupant des éléments au lieu du dictionnaire de hachage, j'ai utilisé des tuples et conservé l'ordre de la liste lors du regroupement.
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 } }
la source
Le dictionnaire Thr (regroupement: arr) est si simple!
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 }
la source
Prenant une feuille de l' exemple "oisdk" . Extension de la solution pour regrouper des objets en fonction du nom de la classe Lien Démo et code source .
Extrait de code pour le regroupement basé sur le nom de la classe:
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)
la source