Допустим, у меня есть этот код:
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] = []
Я мог бы вызвать следующую функцию столько раз вручную, чтобы иметь 2 массива, сгруппированных по «одинаковому имени».
filteredArray1 = currentStat.statEvents.filter({$0.name == "dinner"})
filteredArray2 = currentStat.statEvents.filter({$0.name == "lunch"})
Проблема в том, что я не знаю значение переменной, в данном случае «ужин» и «обед», поэтому я хотел бы автоматически сгруппировать этот массив statEvents по имени, чтобы получить столько массивов, сколько изменится имя.
Как я мог это сделать?
Dictionary
init(grouping:by:)
инициализатор.Ответы:
Swift 4:
Начиная с Swift 4, эта функциональность была добавлена в стандартную библиотеку . Вы можете использовать это так:
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 } }
К сожалению, приведенная
append
выше функция копирует базовый массив вместо того, чтобы изменять его на месте, что было бы предпочтительнее. Это вызывает довольно большое замедление . Вы можете обойти проблему, используя оболочку ссылочного типа: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 } }
Даже если вы дважды просматриваете последний словарь, эта версия в большинстве случаев все равно быстрее оригинала.
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 } }
В вашем случае вы могли бы вернуть «ключи» с
keyFunc
именами: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) ] ]
Таким образом, вы получите словарь, где каждый ключ - это имя, а каждое значение - это массив StatEvents с этим именем.
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 }
Что дает результат:
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 ] ]
(Swiftstub здесь )
источник
dict[key]
(в моем первом примере это было быans["dinner"]
). Если вам нужны индексы самих трех вещей, это будет что-то вродеenumerate(ans["dinner"])
, или, если вы хотите получить доступ через индексы, вы можете сделать это так:,ans["dinner"]?[0]
который вернет вам первый элемент массива, хранящегося вdinner
.if case
) нет необходимости, но, что более важно, добавление к сохраненному в словаре с помощьюdict[key]?.append)
вызывает создание копии каждый раз. См. Rosslebeau.com/2016/…В Swift 5
Dictionary
вызывается метод инициализатораinit(grouping:by:)
.init(grouping:by:)
имеет следующее объявление:init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
Следующий код игровой площадки показывает, как использовать
init(grouping:by:)
для решения вашей проблемы: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] ] */
источник
let dictionary = Dictionary(grouping: statEvents) { $0.name }
Dictionary(grouping: statEvents, by: \.name)
Swift 4: вы можете использовать init (grouping: by :) с сайта разработчика Apple
пример :
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"] let studentsByLetter = Dictionary(grouping: students, by: { $0.first! }) // ["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
Так что в твоем случае
let dictionary = Dictionary(grouping: currentStat.statEvents, by: { $0.name! })
источник
Для 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 } }
Применение:
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) ] ]
источник
В Swift 4 это расширение имеет лучшую производительность и помогает объединить ваших операторов
extension Sequence { func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] { return Dictionary.init(grouping: self, by: key) } }
Пример:
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 })
создает:
[ "ETH": [ Asset(coin: "ETH", amount: 15) ], "BTC": [ Asset(coin: "BTC", amount: 12), Asset(coin: "BTC", amount: 30) ] ]
источник
assets.group(by: { $0.coin.uppercased() })
, но лучше сопоставить, чем группуВы также можете группировать
KeyPath
, например:public extension Sequence { func group<Key>(by keyPath: KeyPath<Element, Key>) -> [Key: [Element]] where Key: Hashable { return Dictionary(grouping: self, by: { $0[keyPath: keyPath] }) } }
Используя криптографический пример @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), ]
Тогда использование выглядит так:
let grouped = assets.group(by: \.coin)
Даем тот же результат:
[ "ETH": [ Asset(coin: "ETH", amount: 15) ], "BTC": [ Asset(coin: "BTC", amount: 12), Asset(coin: "BTC", amount: 30) ] ]
источник
func grouped<Key: Hashable>(by keyForValue: (Element) -> Key) -> [Key: [Element]] {
.init(grouping: self, by: keyForValue)
}
это позволит вам позвонитьassets.grouped(by: \.coin)
илиassets.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)})
источник
Расширение принятого ответа, чтобы разрешить упорядоченную группировку:
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]! } } }
Тогда он будет работать с любым кортежем :
let a = [(grouping: 10, content: "a"), (grouping: 20, content: "b"), (grouping: 10, content: "c")] print(a.group { $0.grouping })
Как и любая структура или класс :
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 })
источник
Вот мой подход на основе кортежей для поддержания порядка при использовании Swift 4 KeyPath в качестве группового компаратора:
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 } } }
Пример использования:
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)
источник
Привет, если вам нужно сохранить порядок при группировке элементов вместо хеш-словаря, я использовал кортежи и сохранил порядок списка при группировке.
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 } }
источник
Словарь Thr (группировка: arr) настолько прост!
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 }
источник
Взять лист из примера "oisdk" . Расширение решения для группировки объектов на основе ссылки Demo & Source Code имени класса .
Фрагмент кода для группировки по имени класса:
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)
источник