web-dev-qa-db-ja.com

配列の要素をいくつかのプロパティでグループ化する

プロパティdateのオブジェクトの配列があります。

私が欲しいのは、各配列に同じ日付のオブジェクトが含まれる配列の配列を作成することです。

オブジェクトをフィルタリングするには.filterのようなものが必要であり、配列にすべてのものを追加するには.mapが必要であることを理解しています。

しかし、グループごとに個別の配列がフィルターされたオブジェクトから必要であること、およびこの配列を「グローバル」配列に追加する必要があることを.mapに伝える方法と、同じ日付のオブジェクトが必要であることを.filterに伝える方法は?

16
Alexey K

遅いかもしれませんが、新しいXcode 9 SDK辞書には新しいinitメソッドがあります

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

ドキュメントには、このメソッドが行うことの簡単な例があります。 この例を以下に投稿します。

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

結果は次のようになります。

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

oriyentelソリューションを改善して、何でも順序付けされたグループ化を可能にします。

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

次に、任意のTuplestructまたはclassで、任意のプロパティで機能します。

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

1つのステップを抽象化して、必要なのは、特定のプロパティによる配列の要素groupです。次のように、マップにグループ化を任せることができます。

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

このグループ化はstableであることに注意してください。つまり、グループは出現順に表示され、グループ内では、個々の要素は元の配列と同じ順序で表示されます。

使用例

整数を使用した例を示します。 Tを含む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

Raphealのソリューションは機能します。ただし、グループ化が実際に安定しているという主張をサポートするために、ソリューションを変更することを提案します。

現在のところ、grouped()を呼び出すとグループ化された配列が返されますが、その後の呼び出しでは、グループが異なる順序で配列された配列が返される可能性があります。

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

これで、新しいグループ化を発見した順序を追跡できるようになったので、Dictionaryの順序付けされていないvaluesプロパティに依存するよりも、一貫してグループ化された配列を返すことができます。

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

GolenKov kosty回答への+1。

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

その他の例:

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

に相当

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

あなたの場合:

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}

出力:

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

Swift 5の場合、Dictionary 's init(grouping:by:) を使用して、配列の要素をプロパティの1つで辞書にグループ化できます。初期化子。完了したら、Dictionaryの-​​ values プロパティとArrayinit(_:) イニシャライザ。


次のPlaygroundサンプルコードは、1つのプロパティで配列の要素を新しい配列の配列にグループ化する方法を示しています。

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