2015-07-04 20 views
31

Diciamo che ho questo codice:Come gruppo dagli elementi di un array a 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] = [] 

ho potuto chiamare quante volte manualmente la funzione successiva in modo da avere 2 array raggruppate per "omonimo ".

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

Il problema è che io non so il valore della variabile, in questo caso "cena" e "pranzo", quindi vorrei gruppo questo array di statEvents automaticamente in base al nome, in modo da ottenere il maggior numero array come il nome diventa diverso.

Come potrei farlo?

+0

Vedi [la mia risposta per Swift 4] (https://stackoverflow.com/a/44555642/1966109) che utilizza il nuovo inizializzatore 'Dictionary'' init (grouping: by:) '. –

risposta

69

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

Sfortunatamente, la funzione append sopra copie matrice sottostante, invece di mutazione in posizione, che sarebbe preferibile. This causes a pretty big slowdown. È possibile aggirare il problema utilizzando un tipo di riferimento involucro:

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

Anche se si attraversa il dizionario finale due volte, questa versione è ancora più veloce di quella originale, nella maggior parte dei casi.

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

Nel tuo caso, si potrebbe avere le "chiavi" restituito dal keyFunc essere i nomi:

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

così avrai un dizionario, dove ogni chiave è un nome e ogni valore è un array di StatEvents con quel nome.

La versione Swift 1 sarebbe:

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 } 

che fornisce l'output:

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

(Lo swiftstub è here)

+0

Grazie mille @oisdk! Sai se esiste un modo per accedere all'indice dei valori del dizionario che viene creato? Voglio dire, so come ottenere le chiavi e i valori, ma mi piacerebbe ottenere l'indice "0", "1", "2" ... di quei dizionari – Ruben

+0

Quindi se vuoi, dì i tre "cena" "valori nel tuo dizionario, dovresti usare' dict [chiave] ', (nel mio primo esempio sarebbe' ans ["dinner"] '). Se si volessero gli indici delle tre cose stesse, sarebbe qualcosa come "enumerate (ans [" dinner "])', oppure, se si volesse * accedere * tramite gli indici, si potrebbe fare come: 'ans [ "dinner"]? [0] ', che restituirebbe il primo elemento dell'array memorizzato in' dinner'. – oisdk

+0

Ups mi restituisce sempre nil – Ruben

-1

Facendo una foglia fuori del "oisdk" example. Estensione della soluzione per raggruppare oggetti in base al nome della classe Demo & Source Code link.

frammento di codice per raggruppare in base a Nome 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) 
24

Per 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) 
    ] 
] 
+9

Un esempio di utilizzo sarebbe molto apprezzato :) Grazie! – Centurion

+0

Ecco un esempio di utilizzo: yourArray.categorise (currentStat.statEvents) {$ 0.name}.La funzione restituirà Dictionary > – Centurion

0

Estendendosi su risposta accettata per consentire ordinato raggruppamento :

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

Allora funzionerà su qualsiasi tupla:

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

così come qualsiasi struct o 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 }) 
20

Con Swift 4, Dictionary ha un inizializzatore metodo chiamato init(grouping:by:). init(grouping:by:) ha la seguente dichiarazione:

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

Crea un nuovo dizionario, dove le chiavi sono i raggruppamenti restituiti dalla chiusura data ei valori sono array di elementi che hanno restituito ogni chiave specifica.


Il seguente codice di giochi mostra come utilizzare init(grouping:by:) al fine di risolvere il problema:

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] 
] 
*/ 
+0

Buono, potresti anche includere che può anche essere scritto come 'let dictionary = Dizionario (grouping: statEvents) {$ 0.name}' - Sintassi Sugar coating – user1046037

0

tuple Hey, se è necessario per mantenere l'ordine, mentre raggruppando gli elementi al posto del dizionario hash ho usato e ha mantenuto l'ordine della lista durante il raggruppamento.

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

Ecco il mio approccio basato tupla per mantenere l'ordine durante l'utilizzo di Swift 4 KeyPath's come gruppo di confronto:

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

Esempio di come usarlo:

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

Swift 4: si possibile utilizzare init (raggruppamento: da :) da apple developer site

Esempio:

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

Quindi nel tuo caso

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

Much prestazioni migliori rispetto alla migliore risposta. – duan

0

In Swift4, questa estensione ha le migliori prestazioni e di aiuto a catena le operazioni

extension Sequence { 
    func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] { 
     return Dictionary.init(grouping: self, by: key) 
    } 
}