स्विफ्ट में शब्दकोश में वस्तुओं का नक्शा सरणी


101

मेरे पास एक Personऑब्जेक्ट है:

class Person {
   let name:String
   let position:Int
}

और सरणी है:

let myArray = [p1,p1,p3]

मैं क्लासिक समाधान का myArrayएक शब्दकोश बनना चाहता हूं [position:name]:

var myDictionary =  [Int:String]()

for person in myArray {
    myDictionary[person.position] = person.name
}

वहाँ स्विफ्ट द्वारा किसी भी सुंदर तरीका है कि कार्यात्मक दृष्टिकोण के साथ map, flatMap... या अन्य आधुनिक स्विफ्ट शैली


खेल के लिए थोड़ा देर हो गई, लेकिन क्या आप एक शब्दकोश चाहते हैं [position:name]या [position:[name]]? यदि आपके पास एक ही स्थिति में दो लोग हैं, तो आपका शब्दकोश केवल आपके पाश में पिछले एक को सामने रखेगा .... मेरे पास एक समान प्रश्न है जिसके लिए मैं एक समाधान खोजने की कोशिश कर रहा हूं, लेकिन मैं चाहता हूं कि परिणाम जैसा हो[1: [p1, p3], 2: [p2]]
Zonker.in.Geneva

1
उनके एक समारोह में बनाया गया है। देखें यहाँ । यह मूल रूप से हैDictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Honey

जवाबों:


133

ठीक mapहै इसका एक अच्छा उदाहरण नहीं है, क्योंकि इसकी लूपिंग के समान ही है, आप reduceइसके बजाय उपयोग कर सकते हैं , यह आपकी प्रत्येक वस्तु को एकल मूल्य में संयोजित और चालू करने के लिए ले गया है:

let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
    var dict = dict
    dict[person.position] = person.name
    return dict
}

//[2: "b", 3: "c", 1: "a"]

स्विफ्ट 4 या उच्चतर में कृपया क्लियर सिंटैक्स के लिए नीचे दिए गए उत्तर का उपयोग करें।


8
मुझे यह दृष्टिकोण पसंद है लेकिन क्या यह केवल एक शब्दकोश लोड करने के लिए एक लूप बनाने की तुलना में अधिक कुशल है? मैं प्रदर्शन के बारे में चिंता करता हूं क्योंकि ऐसा लगता है कि हर आइटम के लिए इसे एक बड़े शब्दकोश की एक नई परिवर्तनशील प्रतिलिपि बनाना होगा ...
केंडल हेल्मेस् ट्टर गेलनर

यह वास्तव में एक लूप है, इस तरह से इसे लिखने का सरल तरीका है, प्रदर्शन समान है या किसी भी तरह सामान्य लूप से बेहतर है
Tj3n

2
केंडल, मेरा जवाब नीचे देखें, यह स्विफ्ट 4 का उपयोग करते समय लूपिंग से तेज हो सकता है। हालांकि, मैंने इसकी पुष्टि करने के लिए इसे बेंचमार्क नहीं किया है।
possen

3
यह प्रयोग न करें !!! जैसा कि @KendallHelmstetterGelner ने बताया, इस दृष्टिकोण के साथ समस्या हर पुनरावृत्ति की है, यह अपने वर्तमान राज्य में पूरे शब्दकोश की नकल कर रहा है, इसलिए पहले लूप में, यह एक आइटम शब्दकोश की नकल कर रहा है। दूसरा पुनरावृति यह दो-आइटम शब्दकोश की नकल कर रहा है। यह एक बड़ा प्रदर्शन नाली है !! इसे करने का बेहतर तरीका यह होगा कि आप उस शब्दकोष पर एक सुविधा init लिखें, जो आपके एरे को लेता है और उसमें सभी आइटम जोड़ता है। इस तरह से यह अभी भी उपभोक्ता के लिए एक-लाइनर है, लेकिन यह इस पूरे आवंटन की गड़बड़ी को समाप्त करता है। साथ ही, आप सरणी के आधार पर पूर्व-आबंटित कर सकते हैं।
मार्क ए। डोनोहे

2
मुझे नहीं लगता कि प्रदर्शन एक मुद्दा होगा। स्विफ्ट कंपाइलर पहचान लेगा कि शब्दकोश की पुरानी प्रतियों में से कोई भी उपयोग नहीं किया जाता है और शायद उन्हें भी नहीं बनाएगा। अपने स्वयं के कोड को अनुकूलित करने का पहला नियम याद रखें: "यह मत करो।" कंप्यूटर विज्ञान की एक और अधिकतमता यह है कि कुशल एल्गोरिदम केवल तभी उपयोगी होते हैं जब N बड़ा होता है, और N लगभग कभी बड़ा नहीं होता है।
बुगलाफ़

149

चूंकि Swift 4आप @ Tj3n के दृष्टिकोण को अधिक सफाई और कुशलता से कर सकते हैं, इसके intoसंस्करण का उपयोग करने reduceसे अस्थायी शब्दकोश और रिटर्न वैल्यू से छुटकारा मिल जाता है, इसलिए यह तेज और पढ़ने में आसान है।

नमूना कोड सेटअप:

struct Person { 
    let name: String
    let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]

Into पैरामीटर परिणाम प्रकार का खाली शब्दकोश पारित किया जाता है:

let myDict = myArray.reduce(into: [Int: String]()) {
    $0[$1.position] = $1.name
}

में उत्तीर्ण किए गए प्रकार के शब्दकोश को सीधे लौटाता है into:

print(myDict) // [2: "c", 0: "h", 4: "b"]

मैं इस बारे में थोड़ा उलझन में हूं कि यह केवल स्थितीय ($ 0, $ 1) तर्कों के साथ काम करने लगता है और न कि जब मैं उनका नाम लेता हूं। संपादित करें: कभी नहीं, संकलक शायद इसलिए उलझे क्योंकि मैंने अभी भी परिणाम को वापस करने की कोशिश की।
डिपार्टमेन्ट बी बी

इस उत्तर में जो बात स्पष्ट नहीं है, $0वह यह दर्शाता है: यह वह inoutशब्दकोश है जिसे हम "वापस" कर रहे हैं - हालांकि वास्तव में वापस नहीं आ रहा है, क्योंकि इसे संशोधित करने का अर्थ है inout-इसकी वजह से लौटने के बराबर ।
13

96

स्विफ्ट 4 के बाद से आप इसे बहुत आसानी से कर सकते हैं। कर रहे हैं दो नए initializers कि tuples के अनुक्रम से (कुंजी और मान के जोड़े) एक शब्दकोश का निर्माण। यदि कुंजियों को विशिष्ट होने की गारंटी दी जाती है, तो आप निम्न कार्य कर सकते हैं:

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })

=> [1: "Franz", 2: "Heinz", 3: "Hans"]

यदि कोई कुंजी डुप्लिकेट है, तो यह रनटाइम त्रुटि के साथ विफल हो जाएगा। उस स्थिति में आप इस संस्करण का उपयोग कर सकते हैं:

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 1)]

Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }

=> [1: "Hans", 2: "Heinz"]

यह लूप के लिए आपका व्यवहार करता है। यदि आप मानों को "अधिलेखित" नहीं करना चाहते हैं और पहले मानचित्रण से चिपके रहते हैं, तो आप इसका उपयोग कर सकते हैं:

Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }

=> [1: "Franz", 2: "Heinz"]

स्विफ्ट 4.2 एक तीसरा इनिशियलाइज़र जोड़ता है जो तत्वों को एक शब्दकोश में अनुक्रमित करता है। शब्दकोश कुंजियाँ एक बंद से ली गई हैं। उसी कुंजी वाले तत्वों को उसी क्रम में एक सरणी में रखा जाता है जैसे कि अनुक्रम में। यह आपको ऊपर दिए गए समान परिणाम प्राप्त करने की अनुमति देता है। उदाहरण के लिए:

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }

=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }

=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]


3
डॉक्स के अनुसार 'समूहीकरण' एक तथ्य प्रारंभकर्ता साथ शब्दकोश बनाता में सरणी मूल्य के रूप में (जो की 'समूहीकरण' का अर्थ है, है ना?), और मामले में ऊपर इसे और अधिक हो जाएगा [1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]। अपेक्षित परिणाम नहीं है, लेकिन यदि आप चाहें तो इसे सपाट शब्दकोश में मैप किया जा सकता है।
वार्री

यूरेका! यह मैं देख रहा हूँ droid है! यह सही है और मेरे कोड में चीजों को बहुत सरल करता है।
Zonker.in.Geneva

शुरुआती लोगों के लिए मृत लिंक। क्या आप पर्मलिंक का उपयोग करने के लिए अपडेट कर सकते हैं?
मार्क ए। डोनोहे

@MarqueIV मैंने टूटे हुए लिंक को ठीक किया। सुनिश्चित नहीं हैं कि क्या आप के साथ बात कर रहे हैं स्थायी लिंक इस संदर्भ में?
मैकी मेसर

Permalinks वे लिंक वेबसाइट हैं जिनका उपयोग कभी नहीं बदलेगा। उदाहरण के लिए, 'www.SomeSite.com/events/today/item1.htm' जैसी कुछ चीज़ों के बजाय, जो दिन-प्रतिदिन बदल सकती हैं, अधिकांश साइटें 'www.SomeSite.com'eventid' जैसी दूसरी 'पर्मलिंक' सेट करेंगी 12345 'जो कभी नहीं बदलेगा, और इस प्रकार, लिंक में उपयोग करना सुरक्षित है। हर कोई ऐसा नहीं करता है, लेकिन बड़ी साइटों के अधिकांश पृष्ठ एक की पेशकश करेंगे।
मार्क ए। डोनोहे

8

आप Dictionaryटाइप के लिए कस्टम इनिशियलाइज़र लिख सकते हैं , उदाहरण के लिए टुपल्स से:

extension Dictionary {
    public init(keyValuePairs: [(Key, Value)]) {
        self.init()
        for pair in keyValuePairs {
            self[pair.0] = pair.1
        }
    }
}

और फिर mapअपनी सरणी के लिए उपयोग करें Person:

var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})

5

कैसे एक KeyPath आधारित समाधान के बारे में?

extension Array {

    func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
        return reduce(into: [:]) { dictionary, element in
            let key = element[keyPath: key]
            let value = element[keyPath: value]
            dictionary[key] = value
        }
    }
}

इस तरह से आप इसका इस्तेमाल करेंगे:

struct HTTPHeader {

    let field: String, value: String
}

let headers = [
    HTTPHeader(field: "Accept", value: "application/json"),
    HTTPHeader(field: "User-Agent", value: "Safari"),
]

let allHTTPHeaderFields = headers.dictionary(withKey: \.field, value: \.value)

// allHTTPHeaderFields == ["Accept": "application/json", "User-Agent": "Safari"]

2

आप एक कम फ़ंक्शन का उपयोग कर सकते हैं। पहले मैंने व्यक्ति वर्ग के लिए एक निर्दिष्ट इनिशियलाइज़र बनाया है

class Person {
  var name:String
  var position:Int

  init(_ n: String,_ p: Int) {
    name = n
    position = p
  }
}

बाद में, मैंने मूल्यों के एक सरणी को इनिशियलाइज़ किया है

let myArray = [Person("Bill",1), 
               Person("Steve", 2), 
               Person("Woz", 3)]

और अंत में, शब्दकोश चर का परिणाम है:

let dictionary = myArray.reduce([Int: Person]()){
  (total, person) in
  var totalMutable = total
  totalMutable.updateValue(person, forKey: total.count)
  return totalMutable
}

2

यह वही है जो मैं उपयोग कर रहा हूं

struct Person {
    let name:String
    let position:Int
}
let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}

अच्छा होगा अगर अंतिम 2 लाइनों को संयोजित करने का एक तरीका था ताकि peopleByPositionएक हो सके let

हम ऐरे के लिए एक विस्तार कर सकते हैं जो ऐसा करता है!

extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

तब हम बस कर सकते हैं

let peopleByPosition = persons.mapToDict(by: {$0.position})

1
extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

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