Swift'teki bir sözlüğe map () uygulamasının en temiz yolu nedir?


154

Sözlükteki tüm tuşlarda bir işlevi eşlemek istiyorum. Aşağıdaki gibi bir şey işe yarayacağını umuyordum, ancak filtre doğrudan sözlüğe uygulanamıyor. Bunu başarmanın en temiz yolu nedir?

Bu örnekte, her bir değeri 1 arttırmaya çalışıyorum. Ancak bu örnek için tesadüfi - asıl amaç map () 'nin bir sözlüğe nasıl uygulanacağını bulmaktır.

var d = ["foo" : 1, "bar" : 2]

d.map() {
    $0.1 += 1
}

1
Sözlüğün harita işlevi yok, bu yüzden ne yapmaya çalıştığınız net değil.
David Berry

7
Evet - Sözlüğün harita işlevi olmadığını biliyorum. Soru, sözlüğün tamamında yinelemeye gerek kalmadan kapaklarla nasıl gerçekleştirilebileceği şeklinde yeniden ifade edilebilir.
Maria Zverina

2
hala tüm sözlük üzerinde yinelemek gerekir çünkü bu ne mapyapar. istediğin, yinelemeyi bir uzantı / kapanışın arkasına saklamanın bir yoludur, böylece onu her kullandığınızda bakmak zorunda kalmazsınız.
Joseph Mark

1
Swift 4 için bkz cevabım gösterileri en fazla 5 farklı yolu sorunu çözmek için söyledi.
Imanou Petit

Yanıtlar:


281

Swift 4+

İyi haberler! Swift 4 mapValues(_:), aynı anahtarlara, ancak farklı değerlere sahip bir sözlüğün bir kopyasını oluşturan bir yöntem içerir . Ayrıca içeren filter(_:)bir döner aşırı Dictionaryve init(uniqueKeysWithValues:)ve init(_:uniquingKeysWith:)bir oluşturmak için başlatıcıları Dictionarydizilerini keyfi dizisinden. Bu, hem anahtarları hem de değerleri değiştirmek istiyorsanız, şöyle bir şey söyleyebileceğiniz anlamına gelir:

let newDict = Dictionary(uniqueKeysWithValues:
    oldDict.map { key, value in (key.uppercased(), value.lowercased()) })

Sözlükleri bir araya getirmek, eksik öğeler için varsayılan bir değer koymak, değerleri gruplamak (bir koleksiyonu diziler sözlüğüne dönüştürmek, koleksiyonun bir işlev üzerinde eşlenmesi sonucunda anahtarlanan) ve daha fazlası için yeni API'ler de vardır.

Bu özellikleri tanıtan SE-0165 teklifinin tartışılması sırasında bu Stack Overflow cevabını birkaç kez gündeme getirdim ve bence çok sayıda upvotes talebi göstermeye yardımcı oldu. Swift'i daha iyi hale getirmen için yardımın için teşekkürler!


6
Kesinlikle kusurlu, ama mükemmelin iyinin düşmanı olmasına izin vermemeliyiz. mapÇakışan anahtarlar üretebilen A , mapbirçok durumda hala yararlıdır . (Öte yandan, yalnızca değerleri eşlemenin mapsözlükler üzerinde doğal bir yorum olduğunu iddia edebilirsiniz - hem orijinal posterin örneği hem de benimki yalnızca değerleri değiştirir ve kavramsal mapolarak bir dizide endeksleri değil, yalnızca değerleri değiştirir.)
Brent Royal-Gordon

2
son kod bloğunuz eksik gibi görünüyor try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim

9
Swift 2.1 için güncellendi mi? BaşlarkenDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson

4
Swift 2.2 ile Argument labels '(_:)' do not match any available overloadsbu son uzantıyı uygularken alıyorum . Nasıl çözüleceğinden tam olarak emin değilim: /
Erken

2
@Audioy Bu kod snippet'i Dictionary, önceki örneklerden birine eklenen başlatıcıya bağlıdır . Her ikisini de eklediğinizden emin olun.
Brent Royal-Gordon

65

Swift 5 ile sorununuzu çözmek için aşağıdaki beş parçacıktan birini kullanabilirsiniz .


# 1. Dictionary mapValues(_:)Yöntemi kullanarak

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.mapValues { value in
    return value + 1
}
//let newDictionary = dictionary.mapValues { $0 + 1 } // also works

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 2. Dictionary mapYöntem ve init(uniqueKeysWithValues:)başlatıcı kullanma

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let tupleArray = dictionary.map { (key: String, value: Int) in
    return (key, value + 1)
}
//let tupleArray = dictionary.map { ($0, $1 + 1) } // also works

let newDictionary = Dictionary(uniqueKeysWithValues: tupleArray)

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

3.. Dictionary reduce(_:_:)Yöntem veya reduce(into:_:)yöntem kullanma

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce([:]) { (partialResult: [String: Int], tuple: (key: String, value: Int)) in
    var result = partialResult
    result[tuple.key] = tuple.value + 1
    return result
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
    result[tuple.key] = tuple.value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

4.. Alt Dictionary subscript(_:default:)simge kullanma

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key, default: value] += 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

5.. Alt Dictionary subscript(_:)simge kullanma

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key] = value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

Lütfen anahtarları değer değil, değiştirin. Yararlı teşekkürler.
Yogesh Patel

18

Buradaki cevapların çoğu tüm sözlüğün nasıl eşleneceğine odaklanırken (tuşlar ve değerler) , soru gerçekten sadece değerleri haritalamak istedi. Bu önemli bir ayrımdır çünkü eşleme değerleri aynı sayıda girişi garanti etmenizi sağlarken, hem anahtar hem de değeri eşlemek yinelenen tuşlarla sonuçlanabilir.

Burada, mapValuesyalnızca değerleri eşlemenizi sağlayan bir uzantı var . Ayrıca init, bir anahtar / değer çiftleri dizisinden gelen bir sözlüğü genişleterek , bir diziden başlatmaktan biraz daha genel olduğunu unutmayın:

extension Dictionary {
    init<S: SequenceType where S.Generator.Element == Element>
      (_ seq: S) {
        self.init()
        for (k,v) in seq {
            self[k] = v
        }
    }

    func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
        return Dictionary<Key,T>(zip(self.keys, self.values.map(transform)))
    }

}

..Nasıl kullanılır? Zihni buraya bir örnek koyarak çabucak yeniyim?
FlowUI. SimpleUITesting.com

myDictionary.map denediğimde, kapanış içinde, düzenli olarak başka bir harita yapmak zorunda kaldım. Bu işe yarıyor, ama bu nasıl kullanılmalı?
FlowUI.

Ayrı olarak adlandırıldığında anahtarların ve değerlerin sırasının eşleştirildiği ve bu nedenle zip için uygun olduğu bir yerde bir garanti var mı?
Aaron Zinman

Neden sadece yeni bir init oluşturursunuz func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }? Ben de daha iyi okudum. Bir performans sorunu var mı?
Marcel

12

En temiz yol sadece sözlüğe eklemektir map:

extension Dictionary {
    mutating func map(transform: (key:KeyType, value:ValueType) -> (newValue:ValueType)) {
        for key in self.keys {
            var newValue = transform(key: key, value: self[key]!)
            self.updateValue(newValue, forKey: key)
        }
    }
}

Çalışıp çalışmadığını kontrol etme:

var dic = ["a": 50, "b": 60, "c": 70]

dic.map { $0.1 + 1 }

println(dic)

dic.map { (key, value) in
    if key == "a" {
        return value
    } else {
        return value * 2
    }
}

println(dic)

Çıktı:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]

1
Bu yaklaşımda gördüğüm sorun SequenceType.mapmutasyona uğratıcı bir fonksiyon olmaması. Örneğiniz, mevcut sözleşmeyi takip etmek için yeniden yazılabilir map, ancak bu kabul edilen yanıtla aynıdır.
Christopher Pickslay

7

reduceHarita yerine de kullanabilirsiniz . reduceher şeyi mapyapabilir ve daha fazlasını yapabilir!

let oldDict = ["old1": 1, "old2":2]

let newDict = reduce(oldDict, [String:Int]()) { dict, pair in
    var d = dict
    d["new\(pair.1)"] = pair.1
    return d
}

println(newDict)   //  ["new1": 1, "new2": 2]

Bunu bir uzantıya sarmak oldukça kolay olurdu, ancak uzantı olmadan bile tek bir işlev çağrısı ile istediğinizi yapmanızı sağlar.


10
Bu yaklaşımın dezavantajı bu fonksiyon (iyileştirici korkunç verimsizdir böylece sözlüğün yepyeni kopya, yeni bir anahtar eklemek her zaman yapılmış olmasıdır olabilir kurtaracak).
Hava Hızı Hızı

Bu iyi bir nokta ... henüz tam olarak anlamadığım bir şey Swift'in bellek yönetiminin içselleri. Beni bu konuda düşündüren böyle yorumları takdir ediyorum :)
Aaron Rasmussen

1
Temp var ve azaltmaya gerek yok şimdi Swift 2.0'da bir yöntem:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey

4

Bunu yapabileceğiniz ortaya çıkıyor. Yapmanız gereken MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>, sözlüklerden döndürülen keysyöntemden bir dizi oluşturmaktır . ( Bilgi burada ) Daha sonra bu diziyi eşleyebilir ve geri sözlüğe güncellenmiş değerleri geçirmek.

var dictionary = ["foo" : 1, "bar" : 2]

Array(dictionary.keys).map() {
    dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}

dictionary

Dönüştürmek nasıl açıklama eklemenizi rica ediyorum [ "foo": 1, "bar": 2] [( "foo", 1), ( "bar", 2)] olarak
Maria Zverina

@MariaZverina Ne demek istediğinden emin değilim.
Mick MacCallum

Yukarıdaki dönüşümü gerçekleştirebiliyorsanız, filtre doğrudan elde edilen diziye uygulanabilir
Maria Zverina

@MariaZverina Gotcha. Evet maalesef bunu yapmanın hiçbir yolunun farkında değilim.
Mick MacCallum

3

Swift Standart Kütüphane Referansı'na göre harita, dizilerin bir fonksiyonudur. Sözlükler için değil.

Ancak anahtarlarınızı değiştirmek için sözlüğünüzü tekrarlayabilirsiniz:

var d = ["foo" : 1, "bar" : 2]

for (name, key) in d {
    d[name] = d[name]! + 1
}

3

Bir sözlüğü özel nesnelerle yazılmış bir Array içine eşlemek için bir yol arıyordum. Bu uzantıda çözüm bulundu:

extension Dictionary {
    func mapKeys<U> (transform: Key -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k))
        }
        return results
    }

    func mapValues<U> (transform: Value -> U) -> Array<U> {
        var results: Array<U> = []
        for v in self.values {
            results.append(transform(v))
        }
        return results
    }

    func map<U> (transform: Value -> U) -> Array<U> {
        return self.mapValues(transform)
    }

    func map<U> (transform: (Key, Value) -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k as Key, self[ k ]! as Value))
        }
        return results
    }

    func map<K: Hashable, V> (transform: (Key, Value) -> (K, V)) -> Dictionary<K, V> {
        var results: Dictionary<K, V> = [:]
        for k in self.keys {
            if let value = self[ k ] {
                let (u, w) = transform(k, value)
                results.updateValue(w, forKey: u)
            }
        }
        return results
    }
}

Aşağıdaki gibi kullanarak:

self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
    return VDLFilterValue(name: key, amount: value)
})

3

Hızlı 3

Swift 3'te kolay bir yol deniyorum.

Ben eşlemek istediğiniz : [dize? Dize] için [dize: String] , onun yerine harita veya düz harita forEach kullanın.

    let oldDict = ["key0": "val0", "key1": nil, "key1": "val2","key2": nil]
    var newDict = [String: String]()
    oldDict.forEach { (source: (key: String, value: String?)) in
        if let value = source.value{
            newDict[source.key] = value
        }
    }

Yeni bir dizi oluşturduğu ve buna eklediği için istenmeyen
Steve Kuo

2

Hızlı 5

sözlüğün harita fonksiyonu bu sözdizimi ile birlikte gelir.

dictData.map(transform: ((key: String, value: String)) throws -> T)

bunun için sadece kapanış değerleri ayarlayabilirsiniz

var dictData: [String: String] = [
    "key_1": "test 1",
    "key_2": "test 2",
    "key_3": "test 3",
    "key_4": "test 4",
    "key_5": "test 5",
]

dictData.map { (key, value) in
    print("Key :: \(key), Value :: \(value)")
}

Çıktı:

Key :: key_5, Value :: test 5
Key :: key_1, Value :: test 1
Key :: key_3, Value :: test 3
Key :: key_2, Value :: test 2
Key :: key_4, Value :: test 4

Bu uyarıyı verebilir Result of call to 'map' is unused

Sonra sadece _ =değişkenin önüne ayarlayın .

Belki size yardımcı olacaktır Teşekkür ederim.


1

Sorunun orijinal sözlüğümüzün anahtarlarını tutmak ve tüm değerlerini bir şekilde eşlemek olduğunu varsayıyorum.

Genelleştirelim ve tüm değerleri başka bir türe eşlemek isteyebileceğimizi söyleyelim .

Başlamak istediğimiz şey, bir sözlük ve bir kapatma (işlev) ve yeni bir sözlükle sonuçlanıyor. Sorun, elbette, Swift'in katı yazımının engellemesidir. Bir kapanışın hangi türün gireceğini ve hangi türün çıktığını belirtmesi gerekir ve bu nedenle genel bir bağlamı alan bir yöntem yapamayız - genel bağlam dışında. Dolayısıyla genel bir işleve ihtiyacımız var.

Diğer çözümler bunu Sözlük jenerik yapısının kendi jenerik dünyasında yapmaya odaklanmıştır, ancak üst düzey bir işlev açısından düşünmeyi daha kolay buluyorum. Örneğin, şunu yazabiliriz; burada K anahtar tiptir, V1 başlangıç ​​sözlüğünün değer tipidir ve V2 bitiş sözlüğünün değer tipidir:

func mapValues<K,V1,V2>(d1:[K:V1], closure:(V1)->V2) -> [K:V2] {
    var d2 = [K:V2]()
    for (key,value) in zip(d1.keys, d1.values.map(closure)) {
        d2.updateValue(value, forKey: key)
    }
    return d2
}

İşte jenerikin gerçekten derleme zamanında kendini çözdüğünü kanıtlamak için bunu çağırmanın basit bir örneği:

let d : [String:Int] = ["one":1, "two":2]
let result = mapValues(d) { (i : Int) -> String in String(i) }

Adlı bir sözlükle başladık [String:Int]ve[String:String] ve ilk sözlüğün değerlerini bir kapanış yoluyla dönüştürerek .

(DÜZENLEME: Şimdi bunun AirspeedVelocity'nin çözümü ile etkili bir şekilde aynı olduğunu görüyorum, ancak bir sözlüğü zip dizisinden çıkaran bir Sözlük başlatıcısının ekstra zarafetini eklemedim.)


1

Hızlı 3

Kullanımı:

let bob = ["a": "AAA", "b": "BBB", "c": "CCC"]
bob.mapDictionary { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]

Uzantı:

extension Dictionary {
    func mapDictionary(transform: (Key, Value) -> (Key, Value)?) -> Dictionary<Key, Value> {
        var dict = [Key: Value]()
        for key in keys {
            guard let value = self[key], let keyValue = transform(key, value) else {
                continue
            }

            dict[keyValue.0] = keyValue.1
        }
        return dict
    }
}

1

Ben sadece (potansiyel olarak türlerini değiştirme) değerleri eşlemek çalışıyorum , bu uzantıyı dahil:

extension Dictionary {
    func valuesMapped<T>(_ transform: (Value) -> T) -> [Key: T] {
        var newDict = [Key: T]()
        for (key, value) in self {
            newDict[key] = transform(value)
        }
        return newDict
    }
}

Bu sözlüğe sahip olduğunuz göz önüne alındığında:

let intsDict = ["One": 1, "Two": 2, "Three": 3]

Tek satırlı değer dönüşümü şöyle görünür:

let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]

Çok satırlı değer dönüşümü şöyle görünür:

let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
    let calculationResult = (value * 3 + 7) % 5
    return String("Complex number #\(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...

0

Başka bir yaklaşım, mapsözlüğe ve reduceişlevlerin keyTransformve valueTransformişlevlerin olduğu yerdir .

let dictionary = ["a": 1, "b": 2, "c": 3]

func keyTransform(key: String) -> Int {
    return Int(key.unicodeScalars.first!.value)
}

func valueTransform(value: Int) -> String {
    return String(value)
}

dictionary.map { (key, value) in
     [keyTransform(key): valueTransform(value)]
}.reduce([Int:String]()) { memo, element in
    var m = memo
    for (k, v) in element {
        m.updateValue(v, forKey: k)
    }
    return m
}

Daha sonra gerçek kod bir kavramdı, ama yine de, çalışan kod haline genişletti.
NebulaFox

0

Swift 3 Bunu kullandım,

func mapDict(dict:[String:Any])->[String:String]{
    var updatedDict:[String:String] = [:]
    for key in dict.keys{
        if let value = dict[key]{
            updatedDict[key] = String(describing: value)
        }
    }

   return updatedDict
}

Kullanımı:

let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)

Ref

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.