Nesne dizisini Swift'de Sözlük'e eşle


95

Bir dizi Personnesnem var:

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

ve dizi:

let myArray = [p1,p1,p3]

Klasik çözümün myArraySözlüğü olmak için eşlemek istiyorum [position:name]:

var myDictionary =  [Int:String]()

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

Fonksiyonel yaklaşımla bunu Swift tarafından herhangi zarif bir yolu yoktur map, flatMap... ya da diğer çağdaş Swift tarzı


Geç oyuna Biraz ama sen sözlüğü istiyorsun [position:name]yoksa [position:[name]]? Aynı pozisyonda iki kişi varsa, sözlüğünüz yalnızca son karşılaşılanı döngünüzde tutacaktır .... Benzer bir sorum var ve çözüm bulmaya çalışıyorum ama sonucun şöyle olmasını istiyorum[1: [p1, p3], 2: [p2]]
Zonker.in.Geneva

1
Onların yerleşik bir işlevi vardır. Buraya bakın . TemeldeDictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Honey

Yanıtlar:


129

Tamam map, bunun iyi bir örneği değil, çünkü döngü yapmakla aynı reduce, onun yerine kullanabilirsiniz , her bir nesnenizin birleştirilmesi ve tek bir değere dönüşmesi gerekiyordu:

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"]

Swift 4 veya daha yüksek sürümlerde daha net sözdizimi için lütfen aşağıdaki cevabı kullanın.


8
Bu yaklaşımı beğendim, ancak bu bir sözlüğü yüklemek için bir döngü oluşturmaktan daha verimli mi? Performans konusunda endişeliyim çünkü her öğe için daha büyük bir sözlüğün yeni bir değişken kopyasını oluşturması gerekiyor gibi görünüyor ...
Kendall Helmstetter Gelner

aslında bir döngü, bu şekilde yazmanın basitleştirilmiş yolu, performans aynı veya bir şekilde normal döngüden daha iyi
Tj3n

2
Kendall, aşağıdaki cevabıma bakın, Swift 4'ü kullanırken döngü yapmaktan daha hızlı olabilir. Yine de, bunu doğrulamak için kıyaslama yapmadım.
Possen

2
Bunu kullanmayın !!! @KendallHelmstetterGelner'ın da belirttiği gibi, bu yaklaşımdaki sorun her yinelemedir, sözlüğün tamamını mevcut durumunda kopyalamaktır, bu nedenle ilk döngüde tek maddelik bir sözlüğü kopyalamaktır. İkinci yineleme, iki maddelik bir sözlüğü kopyalamaktır. Bu BÜYÜK bir performans kaybı !! Bunu yapmanın daha iyi yolu, sözlüğe dizinizi alan ve içindeki tüm öğeleri ekleyen kullanışlı bir başlangıç ​​yazmaktır. Bu şekilde, tüketici için hala tek satırlık bir programdır, ancak sahip olduğu tüm tahsis karmaşasını ortadan kaldırır. Ayrıca, diziye göre önceden ayırabilirsiniz.
Mark A. Donohoe

1
Performansın bir sorun olacağını sanmıyorum. Swift derleyicisi, sözlüğün eski kopyalarının hiçbirinin kullanılmadığını ve muhtemelen onları yaratmayacağını fark edecek. Kendi kodunuzu optimize etmenin ilk kuralını hatırlayın: "Yapma." Bilgisayar biliminin bir başka kuralı, verimli algoritmaların yalnızca N büyük olduğunda ve N neredeyse hiçbir zaman büyük olmadığında işe yarar olmasıdır.
bugloaf

138

Beri Swift 4kullanmakta daha temiz ve daha verimli Tj3n yaklaşımı @ yapabileceği intosürümünü reducedaha hızlı ve kolay okumaktır böylece geçici sözlüğe ve dönüş değeri kurtulmak alır.

Örnek kod kurulumu:

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 parametre sonuç türünün boş sözlüğüne geçirildi:

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

Doğrudan iletilen türde bir sözlüğü döndürür into:

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

Neden sadece konumsal ($ 0, $ 1) argümanlarla çalıştığı ve onları isimlendirdiğimde çalışmadığı konusunda biraz kafam karıştı. Düzenleme: boşver, derleyici belki hata yaptı çünkü hala sonucu döndürmeye çalıştım.
Departamento B

Bu cevapta belirsiz olan şey, neyi $0temsil ettiğidir: inout"geri döndüğümüz" sözlüktür - gerçekten geri inoutdönmese de , çünkü onu değiştirmek -ness nedeniyle geri dönmeye eşdeğerdir .
gelecek-adam

94

Swift 4'ten bu yana bunu çok kolay bir şekilde yapabilirsiniz. Vardır , iki yeni dizilerini bir dizisinden (anahtar ve değer çiftleri) bir sözlük oluşturmak ilklendiriciler. Anahtarların benzersiz olduğu garanti ediliyorsa, aşağıdakileri yapabilirsiniz:

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"]

Herhangi bir anahtar kopyalanırsa, bu bir çalışma zamanı hatasıyla başarısız olur. Bu durumda bu sürümü kullanabilirsiniz:

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"]

Bu sizin for döngüsü gibi davranır. Değerlerin "üzerine yazmak" ve ilk eşlemeye bağlı kalmak istemiyorsanız, şunu kullanabilirsiniz:

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

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

Swift 4.2 , sıralama öğelerini bir sözlüğe gruplayan üçüncü bir başlatıcı ekler . Sözlük anahtarları bir kapanışla türetilir. Aynı anahtara sahip öğeler, sırayla aynı sırayla bir diziye yerleştirilir. Bu, yukarıdaki gibi benzer sonuçlar elde etmenizi sağlar. Örneğin:

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
Aslında belgelere göre 'gruplama' başlatıcı, değer olarak dizi ile sözlük oluşturur (bu 'gruplama' anlamına gelir, değil mi?) Ve yukarıdaki durumda daha çok benzer olacaktır [1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]. Beklenen sonuç değil, ancak isterseniz düz sözlüğe daha da eşleştirilebilir.
Varrry

Eureka! Bu aradığım droid! Bu mükemmel ve kodumdaki şeyleri büyük ölçüde basitleştiriyor.
Zonker.in.Geneva

Başlatıcılara ölü bağlantılar. Kalıcı bağlantı kullanmak için güncelleme yapabilir misiniz?
Mark A. Donohoe

@MarqueIV Kırık bağlantıları düzelttim. Bu bağlamda permalink ile neyi kastettiğinizden emin değil misiniz ?
Mackie Messer

Kalıcı bağlantılar, web sitelerinin kullandığı ve asla değişmeyecek olan bağlantılardır. Dolayısıyla, örneğin, günden güne değişebilen 'www.SomeSite.com/events/today/item1.htm' gibi bir şey yerine, çoğu site 'www.SomeSite.com?eventid= 12345 'asla değişmeyecek ve bu nedenle bağlantılarda kullanmak güvenlidir. Herkes bunu yapmaz, ancak büyük sitelerdeki çoğu sayfa bir tane sunar.
Mark A. Donohoe

8

DictionaryTuple'lar gibi tür için özel başlatıcı yazabilirsiniz :

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

ve sonra mapdiziniz için kullanın Person:

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

5

KeyPath tabanlı bir çözüme ne dersiniz?

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

Bunu nasıl kullanacağınız:

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

Bir azaltma işlevi kullanabilirsiniz. İlk olarak, Kişi sınıfı için belirlenmiş bir başlatıcı oluşturdum

class Person {
  var name:String
  var position:Int

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

Daha sonra, bir değerler dizisi başlattım

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

Ve son olarak, sözlük değişkeninin sonucu:

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

1

Bu benim kullandığım şey

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}

Son 2 satırı birleştirmenin bir yolu olsaydı iyi olurdu, böylece bu peopleByPositionbir let.

Bunu yapan Array'e bir uzantı yapabiliriz!

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

O zaman yapabiliriz

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

1

Belki bunun gibi bir şey?

myArray.forEach({ myDictionary[$0.position] = $0.name })
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.