arreglos array ios arrays swift nsarray

ios - arreglos - array xcode swift



Cómo agrupar por los elementos de una matriz en Swift (12)

Swift 4:

Desde Swift 4, esta funcionalidad se ha agregado a la biblioteca estándar . Puedes usarlo así:

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

Desafortunadamente, la función append anterior copia la matriz subyacente, en lugar de mutarla en su lugar, lo que sería preferible. Esto causa una desaceleración bastante grande . Puede solucionar el problema utilizando un contenedor de tipo de referencia:

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

Aunque atraviese el diccionario final dos veces, esta versión sigue siendo más rápida que la original en la mayoría de los casos.

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

En su caso, podría hacer que las "claves" devueltas por keyFunc sean los nombres:

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

Entonces obtendrá un diccionario, donde cada clave es un nombre, y cada valor es una matriz de los eventos de estadísticas con ese nombre.

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 }

Lo que da la salida:

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

(El swiftstub está here )

Digamos que tengo este código:

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

Podría llamar tantas veces manualmente la siguiente función para tener 2 matrices agrupadas por "mismo nombre".

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

El problema es que no sabré el valor de la variable, en este caso "cena" y "almuerzo", por lo que me gustaría agrupar esta matriz de statEvents automáticamente por nombre, por lo que obtengo tantas matrices como el nombre es diferente.

¿Cómo podría hacer eso?


¡Thr Dictionary (agrupación: arr) es tan fácil!

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 }


Aquí está mi enfoque basado en tuplas para mantener el orden mientras uso Swift 4 KeyPath''s como comparador de grupo:

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

Ejemplo de cómo 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)


Con Swift 5, Dictionary tiene un método inicializador llamado init(grouping:by:) . init(grouping:by:) tiene la siguiente declaración:

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

Crea un nuevo diccionario donde las claves son las agrupaciones devueltas por el cierre dado y los valores son matrices de los elementos que devolvieron cada clave específica.

El siguiente código de Playground muestra cómo usar init(grouping:by:) para resolver su 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 dictionary = Dictionary(grouping: statEvents, by: { (element: StatEvents) in return element.name }) //let dictionary = Dictionary(grouping: statEvents) { $0.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] ] */


En Swift 4, esta extensión tiene el mejor rendimiento y ayuda a encadenar a sus operadores

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

Ejemplo:

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

crea:

[ "ETH": [ Asset(coin: "ETH", amount: 15) ], "BTC": [ Asset(coin: "BTC", amount: 12), Asset(coin: "BTC", amount: 30) ] ]


Extender sobre la respuesta aceptada para permitir la agrupación ordenada :

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

Entonces funcionará en cualquier tupla :

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

Además de cualquier estructura o clase :

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


Hola, si necesita mantener el orden al agrupar elementos en lugar del diccionario hash, he usado tuplas y he mantenido el orden de la lista durante la agrupación.

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


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

Uso:

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


Sacando una hoja del ejemplo "oisdk" . Extender la solución para agrupar objetos en función del nombre de la clase Enlace de demostración y código fuente .

Fragmento de código para agrupar basado en el Nombre de clase:

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)


Swift 4: puede usar init (agrupación: por :) del sitio de desarrolladores de Apple

Ejemplo :

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

Entonces en tu caso

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


También puede agrupar por KeyPath , así:

public extension Sequence { func group<Key>(by keyPath: KeyPath<Element, Key>) -> [Key: [Element]] where Key: Hashable { return Dictionary(grouping: self, by: { $0[keyPath: keyPath] }) } }

Usando el ejemplo de cifrado 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), ]

Entonces el uso se ve así:

let grouped = assets.group(by: /.coin)

Produciendo el mismo resultado:

[ "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({ ($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)})