Soru Swift'de bir sözlükte map () uygulamasının en temiz yolu nedir?


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

Bu örnekte, her değeri 1'e yükseltmeye çalışıyorum. Ancak bu örnek için rastlantısaldır - ana amaç, bir sözlük için map () yönteminin nasıl uygulanacağını belirlemektir.

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

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

108
2018-06-09 08:19


Menşei


Sözlük bir harita işlevine sahip değil, bu yüzden yapmaya çalıştığınız şey net değil. - David Berry
Evet - Sözlük'ün harita işlevinin olmadığını biliyorum. Bu soru, bütün sözlük üzerinde yineleme gerektirmeden kapamalarla nasıl gerçekleştirilebileceği şeklinde yeniden yorumlanabilir. - Maria Zverina
hala tüm sözlük üzerinde yinelemelisiniz çünkü map yapar. İstediğiniz şey, bir uzantının / kapamanın ardında yinelemeyi gizlemenin bir yoludur, böylece her kullandığınızda ona bakmak zorunda kalmazsınız. - Joseph Mark
Swift 4 için bkz. cevabım Bu, probleminizi çözmek için 5 farklı yol gösterir. - Imanou Petit


Cevaplar:


Swift 4

İyi haberler! Swift 4 bir içerir mapValues(_:) Bir sözlükte aynı tuşlarla bir kopyasını oluşturan yöntem, ancak farklı değerler. Ayrıca bir filter(_:) bir aşırı yük verir Dictionary, ve init(uniqueKeysWithValues:) ve init(_:uniquingKeysWith:) başlatmak için başlatıcılar Dictionary tuplelerin keyfi bir dizisinden. Yani, hem anahtarları hem de değerleri değiştirmek isterseniz, şöyle bir şey söyleyebilirsiniz:

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

Ayrıca, sözlüklerin bir araya getirilmesi için yeni API'ler de vardır; eksik elemanlar için varsayılan değerlerin yerine koyulması, değerlerin gruplandırılması (bir koleksiyonun, bazı fonksiyonlar üzerinde koleksiyonun çıkarılması sonucu dizilen bir dizinin sözlüğüne dönüştürülmesi) ve daha fazlası.

Teklifin tartışması sırasında, SE-0165Bu özellikleri tanıtan bu Yığın Taşımı yanıtını birkaç kez gündeme getirdim ve bence çok sayıda verinin talebi kanıtlamasına yardımcı oldu. Swift'i daha iyi hale getirdiğin için teşekkürler!

Swift 3

map bir uzatma yöntemidir Sequence ve Collection. Bu şekilde tanımlanır:

func map<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]

Dan beri Dictionary uyuyor Collection, ve Onun Element typealias (anahtar, değer) bir tupledır, yani bunun gibi bir şey yapabilmeniz gerektiği anlamına gelir:

result = dict.map { (key, value) in (key, value.uppercaseString) }

Ancak, bu aslında bir Dictionarydeğişkenli değişken. map yöntem her zaman bir dizi döndürmek için tanımlanır [T]), sözlükler gibi diğer türler için bile. Eğer iki-tuples bir dizi bir dönüşecek bir kurucu yazarsanız Dictionary ve tüm dünya ile doğru olacak:

extension Dictionary {
    init(_ pairs: [Element]) {
        self.init()
        for (k, v) in pairs {
            self[k] = v
        }
    }
}

result = Dictionary(dict.map { (key, value) in (key, value.uppercaseString) })

Hatta sözlüğe özgü bir sürümünü yazmak isteyebilirsiniz map sadece kurucuyu açıkça çağırmaktan kaçınmak için. Burada ayrıca bir uygulama dahil ettik filter:

extension Dictionary {
    func mapPairs<OutKey: Hashable, OutValue>(_ transform: (Element) throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
        return Dictionary<OutKey, OutValue>(try map(transform))
    }

    func filterPairs(_ includeElement: (Element) throws -> Bool) rethrows -> [Key: Value] {
        return Dictionary(try filter(includeElement))
    }
}

Farklı isimler kullandım, çünkü aksi takdirde sadece geri dönüş değerleri iki yöntemi birbirinden ayırabilirdi, bu da kodu çok belirsiz hale getirebilirdi.

Bunu şöyle kullanın:

result = dict.mapPairs { (key, value) in (key, value.uppercaseString) }

Bu arada, birçok amaç için, sadece değerleri eşlemek isteyebilirsiniz:

extension Dictionary {
    func map<OutValue>(_ transform: (Value) throws -> OutValue) rethrows -> [Key: OutValue] {
        return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
    }
}

Nedeni bu mapPairs Aynı tuşa eşlenmiş iki son varsa çiftleri kaybedebilir. map(Value -> OutValue)Diğer taraftan, anahtarları her zaman korur, bu yüzden bu bir tehlike değildir.


198
2018-06-14 10:40



Kesinlikle kusurludur, ama mükemmelin düşmanı olmanın mükemmelliğine izin vermemeliyiz. bir map Çakışan anahtarlar üretebilecek olsa da hala yararlı map birçok durumda. (Öte yandan, sadece değerlerin haritalandırılmasının doğal bir yorum olduğunu iddia edebilirsiniz. map sözlüklerde - hem orijinal posterin örneği hem de benimki sadece değerleri değiştiriyor ve kavramsal olarak, map bir dizide sadece endeksleri değil, değerleri değiştirir.) - Brent Royal-Gordon
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
Swift 2.1 için güncellendi mi? Alma DictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads - Per Eriksson
Swift 2.2 ile alıyorum Argument labels '(_:)' do not match any available overloads son uzantıyı uygularken. Bunu nasıl çözeceğine gerçekten emin değilim: / - Audioy
@ Sesli Kod snippet'i Dictionary başlatıcı önceki örneklerden birinde eklendi. İkisini de dahil ettiğinizden emin olun. - Brent Royal-Gordon


Buradaki cevapların çoğu, tüm sözlüğün nasıl eşleştirileceğine odaklanırken (tuşlar) ve Değerler), soru sadece değerleri haritalamak istedim. Bu, haritalama değerleri aynı sayıda girişi garanti etmenize olanak tanıdığı için önemli bir ayrımdır, oysa hem anahtar hem de değer eşlemesi, yinelenen anahtarlara neden olabilir.

İşte bir uzantı, mapValuesBu sadece değerleri haritalamanıza olanak tanır. Ayrıca, sözlüğü initBir diziden başlatılmasından biraz daha genel olan bir anahtar / değer çiftleri dizisinden:

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

}

18
2018-04-05 19:04



..Nasıl kullanılır? Zihin için yeni bir örnek mi taktım buraya? - iOS Calendar View OnMyProfile
myDictionary.map'i denediğimde, kapağın içinde, normal bir şekilde başka bir harita yapmak zorunda kaldım. Bu işe yarıyor, ama bu şekilde kullanılması gerekiyor? - iOS Calendar View OnMyProfile
Ayrı olarak çağrıldığında anahtarların ve değerlerin sırasının eşleştirildiği ve dolayısıyla zip için uygun olduğu bir yerde bir garanti var mı? - Aaron Zinman
Sadece kullanabildiğiniz zaman neden yeni bir init oluşturun? 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


Swift 4 ile, birini kullanabilirsiniz snippet'leri takip eden beş Sorunu çözmek için.


# 1. kullanma Dictionary  mapValues(_:) yöntem

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. kullanma Dictionary  map yöntem ve init(uniqueKeysWithValues:) başlatıcı

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.. kullanma Dictionary  reduce(_:_:) yöntem

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]

4.. kullanma Dictionary  subscript(_:default:) altindis

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.. kullanma Dictionary  subscript(_:) altindis

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]

18
2018-06-17 20:48





En temiz yol sadece eklemek map Sözlüğe

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ıştığını kontrol etmek:

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]

11
2018-06-09 09:52



Size temiz olduğu gibi +1 vereceğim ama yine de daha fazla bir şeyin ortaya çıkacağını umuyoruz! :) - Maria Zverina
Bu yaklaşımla gördüğüm problem şu: SequenceType.map bir mutasyon işlevi değildir. Örneğiniz için mevcut sözleşmeyi takip etmek için yeniden yazılabilir. mapAma bu kabul edilen cevapla aynı. - Christopher Pickslay


Ayrıca kullanabilirsiniz reduce harita yerine reduce herşeyi yapabilir map yapabilir ve daha fazlası!

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ıza izin veriyor.


7
2018-02-13 15:22



Bu yaklaşımın dezavantajı, yeni bir anahtar eklediğinizde sözlüğün yepyeni bir kopyasının yapılmasıdır, bu nedenle bu işlev çok verimsizdir (optimizatör belki Seni kurtarmak). - Airspeed Velocity
Bu iyi bir nokta ... ben çok iyi anlamadığım bir şey henüz Swift'in bellek yönetiminin içselidir. Bunun hakkında düşünmeme neden olan yorumları takdir ediyorum :) - Aaron Rasmussen
Temp 2.0'da artık bir gereksinim ve azaltmaya gerek yok artık 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


Bunu yapabileceğiniz ortaya çıkıyor. Yapman gereken şey, MapCollectionView<Dictionary<KeyType, ValueType>, KeyType> sözlüklerden döndü keys yöntem. (Bilgi buradaSonra bu diziyi eşleştirebilir ve güncellenen değerleri sözlüğe geri aktarabilirsiniz.

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

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

dictionary

4
2018-06-09 08:47



["Foo": 1, "bar": 2] 'den [("foo", 1), ("bar", 2)]' a nasıl dönüştürüleceğini açıklayabilir misiniz? - Maria Zverina
@MariaZverina Ne demek istediğinden emin değilim. - Mick MacCallum
Yukarıdaki dönüşümü gerçekleştirirseniz, 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


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

Ancak, anahtarınızı değiştirmek için sözlüğünüzü yineleyebilirsiniz:

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

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

3
2018-06-09 08:43





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

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

Bunu takip eden şekilde kullanma:

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

3
2018-01-26 14:23