Comment regrouper les éléments d'un tableau en Swift
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 pouvais l'appeler autant de fois manuellement la fonction suivante afin d'avoir 2 tableaux regroupés par "même nom".
filteredArray1 = currentStat.statEvents.filter({"151910920".name == "dinner"})
filteredArray2 = currentStat.statEvents.filter({"151910920".name == "lunch"})
le problème est que je ne connaîtrai pas la valeur variable, dans ce cas" dinner "et" lunch", donc je voudrais regrouper ce tableau de statEvents automatiquement par nom, donc j'obtiens autant de tableaux que le nom devient différent.
comment j'ai pu faire ça?
10 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: { "151900920".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 fonction append
copie le tableau sous-jacent, au lieu de le muter en place, ce qui serait préférable. cela provoque un assez grand ralentissement . Vous pouvez obtenir autour de la problème en utilisant un type de référence wrapper:
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 encore 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
être les noms:
currentStat.statEvents.categorise { "151940920".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 dictionnaire, où chaque clé est un nom, et chaque valeur est un tableau de la 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) { "151950920".name }
qui donne la sortie:
extension StatEvents : Printable {
var description: String {
return "\(self.name): \(self.date)"
}
}
print(categorise(currentStat.statEvents) { "151960920".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 )
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 dictionnaire où les clés sont les groupes retournés par la fermeture donnée et les valeurs sont des tableaux des éléments qui ont retourné chaque clé spécifique.
le code de terrain de jeu suivant montre comment utilisez 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 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]
]
*/
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
}
}
Utilisation:
currentStat.statEvents.categorise { "151910920".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: vous pouvez utiliser init(groupement:par:) à partir de site des développeurs apple
exemple :
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { "151900920".first! })
// ["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
donc dans votre cas
let dictionary = Dictionary(grouping: currentStat.statEvents, by: { "151910920".name! })
dans Swift 4, Cette extension offre 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: { "151910920".coin })
crée:
[
"ETH": [
Asset(coin: "ETH", amount: 15)
],
"BTC": [
Asset(coin: "BTC", amount: 12),
Asset(coin: "BTC", amount: 30)
]
]
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({ ("151900920".fizz, "151900920")},
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({ ("151900920".buzz, "151900920")})
extension sur réponse acceptée pour permettre ordonné grouping:
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["151900920"]! }
}
}
puis il fonctionnera sur n'importe quel tuple :
let a = [(grouping: 10, content: "a"),
(grouping: 20, content: "b"),
(grouping: 10, content: "c")]
print(a.group { "151910920".grouping })
ainsi que toute struct 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 { "151920920".grouping })
Voici mon approche basée sur les tuples pour maintenir L'ordre tout 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:{ "151900920".key == element[keyPath:by]}) ?? (key: element[keyPath:by], values:[])
result.values.append(element)
if let index = accumulator.index(where: { "151900920".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)
Hey si vous avez besoin de garder l'ordre tout en groupant des éléments au lieu de dictionnaire de hachage j'ai utilisé des tuples et ai gardé l'ordre de la liste tout en groupant.
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: { "151900920".0 == groupKey }) else { groupCategorized.append((groupKey, [item])); continue }
groupCategorized[index].1.append(item)
}
return groupCategorized
}
}
prenant une feuille de "oisdk" exemple . Extension de la solution aux objets de groupe basés sur le nom de classe Démo & lien de code Source .
extrait de Code pour le regroupement basé sur le nom de 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)