कैसे स्विफ्ट में एक सरणी के तत्वों द्वारा समूह


90

मान लीजिए कि मेरे पास यह कोड है:

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

समस्या यह है कि मुझे चर मान नहीं पता होगा, इस मामले में "रात का खाना" और "दोपहर का भोजन", इसलिए मैं नाम से स्वचालित रूप से इस तरह के स्टैटव्यू को समूह बनाना चाहूंगा, इसलिए मुझे कई सरणियां मिलती हैं जैसे कि नाम अलग-अलग होता है।

ऐसा कैसे किया जा सकता था?


स्विफ्ट 4 के लिए मेरा जवाब देखें जो नए Dictionary init(grouping:by:)इनिशियलाइज़र का उपयोग करता है।
इमानौ पेटिट

जवाबों:


196

स्विफ्ट 4:

स्विफ्ट 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)
]

स्विफ्ट 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
  }
}

भले ही आप अंतिम शब्दकोश को दो बार पार करते हैं, यह संस्करण अभी भी ज्यादातर मामलों में मूल से तेज है।

स्विफ्ट 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)
  ]
]

तो आपको एक शब्दकोश मिलेगा, जहाँ हर कुंजी एक नाम है, और हर मूल्य उस नाम के साथ स्टेटिवेंट्स की एक सरणी है।

स्विफ्ट 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
  ]
]

(स्विफ्टस्टब यहाँ है )


बहुत बहुत शुक्रिया @oisdk! क्या आप जानते हैं कि जो शब्दकोश बनाया गया है उसके मूल्यों के सूचकांक तक पहुंचने का एक तरीका है? मेरा मतलब है, मुझे पता है कि चाबियाँ और मूल्य कैसे प्राप्त करें, लेकिन मैं उन शब्दकोशों के सूचकांक "0", "1", "2" ... प्राप्त करना चाहूंगा
रुबेन

इसलिए यदि आप चाहें, तो अपने शब्दकोश में तीन "डिनर" मान कहें, आप जाएंगे dict[key](मेरे पहले उदाहरण में यह होगा ans["dinner"])। यदि आप तीन चीजों के सूचकांकों को स्वयं चाहते थे, तो यह कुछ ऐसा होगा enumerate(ans["dinner"]), या, यदि आप सूचकांकों के माध्यम से पहुंचना चाहते हैं, तो आप इसे ऐसा कर सकते हैं:, ans["dinner"]?[0]जो आपके नीचे संग्रहीत सरणी का पहला तत्व लौटाएगा dinner
oisdk

अप्स यह हमेशा मुझे शून्य लौटाता है
Ruben

अरे हाँ मैं इसे प्राप्त करता हूं, लेकिन समस्या यह है कि इस उदाहरण में मुझे "रात के खाने" का मूल्य पता होना चाहिए, लेकिन वास्तविक कोड में मुझे इस मूल्यों को नहीं पता होगा और न ही कितनी वस्तुओं का शब्दकोश होगा
रुबेन

1
यह एक समाधान की दिशा में एक अच्छी शुरुआत है, लेकिन इसमें कुछ कम समय है। यहां मिलान पैटर्न का उपयोग if caseअनावश्यक है, लेकिन इससे भी महत्वपूर्ण बात यह है कि एक शब्दकोश में संग्रहित करने के लिए संलग्न करने के dict[key]?.append)लिए हर बार एक कॉपी होती है। Rosslebeau.com/2016// पर
अलेक्जेंडर

65

स्विफ्ट 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]
]
*/

4
अच्छा एक, क्या आप यह भी शामिल कर सकते हैं कि इसे भी लिखा जा सकता है let dictionary = Dictionary(grouping: statEvents) { $0.name }- सिंटेक्स शुगर कोटिंग
user1046037

1
यह उत्तर होना चाहिए तेजी से शुरू 4 - पूरी तरह से सेब द्वारा समर्थित है, और उम्मीद है कि अत्यधिक प्रदर्शन।
हर्बल 7

विधेय में लौटी गैर-वैकल्पिक कुंजी पर भी ध्यान दें, अन्यथा आप त्रुटि देखेंगे: "अधिक संदर्भ के बिना अभिव्यक्ति का प्रकार अस्पष्ट है ..."
असिक

1
@ user1046037 स्विफ्ट 5.2Dictionary(grouping: statEvents, by: \.name)
लियो डबस

31

स्विफ्ट 4: आप ऐप्पल डेवलपर साइट से init (समूहीकरण: :) का उपयोग कर सकते हैं

उदाहरण :

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

1
यह अब तक का सबसे अच्छा जवाब है, यह नहीं पता है कि यह धन्यवाद मौजूद है;)
रिचपेज़

यह भी एक कीपैथ के साथ काम करता है: डिक्शनरी = शब्दकोश (समूहीकरण: currentStat.statEvents, द्वारा: \
_name

26

स्विफ्ट 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)
  ]
]

9
एक उपयोग उदाहरण अत्यधिक सराहना की जाएगी :) धन्यवाद!
सेंचुरियन

यहाँ उपयोग का एक उदाहरण है: yourArray.categorise (currentStat.statEvents) {$ 0.name}। यह समारोह डिक्शनरी <स्ट्रींग, एरे <स्टेटवाइंट्स >>
सेंचुरियन

6

स्विफ्ट 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)
    ]
]

क्या आप उपयोग का उदाहरण लिख सकते हैं?
उत्कु डालमज

@duan क्या BTC और btc जैसे मामले को नजरअंदाज करना संभव है ...
मोइन शिराजी

1
@ मोइनश्रीज़ी assets.group(by: { $0.coin.uppercased() }), लेकिन इसका 'मैप के लिए बेहतर तो ग्रुप
डुआन

3

आप 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]
        })
    }
}

@ डुआन के क्रिप्टो उदाहरण का उपयोग करना:

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 }
कीपथ के

2

स्विफ्ट 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)})

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

0

यहां स्विफ्ट 4 कीपथ के समूह तुलनित्र के रूप में ऑर्डर देने के लिए मेरा टपल आधारित दृष्टिकोण है :

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)

0

अरे अगर आपको हैश शब्दकोश के बजाय तत्वों को समूहीकृत करते समय आदेश रखने की आवश्यकता है तो मैंने टुपल्स का उपयोग किया है और समूह बनाते समय सूची का क्रम रखा है।

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

0

Thr Dictionary (समूहीकरण: गिरफ्तारी) इतना आसान है!

 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
}

-2

"ऑइस्डक" उदाहरण से एक पत्ती लेना । समूह नाम डेमो और स्रोत कोड लिंक के आधार पर समूह की वस्तुओं के समाधान का विस्तार ।

वर्ग नाम के आधार पर समूहन के लिए कोड स्निपेट:

 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)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.