web-dev-qa-db-fra.com

Grouper les éléments d'un tableau par une propriété

J'ai un tableau d'objets avec la propriété date.

Ce que je veux, c'est créer un tableau de tableaux où chaque tableau contiendra des objets avec la même date.

Je comprends que j'ai besoin de quelque chose comme .filter pour filtrer les objets, puis .map pour ajouter tout au tableau.

Mais comment dire .map que je veux un tableau séparé pour chaque groupe des objets filtrés et que ce tableau doit être ajouté au tableau "global" et comment dire .filter que je veux des objets avec la même date?

16
Alexey K

Il est peut-être tard mais le nouveau dictionnaire sdk Xcode 9 a une nouvelle méthode init

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

La documentation a un exemple simple de ce que fait cette méthode. Je viens de poster cet exemple ci-dessous:

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

Le résultat sera:

["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
13
golenkovkostya

amélioration de la solution oriyentel pour permettre le regroupement ordonné sur n'importe quoi:

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, struct ou class et pour toute propriété:

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 })
9
Cœur

Résumé d'une étape, ce que vous voulez, c'est grouper les éléments d'un tableau par une certaine propriété. Vous pouvez laisser une carte faire le regroupement comme vous le souhaitez:

protocol Groupable {
    associatedtype GroupingType: Hashable
    var grouping: GroupingType { get set }
}

extension Array where Element: Groupable  {
    typealias GroupingType = Element.GroupingType

    func grouped() -> [[Element]] {
        var groups = [GroupingType: [Element]]()

        for element in self {
            if let _ = groups[element.grouping] {
                groups[element.grouping]!.append(element)
            } else {
                groups[element.grouping] = [element]
            }
        }

        return Array<[Element]>(groups.values)
    }
}

Notez que ce regroupement est stable, c'est-à-dire que les groupes apparaissent dans l'ordre d'apparition, et à l'intérieur des groupes, les éléments individuels apparaissent dans le même ordre que dans le tableau d'origine.

Exemple d'utilisation

Je vais donner un exemple en utilisant des entiers; il doit être clair comment utiliser n'importe quel type (hachable) pour T, y compris Date.

struct GroupInt: Groupable {
    typealias GroupingType = Int
    var grouping: Int
    var content: String
}

var a = [GroupInt(grouping: 1, content: "a"),
         GroupInt(grouping: 2, content: "b") ,
         GroupInt(grouping: 1, content: "c")]

print(a.grouped())
// > [[GroupInt(grouping: 2, content: "b")], [GroupInt(grouping: 1, content: "a"), GroupInt(grouping: 1, content: "c")]]
1
Raphael

La solution de Rapheal fonctionne. Cependant, je proposerais de modifier la solution pour soutenir l'affirmation selon laquelle le groupement est en fait stable.

Dans l'état actuel des choses, l'appel de grouped() renverra un tableau groupé, mais les appels ultérieurs pourraient retourner un tableau avec des groupes dans un ordre différent, bien que les éléments de chaque groupe soient dans l'ordre attendu.

internal protocol Groupable {
    associatedtype GroupingType : Hashable
    var groupingKey : GroupingType? { get }
}

extension Array where Element : Groupable {

    typealias GroupingType = Element.GroupingType

    func grouped(nilsAsSingleGroup: Bool = false) -> [[Element]] {
        var groups = [Int : [Element]]()
        var groupsOrder = [Int]()
        let nilGroupingKey = UUID().uuidString.hashValue
        var nilGroup = [Element]()

        for element in self {

            // If it has a grouping key then use it. Otherwise, conditionally make one based on if nils get put in the same bucket or not
            var groupingKey = element.groupingKey?.hashValue ?? UUID().uuidString.hashValue
            if nilsAsSingleGroup, element.groupingKey == nil { groupingKey = nilGroupingKey }

            // Group nils together
            if nilsAsSingleGroup, element.groupingKey == nil {
                nilGroup.append(element)
                continue
            }

            // Place the element in the right bucket
            if let _ = groups[groupingKey] {
                groups[groupingKey]!.append(element)
            } else {
                // New key, track it
                groups[groupingKey] = [element]
                groupsOrder.append(groupingKey)
            }

        }

        // Build our array of arrays from the dictionary of buckets
        var grouped = groupsOrder.flatMap{ groups[$0] }
        if nilsAsSingleGroup, !nilGroup.isEmpty { grouped.append(nilGroup) }

        return grouped
    }
}

Maintenant que nous suivons l'ordre dans lequel nous découvrons de nouveaux regroupements, nous pouvons retourner un tableau groupé de manière plus cohérente que de simplement compter sur la propriété values non ordonnée d'un dictionnaire.

struct GroupableInt: Groupable {
    typealias GroupingType = Int
    var grouping: Int?
    var content: String
}

var a = [GroupableInt(groupingKey: 1, value: "test1"),
         GroupableInt(groupingKey: 2, value: "test2"),
         GroupableInt(groupingKey: 2, value: "test3"),
         GroupableInt(groupingKey: nil, value: "test4"),
         GroupableInt(groupingKey: 3, value: "test5"),
         GroupableInt(groupingKey: 3, value: "test6"),
         GroupableInt(groupingKey: nil, value: "test7")]

print(a.grouped())
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")],[GroupableInt(groupingKey: nil, value: "test7")]]

print(a.grouped(nilsAsSingleGroup: true))
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4"),GroupableInt(groupingKey: nil, value: "test7")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")]]
1
oriyentel

+1 à la réponse kosty de GolenKov.

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

Plus d'exemples:

enum Parity {
   case even, odd
   init(_ value: Int) {
       self = value % 2 == 0 ? .even : .odd
   }
}
let parity = Dictionary(grouping: 0 ..< 10 , by: Parity.init )

Équivalent à

let parity2 = Dictionary(grouping: 0 ..< 10) { $0 % 2 }

Dans ton cas:

struct Person : CustomStringConvertible {
    let dateOfBirth : Date
    let name :String
    var description: String {
        return "\(name)"
    }
}

extension Date {
    init(dateString:String) {
        let formatter = DateFormatter()
        formatter.timeZone = NSTimeZone.default
        formatter.dateFormat = "MM/dd/yyyy"
        self = formatter.date(from: dateString)!
    }
}
let people = [Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Foo"),
              Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Bar"),
              Person(dateOfBirth:Date(dateString:"02/01/2017"),name:"FooBar")]
let parityFields = Dictionary(grouping: people) {$0.dateOfBirth}

Production:

[2017-01-01: [Foo, Bar], 2017-02-01:  [FooBar] ]
1
Niz

Avec Swift 5, vous pouvez regrouper les éléments d'un tableau par l'une de leurs propriétés dans un dictionnaire en utilisant les Dictionaryinit(grouping:by:) Une fois terminé, vous pouvez créer un tableau de tableaux à partir du dictionnaire en utilisant la propriété Dictionary de values et la fonction Arrayinit(_:) initialiseur.


L'exemple de code Playground suivant montre comment regrouper les éléments d'un tableau par une propriété dans un nouveau tableau de tableaux:

import Foundation

struct Purchase: CustomStringConvertible {
    let id: Int 
    let date: Date
    var description: String {
        return "Purchase #\(id) (\(date))"
    }
}

let date1 = Calendar.current.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let date2 = Calendar.current.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let date3 = Calendar.current.date(from: DateComponents(year: 2012, month: 8, day: 15))!
let purchases = [
    Purchase(id: 1, date: date1),
    Purchase(id: 2, date: date1),
    Purchase(id: 3, date: date2),
    Purchase(id: 4, date: date3),
    Purchase(id: 5, date: date3)
]

let groupingDictionary = Dictionary(grouping: purchases, by: { $0.date })
print(groupingDictionary)
/*
 [
    2012-08-14 22:00:00 +0000: [Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
    2010-11-21 23:00:00 +0000: [Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)],
    2015-04-30 22:00:00 +0000: [Purchase #3 (2015-04-30 22:00:00 +0000)]
 ]
 */

let groupingArray = Array(groupingDictionary.values)
print(groupingArray)
/*
 [
    [Purchase #3 (2015-04-30 22:00:00 +0000)],
    [Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
    [Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)]
 ]
 */
0
Imanou Petit