อะไรคือวิธีที่สะอาดที่สุดในการใช้แผนที่ () กับพจนานุกรมใน Swift


154

ฉันต้องการแมปฟังก์ชั่นกับปุ่มทั้งหมดในพจนานุกรม ฉันหวังว่าสิ่งต่อไปนี้จะได้ผล แต่ตัวกรองไม่สามารถใช้กับพจนานุกรมได้โดยตรง อะไรคือวิธีที่สะอาดที่สุดในการบรรลุเป้าหมายนี้?

ในตัวอย่างนี้ฉันกำลังพยายามเพิ่มค่าแต่ละค่าทีละ 1 อย่างไรก็ตามนี่เป็นเรื่องบังเอิญสำหรับตัวอย่าง - จุดประสงค์หลักคือเพื่อหาวิธีใช้แผนที่ () กับพจนานุกรม

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

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

1
พจนานุกรมไม่มีฟังก์ชั่นแผนที่ดังนั้นจึงไม่ชัดเจนว่าคุณกำลังพยายามทำอะไร
David Berry

7
ใช่ - ฉันรู้ว่าพจนานุกรมไม่มีฟังก์ชั่นแผนที่ คำถามนี้สามารถใช้ใหม่ได้ด้วยวิธีการปิดด้วยการปิดโดยไม่จำเป็นต้องทำซ้ำทั้งพจนานุกรม
Maria Zverina

2
คุณยังต้องทำซ้ำทั้งพจนานุกรมเนื่องจากเป็นสิ่งที่mapทำ สิ่งที่คุณขอเป็นวิธีซ่อนการทำซ้ำหลังส่วนขยาย / ปิดดังนั้นคุณไม่จำเป็นต้องดูทุกครั้งที่ใช้
Joseph Mark

1
สำหรับ Swift 4 ดูคำตอบของฉันที่แสดงวิธีต่าง ๆ ถึง 5 วิธีในการแก้ปัญหาของคุณ
Imanou Petit

คำตอบ:


281

Swift 4+

ข่าวดี! Swift 4 มีmapValues(_:)วิธีการที่สร้างสำเนาของพจนานุกรมด้วยปุ่มเดียวกัน แต่มีค่าต่างกัน นอกจากนี้ยังมีfilter(_:)โอเวอร์โหลดซึ่งส่งคืน a Dictionaryและinit(uniqueKeysWithValues:)และinit(_:uniquingKeysWith:)initializers เพื่อสร้างDictionaryจากลำดับของ tuples ตามอำเภอใจ นั่นหมายความว่าหากคุณต้องการเปลี่ยนทั้งคีย์และค่าคุณสามารถพูดได้ดังนี้:

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

นอกจากนี้ยังมี API ใหม่สำหรับการรวมพจนานุกรมเข้าด้วยกันการแทนที่ค่าเริ่มต้นสำหรับองค์ประกอบที่ขาดหายไปค่าการจัดกลุ่ม (การแปลงคอลเล็กชันเป็นพจนานุกรมของอาร์เรย์โดยใช้ผลการแมปคอลเลกชันผ่านบางฟังก์ชัน) และอื่น ๆ

ในระหว่างการอภิปรายของข้อเสนอ SE-0165ที่นำเสนอคุณสมบัติเหล่านี้ฉันได้นำ Stack Overflow นี้มาตอบหลายครั้งและฉันคิดว่าจำนวน upvotes ที่แท้จริงช่วยแสดงให้เห็นถึงความต้องการ ดังนั้นขอขอบคุณสำหรับความช่วยเหลือในการทำให้ Swift ดีขึ้น!


6
แน่นอนว่ามันไม่สมบูรณ์ แต่เราต้องไม่ปล่อยให้สิ่งที่สมบูรณ์แบบเป็นศัตรูของความดี A mapที่สามารถสร้างคีย์ที่ขัดแย้งกันยังคงมีประโยชน์mapในหลาย ๆ สถานการณ์ (ในอีกทางหนึ่งคุณสามารถยืนยันว่าการทำแผนที่เฉพาะค่าคือการตีความตามธรรมชาติของmapพจนานุกรม - ทั้งตัวอย่างของโปสเตอร์ต้นฉบับและของฉันเพียง แต่ปรับเปลี่ยนค่าและแนวคิดmapในอาร์เรย์จะปรับเปลี่ยนค่าเท่านั้นไม่ใช่ดัชนี)
เบรนต์ Royal-Gordon

2
ดูเหมือนว่าบล็อกรหัสสุดท้ายของคุณจะหายไปtry:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim

9
อัปเดตสำหรับ Swift 2.1 แล้วหรือยัง ได้รับDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
ต่อ Eriksson

4
ด้วย Swift 2.2 ฉันได้รับArgument labels '(_:)' do not match any available overloadsเมื่อใช้งานส่วนขยายสุดท้ายนั้น ไม่ได้จริงๆว่าวิธีการแก้ปัญหามัน: /
Erken

2
@Audioy ข้อมูลโค้ดนั้นขึ้นอยู่กับDictionaryinitializer ที่เพิ่มเข้ามาในตัวอย่างก่อนหน้านี้ ตรวจสอบให้แน่ใจว่าคุณรวมทั้งสองอย่าง
Brent Royal-Gordon

65

ด้วย Swift 5 คุณสามารถใช้หนึ่งในห้าตัวอย่างต่อไปนี้เพื่อแก้ไขปัญหาของคุณ


# 1 ใช้Dictionary mapValues(_:)วิธีการ

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 mapวิธีการและinit(uniqueKeysWithValues:)เริ่มต้น

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(_:_:)วิธีการหรือreduce(into:_:)วิธีการ

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 ใช้ตัวDictionary subscript(_:default:)ห้อย

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 ใช้ตัวDictionary subscript(_:)ห้อย

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]

โปรดตัวอย่างที่เปลี่ยนคีย์ไม่ใช่ค่า ขอบคุณมันเป็นประโยชน์
Yogesh Patel

18

ในขณะที่คำตอบส่วนใหญ่ที่นี่มุ่งเน้นไปที่วิธีแมปพจนานุกรมทั้งหมด (คีย์และค่า) คำถามจริง ๆ เพียงต้องการแมปค่า นี่คือความแตกต่างที่สำคัญเนื่องจากค่าการทำแผนที่ช่วยให้คุณรับประกันจำนวนรายการที่เท่ากันในขณะที่การทำแผนที่ทั้งคีย์และค่าอาจส่งผลให้คีย์ที่ซ้ำกัน

ต่อไปนี้เป็นส่วนขยายmapValuesที่ช่วยให้คุณสามารถจับคู่ค่าต่างๆ โปรดทราบว่ามันยังขยายพจนานุกรมด้วยinitจากลำดับของคู่คีย์ / ค่าซึ่งค่อนข้างกว้างกว่าการเริ่มต้นจากอาร์เรย์:

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

}

.. มันใช้งานอย่างไร? ฉันใหม่เพื่อรวดเร็วใจวางตัวอย่างที่นี่หรือไม่
FlowUI SimpleUITesting.com

เมื่อฉันลอง myDictionary.map, ภายในการปิด, ฉันต้องทำแผนที่อื่นตามปกติ ใช้งานได้ แต่เป็นวิธีที่ควรใช้หรือไม่
FlowUI SimpleUITesting.com

มีการรับประกันบางอย่างหรือไม่ว่าคำสั่งของคีย์ & ค่าเมื่อมีการเรียกแยกต่างหากนั้นจับคู่กันและเหมาะสำหรับการซิปหรือไม่?
แอรอน Zinman

ทำไมต้องสร้าง init ใหม่เมื่อคุณสามารถfunc mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }ใช้ได้ ฉันคิดว่ามันอ่านดีขึ้นเช่นกัน มีปัญหาเรื่องประสิทธิภาพหรือไม่
Marcel

12

วิธีที่สะอาดที่สุดคือเพิ่ม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)
        }
    }
}

ตรวจสอบว่ามันใช้งานได้:

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)

เอาท์พุท:

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

1
ปัญหาที่ฉันเห็นด้วยวิธีนี้SequenceType.mapคือไม่ใช่ฟังก์ชันการกลายพันธุ์ ตัวอย่างของคุณอาจถูกเขียนใหม่เพื่อทำตามสัญญาที่มีอยู่สำหรับmapแต่นั่นเป็นเช่นเดียวกับคำตอบที่ยอมรับ
Christopher Pickslay

7

คุณสามารถใช้reduceแทนแผนที่ได้ reduceมีความสามารถในการทำสิ่งที่mapสามารถทำได้และอื่น ๆ !

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]

มันจะค่อนข้างง่ายในการพันในส่วนขยาย แต่แม้จะไม่มีส่วนขยายก็ให้คุณทำสิ่งที่คุณต้องการด้วยการเรียกใช้ฟังก์ชันเดียว


10
ข้อเสียของวิธีการนี้คือการทำสำเนาพจนานุกรมใหม่ทุกครั้งที่คุณเพิ่มคีย์ใหม่ดังนั้นฟังก์ชันนี้จึงไม่มีประสิทธิภาพอย่างน่ากลัว (เครื่องมือเพิ่มประสิทธิภาพอาจช่วยคุณได้)
ความเร็วของเครื่องบิน

นั่นเป็นประเด็นที่ดี ... สิ่งหนึ่งที่ฉันไม่เข้าใจดีมากคือการจัดการหน่วยความจำของ Swift ฉันขอขอบคุณความคิดเห็นเช่นนี้ที่ทำให้ฉันคิดถึง :) :)
Aaron Rasmussen

1
ไม่ต้องใช้ temp var และลดขณะนี้เป็นวิธีใน Swift 2.0:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey

4

ปรากฎว่าคุณสามารถทำได้ สิ่งที่คุณต้องทำคือการสร้างอาร์เรย์จากMapCollectionView<Dictionary<KeyType, ValueType>, KeyType>กลับมาจากพจนานุกรมkeysวิธี ( ข้อมูลที่นี่ ) จากนั้นคุณสามารถแมปอาร์เรย์นี้และส่งค่าที่อัปเดตกลับไปที่พจนานุกรม

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

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

dictionary

คุณช่วยเพิ่มคำอธิบายวิธีการแปลง ["foo": 1, "bar": 2] เป็น [("foo", 1), ("bar", 2)]
Maria Zverina

@MariaZverina ฉันไม่แน่ใจว่าคุณหมายถึงอะไร
มิก MacCallum

หากคุณสามารถทำการแปลงด้านบนตัวกรองสามารถนำไปใช้โดยตรงกับอาร์เรย์ผลลัพธ์
Maria Zverina

@MariaZverina Gotcha ใช่น่าเสียดายที่ฉันไม่ได้ตระหนักถึงวิธีการทำเช่นนั้น
มิก MacCallum

3

อ้างอิงจาก Swift Standard Library Reference แผนที่เป็นฟังก์ชันของอาร์เรย์ ไม่ใช่สำหรับพจนานุกรม

แต่คุณสามารถทำซ้ำพจนานุกรมของคุณเพื่อปรับเปลี่ยนคีย์:

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

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

3

ฉันกำลังมองหาวิธีแมปพจนานุกรมลงในอาร์เรย์ที่พิมพ์ด้วยวัตถุที่กำหนดเอง พบวิธีแก้ปัญหาในส่วนขยายนี้:

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

ใช้มันดังต่อไปนี้:

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

3

สวิฟท์ 3

ฉันลองวิธีง่ายๆใน Swift 3

ฉันต้องการแมป[String: String?]กับ[String: String]ฉันใช้ forEach แทน map หรือ flat map

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

ไม่เป็นที่พอใจเนื่องจากมันสร้างอาร์เรย์ใหม่และผนวกเข้ากับมัน
Steve Kuo

2

สวิฟท์ 5

ฟังก์ชั่นแผนที่ของพจนานุกรมมาพร้อมกับไวยากรณ์นี้

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

คุณสามารถตั้งค่าการปิดได้ที่นี่

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

ผลผลิตจะเป็น ::

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

มันอาจจะให้คำเตือนนี้ Result of call to 'map' is unused

จากนั้นตั้งค่า_ =ก่อนตัวแปร

อาจจะช่วยขอบคุณ


1

ฉันถือว่าปัญหาคือเก็บคีย์ของพจนานุกรมดั้งเดิมของเราและแมปค่าทั้งหมดในบางวิธี

ขอให้เราคุยและบอกว่าเราอาจต้องการที่จะ map ค่าทั้งหมดไปเป็นประเภทอื่น

ดังนั้นสิ่งที่เราต้องการเริ่มต้นคือพจนานุกรมและการปิด (ฟังก์ชัน) และจบลงด้วยพจนานุกรมใหม่ แน่นอนว่าปัญหาคือการพิมพ์ที่เข้มงวดของ Swift เข้ามาขวางทาง การปิดจะต้องระบุประเภทของสิ่งที่จะเข้ามาและประเภทที่ออกมาและดังนั้นเราจึงไม่สามารถสร้างวิธีการที่จะปิดทั่วไป - ยกเว้นในบริบทของทั่วไป ดังนั้นเราต้องการฟังก์ชั่นทั่วไป

วิธีแก้ปัญหาอื่นมุ่งเน้นที่การทำสิ่งนี้ภายในโลกทั่วไปของพจนานุกรมทั่วไปของตัวมันเอง แต่ฉันคิดว่ามันง่ายกว่าที่จะคิดในแง่ของฟังก์ชั่นระดับบนสุด ตัวอย่างเช่นเราสามารถเขียนสิ่งนี้โดยที่ K เป็นประเภทคีย์ V1 เป็นประเภทค่าของพจนานุกรมเริ่มต้นและ V2 เป็นประเภทค่าของพจนานุกรมสิ้นสุด:

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
}

นี่เป็นตัวอย่างง่ายๆของการเรียกมันเพียงเพื่อพิสูจน์ว่ายาสามัญช่วยแก้ไขตัวเองในเวลารวบรวม:

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

เราเริ่มด้วยพจนานุกรม[String:Int]และสิ้นสุดด้วยพจนานุกรมของ[String:String]โดยการเปลี่ยนค่าของพจนานุกรมแรกผ่านการปิด

(แก้ไข: ตอนนี้ฉันเห็นแล้วว่านี่เป็นวิธีเดียวกับวิธีแก้ปัญหาของ AirspeedVelocity ยกเว้นว่าฉันไม่ได้เพิ่มความงดงามเป็นพิเศษของตัวเริ่มต้นพจนานุกรมที่ทำให้ Dictionary ออกมาในลำดับ zip)


1

สวิฟท์ 3

การใช้งาน:

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

ส่วนขยาย:

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

ฉันคุณแค่พยายามแมปค่า (อาจเปลี่ยนประเภทของพวกเขา) รวมถึงส่วนขยายนี้:

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

ให้คุณมีพจนานุกรมนี้:

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

การแปลงค่าบรรทัดเดียวจะมีลักษณะดังนี้:

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

การแปลงค่าหลายบรรทัดจะมีลักษณะดังนี้:

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

อีกวิธีหนึ่งคือการmapใช้พจนานุกรมและreduceฟังก์ชันkeyTransformและvalueTransformฟังก์ชัน

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

0

Swift 3ฉันใช้สิ่งนี้

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
}

การใช้งาน:

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

อ้าง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.