Apa cara paling bersih untuk menerapkan peta () ke kamus di Swift?

154

Saya ingin memetakan fungsi pada semua tombol di kamus. Saya berharap sesuatu seperti yang berikut ini akan berfungsi, tetapi filter tidak dapat diterapkan ke kamus secara langsung. Apa cara paling bersih untuk mencapai ini?

Dalam contoh ini, saya mencoba untuk menambah setiap nilai dengan 1. Namun ini insidental untuk contoh - tujuan utamanya adalah untuk mengetahui cara menerapkan peta () ke kamus.

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

d.map() {
    $0.1 += 1
}
Maria Zverina
sumber
1
Kamus tidak memiliki fungsi peta, jadi tidak jelas apa yang Anda coba lakukan.
David Berry
7
Ya - Saya tahu Kamus tidak memiliki fungsi peta. Pertanyaan itu dapat diulangi sebagai bagaimana hal ini dapat dilakukan dengan penutupan tanpa perlu beralih ke seluruh kamus.
Maria Zverina
2
Anda masih perlu mengulangi seluruh kamus karena itulah yang mapdilakukan. apa yang Anda minta adalah cara menyembunyikan iterasi di balik ekstensi / penutupan sehingga Anda tidak perlu melihatnya setiap kali Anda menggunakannya.
Joseph Mark
1
Untuk Swift 4, lihat jawaban saya yang menunjukkan hingga 5 cara berbeda untuk menyelesaikan masalah Anda.
Imanou Petit

Jawaban:

281

Swift 4+

Kabar baik! Swift 4 mencakup mapValues(_:)metode yang membangun salinan kamus dengan tombol yang sama, tetapi nilainya berbeda. Ini juga termasuk filter(_:)kelebihan yang mengembalikan a Dictionary, dan init(uniqueKeysWithValues:)dan init(_:uniquingKeysWith:)initializers untuk membuat Dictionarydari urutan tuple yang sewenang-wenang. Itu berarti, jika Anda ingin mengubah kunci dan nilai, Anda bisa mengatakan sesuatu seperti:

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

Ada juga API baru untuk menggabungkan kamus bersama, menggantikan nilai default untuk elemen yang hilang, mengelompokkan nilai-nilai (mengubah koleksi menjadi kamus array, dikunci oleh hasil pemetaan koleksi atas beberapa fungsi), dan banyak lagi.

Selama diskusi proposal, SE-0165 , yang memperkenalkan fitur-fitur ini, saya mengemukakan jawaban Stack Overflow ini beberapa kali, dan saya pikir banyaknya upvotes membantu menunjukkan permintaan. Jadi terima kasih atas bantuan Anda membuat Swift lebih baik!

Brent Royal-Gordon
sumber
6
Ini tentu saja tidak sempurna, tetapi kita tidak harus membiarkan yang sempurna menjadi musuh orang baik. A mapyang dapat menghasilkan kunci yang saling bertentangan masih berguna mapdalam banyak situasi. (Di sisi lain, Anda bisa berargumen bahwa memetakan hanya nilai-nilai adalah interpretasi alami mappada kamus-baik contoh poster asli dan milik saya hanya mengubah nilai-nilai, dan secara konseptual, mappada array hanya memodifikasi nilai-nilai, bukan indeks.)
Brent Royal-Gordon
2
blok kode terakhir Anda sepertinya tidak ada try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim
9
Diperbarui untuk Swift 2.1? GettingDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson
4
Dengan Swift 2.2 saya dapatkan Argument labels '(_:)' do not match any available overloadsketika menerapkan ekstensi terakhir itu. Tidak benar-benar yakin bagaimana menyelesaikannya: /
Erken
2
@Audioy Potongan kode itu tergantung pada Dictionarypenginisialisasi yang ditambahkan dalam salah satu contoh sebelumnya. Pastikan Anda memasukkan keduanya.
Brent Royal-Gordon
65

Dengan Swift 5, Anda dapat menggunakan salah satu dari lima cuplikan berikut untuk menyelesaikan masalah Anda.


# 1. Menggunakan Dictionary mapValues(_:)metode

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. Menggunakan Dictionary mapmetode dan init(uniqueKeysWithValues:)penginisialisasi

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. Menggunakan Dictionary reduce(_:_:)metode atau reduce(into:_:)metode

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 Menggunakan Dictionary subscript(_:default:)subskrip

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 Menggunakan Dictionary subscript(_:)subskrip

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]
Imanou Petit
sumber
Misalnya, ubah kunci bukan nilai. Terima kasih ini sangat membantu.
Yogesh Patel
18

Sementara sebagian besar jawaban di sini fokus pada cara memetakan seluruh kamus (kunci dan nilai), pertanyaannya hanya ingin memetakan nilai. Ini adalah perbedaan penting karena nilai pemetaan memungkinkan Anda untuk menjamin jumlah entri yang sama, sedangkan pemetaan kedua kunci dan nilai dapat menghasilkan kunci duplikat.

Berikut ini ekstensi,, mapValuesyang memungkinkan Anda untuk memetakan nilai-nilai saja. Catatan juga memperluas kamus dengan initdari urutan pasangan kunci / nilai, yang sedikit lebih umum daripada menginisialisasi dari array:

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

}
Kecepatan Kecepatan Udara
sumber
..bagaimana ini digunakan? Apakah saya baru ke Swift mind memberikan contoh di sini?
FlowUI. SimpleUITesting.com
ketika saya mencoba myDictionary.map, di dalam penutupan, saya harus melakukan peta lain dengan cara biasa. Ini bekerja, tetapi apakah ini cara yang seharusnya digunakan?
FlowUI. SimpleUITesting.com
Apakah ada jaminan di suatu tempat bahwa urutan kunci & nilai saat dipanggil secara terpisah dipasangkan, dan karenanya cocok untuk zip?
Aaron Zinman
Mengapa membuat init baru ketika Anda bisa menggunakannya func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }. Saya menemukan itu juga lebih baik. Apakah ada masalah kinerja?
Marcel
12

Cara terbersih adalah menambahkan mapKamus:

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

Memeriksa apakah itu berfungsi:

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)

Keluaran:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
Joseph Mark
sumber
1
Masalah yang saya lihat dengan pendekatan ini adalah bahwa SequenceType.mapitu bukan fungsi yang bermutasi. Contoh Anda dapat ditulis ulang untuk mengikuti kontrak yang ada map, tetapi itu sama dengan jawaban yang diterima.
Christopher Pickslay
7

Anda juga bisa menggunakan reducealih-alih peta. reducemampu melakukan apa pun yang mapbisa dilakukan dan banyak lagi!

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]

Akan cukup mudah untuk membungkus ini dalam ekstensi, tetapi bahkan tanpa ekstensi itu memungkinkan Anda melakukan apa yang Anda inginkan dengan satu panggilan fungsi.

Aaron Rasmussen
sumber
10
Kelemahan dari pendekatan ini adalah salinan kamus yang baru dibuat setiap kali Anda menambahkan kunci baru, sehingga fungsi ini sangat tidak efisien (pengoptimal mungkin menyelamatkan Anda).
Airspeed Velocity
Itu adalah poin yang bagus ... satu hal yang saya belum mengerti dengan baik adalah internal manajemen memori Swift. Saya menghargai komentar seperti ini yang membuat saya berpikir tentang hal itu :)
Aaron Rasmussen
1
Tidak perlu untuk temp var dan kurangi sekarang menjadi metode di Swift 2.0:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey
4

Ternyata Anda bisa melakukan ini. Yang harus Anda lakukan adalah membuat array dari yang MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>kembali dari keysmetode kamus . ( Info di sini ) Anda kemudian dapat memetakan array ini, dan meneruskan nilai yang diperbarui kembali ke kamus.

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

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

dictionary
Mick MacCallum
sumber
Bisakah Anda menambahkan penjelasan bagaimana mengonversi ["foo": 1, "bar": 2] menjadi [("foo", 1), ("bar", 2)]
Maria Zverina
@MariaZverina Saya tidak yakin apa yang Anda maksud.
Mick MacCallum
Jika Anda dapat melakukan konversi di atas, filter dapat diterapkan langsung ke array yang dihasilkan
Maria Zverina
@MariaZverina Gotcha. Ya sayangnya saya tidak mengetahui cara untuk melakukan itu.
Mick MacCallum
3

Menurut Referensi Perpustakaan Standar Swift, peta adalah fungsi dari array. Bukan untuk kamus.

Tetapi Anda dapat mengulangi kamus Anda untuk memodifikasi kunci:

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

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

Saya sedang mencari cara untuk memetakan kamus langsung ke Array yang diketik dengan objek khusus. Temukan solusinya dalam ekstensi ini:

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

Menggunakannya sebagai berikut:

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

Cepat 3

Saya mencoba cara mudah di Swift 3.

Saya ingin peta [String: String] ke [String: String] , saya menggunakan forEach bukan peta atau peta datar.

    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
        }
    }
Pasangan
sumber
Tidak diinginkan karena menciptakan array baru dan menambahkannya
Steve Kuo
2

Cepat 5

fungsi peta kamus dilengkapi dengan sintaks ini.

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

Anda bisa mengatur nilai penutupan untuk ini

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

Output akan ::

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

Mungkin memberi peringatan ini Result of call to 'map' is unused

Kemudian cukup atur _ =sebelum variabel.

Mungkin itu akan membantu Anda Terima kasih.

NøBi Mac
sumber
1

Saya berasumsi masalahnya adalah menjaga kunci kamus asli kami dan memetakan semua nilainya dengan cara tertentu.

Mari kita generalisasi dan katakan bahwa kita mungkin ingin memetakan semua nilai ke tipe lain .

Jadi yang ingin kita mulai adalah kamus dan penutup (fungsi) dan berakhir dengan kamus baru. Masalahnya adalah, tentu saja, pengetikan ketat Swift menghalangi. Penutupan perlu menentukan jenis apa yang masuk dan jenis apa yang keluar, dan dengan demikian kita tidak bisa membuat metode yang mengambil jenderal penutupan - kecuali dalam konteks generik. Jadi kita membutuhkan fungsi generik.

Solusi lain telah berkonsentrasi pada melakukan hal ini dalam dunia generik Kamus generic struct itu sendiri, tetapi saya merasa lebih mudah untuk berpikir dalam hal fungsi tingkat atas. Sebagai contoh, kita bisa menulis ini, di mana K adalah tipe kunci, V1 adalah tipe nilai kamus awal, dan V2 adalah tipe nilai kamus akhir:

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
}

Berikut adalah contoh sederhana menyebutnya, hanya untuk membuktikan bahwa generik memang menyelesaikan sendiri pada waktu kompilasi:

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

Kami mulai dengan kamus [String:Int]dan berakhir dengan kamus [String:String]dengan mengubah nilai-nilai kamus pertama melalui penutupan.

(EDIT: Saya melihat sekarang bahwa ini secara efektif sama dengan solusi AirspeedVelocity, kecuali bahwa saya tidak menambahkan keanggunan ekstra dari penginisialisasi Kamus yang membuat Kamus keluar dari urutan zip.)

matt
sumber
1

Cepat 3

Pemakaian:

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

Perpanjangan:

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
    }
}
dimpiax
sumber
1

Jika Anda hanya mencoba memetakan nilai (berpotensi mengubah tipenya), sertakan ekstensi ini:

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

Mengingat Anda memiliki kamus ini:

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

Transformasi nilai single-line kemudian terlihat seperti ini:

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

Transformasi nilai multi-garis kemudian terlihat seperti ini:

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

Pendekatan lain adalah dengan mapkamus dan reduce, di mana fungsi keyTransformdan valueTransformfungsi.

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
}
NebulaFox
sumber
Itu lebih merupakan konsep daripada kode aktual, tetapi meskipun demikian, saya mengembangkannya menjadi kode yang sedang berjalan.
NebulaFox
0

Swift 3 saya menggunakan ini,

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
}

Pemakaian:

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

Ref

Mohammad Zaid Pathan
sumber