Cách sạch nhất để áp dụng map () vào từ điển trong Swift là gì?


154

Tôi muốn ánh xạ một chức năng trên tất cả các khóa trong từ điển. Tôi đã hy vọng một cái gì đó như sau sẽ hoạt động, nhưng bộ lọc không thể được áp dụng trực tiếp vào từ điển. Cách sạch nhất để đạt được điều này là gì?

Trong ví dụ này, tôi đang cố gắng tăng từng giá trị lên 1. Tuy nhiên, điều này là ngẫu nhiên cho ví dụ - mục đích chính là tìm ra cách áp dụng map () vào từ điển.

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

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

1
Từ điển không có chức năng bản đồ, vì vậy không rõ bạn đang cố gắng làm gì.
David Berry

7
Có - Tôi biết rằng Từ điển không có chức năng bản đồ. Câu hỏi có thể được nhắc lại là làm thế nào điều này có thể được thực hiện với các lần đóng mà không cần lặp lại toàn bộ từ điển.
Maria Zverina

2
bạn vẫn cần lặp lại toàn bộ từ điển vì đó là những gì mapnó làm. những gì bạn đang yêu cầu là một cách ẩn đi lặp lại đằng sau một phần mở rộng / đóng cửa để bạn không phải nhìn vào nó mỗi khi bạn sử dụng nó.
Joseph Mark

1
Đối với Swift 4, hãy xem câu trả lời của tôi hiển thị tối đa 5 cách khác nhau để giải quyết vấn đề của bạn.
Imanou Petit

Câu trả lời:


281

Swift 4+

Tin tốt! Swift 4 bao gồm một mapValues(_:)phương thức xây dựng một bản sao của một từ điển có cùng các khóa, nhưng các giá trị khác nhau. Nó cũng bao gồm một filter(_:)quá tải trả về một Dictionary, init(uniqueKeysWithValues:)và các bộ init(_:uniquingKeysWith:)khởi tạo để tạo ra một Dictionarychuỗi các bộ dữ liệu tùy ý. Điều đó có nghĩa là, nếu bạn muốn thay đổi cả khóa và giá trị, bạn có thể nói điều gì đó như:

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

Ngoài ra còn có các API mới để hợp nhất các từ điển lại với nhau, thay thế một giá trị mặc định cho các phần tử bị thiếu, nhóm các giá trị (chuyển đổi một bộ sưu tập thành một từ điển của các mảng, được khóa bởi kết quả của việc ánh xạ bộ sưu tập qua một số chức năng), v.v.

Trong cuộc thảo luận về đề xuất, SE-0165 , đã giới thiệu các tính năng này, tôi đã đưa ra câu trả lời Stack Overflow này nhiều lần và tôi nghĩ rằng số lượng upvote tuyệt đối đã giúp chứng minh nhu cầu. Vì vậy, cảm ơn sự giúp đỡ của bạn làm cho Swift tốt hơn!


6
Nó chắc chắn là không hoàn hảo, nhưng chúng ta không được để người hoàn hảo trở thành kẻ thù của điều tốt. Một mapcái có thể tạo ra các khóa xung đột vẫn hữu ích maptrong nhiều tình huống. (Mặt khác, bạn có thể lập luận rằng ánh xạ chỉ các giá trị là một cách giải thích tự nhiên maptrên từ điển, cả ví dụ của người gửi bài gốc và tôi chỉ sửa đổi các giá trị, và về mặt khái niệm, maptrên một mảng chỉ sửa đổi các giá trị, chứ không phải các chỉ số.)
Brent Royal-Gordon

2
khối mã cuối cùng của bạn dường như bị thiếu a try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim

9
Đã cập nhật cho Swift 2.1? BắtDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson

4
Với Swift 2.2 tôi sẽ nhận được Argument labels '(_:)' do not match any available overloadskhi thực hiện tiện ích mở rộng cuối cùng đó. Không thực sự chắc chắn làm thế nào để giải quyết nó: /
Erken

2
@Audioy Đoạn mã đó phụ thuộc vào trình Dictionarykhởi tạo được thêm vào một trong các ví dụ trước. Hãy chắc chắn rằng bạn bao gồm cả hai.
Brent Royal-Gordon

65

Với Swift 5, bạn có thể sử dụng một trong năm đoạn trích sau để giải quyết vấn đề của mình.


# 1. Sử dụng Dictionary mapValues(_:)phương pháp

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. Sử dụng Dictionary mapphương thức và trình init(uniqueKeysWithValues:)khởi tạo

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. Sử dụng Dictionary reduce(_:_:)phương pháp hoặc reduce(into:_:)phương pháp

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. Sử dụng Dictionary subscript(_:default:)chỉ mục

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. Sử dụng Dictionary subscript(_:)chỉ mục

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]

Xin ví dụ rằng thay đổi khóa không phải là giá trị. Cảm ơn nó hữu ích.
Yogesh Patel

18

Mặc dù hầu hết các câu trả lời ở đây tập trung vào cách ánh xạ toàn bộ từ điển (khóa giá trị), câu hỏi thực sự chỉ muốn ánh xạ các giá trị. Đây là một sự khác biệt quan trọng vì các giá trị ánh xạ cho phép bạn đảm bảo cùng số lượng mục nhập, trong khi ánh xạ cả khóa và giá trị có thể dẫn đến các khóa trùng lặp.

Đây là một phần mở rộng, mapValuescho phép bạn chỉ ánh xạ các giá trị. Lưu ý rằng nó cũng mở rộng từ điển với initmột chuỗi các cặp khóa / giá trị, tổng quát hơn một chút so với khởi tạo từ một mảng:

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

}

..Nó được sử dụng như thế nào? Tôi mới để nhanh chóng đưa ra một ví dụ ở đây?
FlowUI. SimpleUITesting.com

Khi tôi thử myDipedia.map, bên trong việc đóng cửa, tôi phải thực hiện một bản đồ khác theo cách thông thường. Điều này hoạt động, nhưng đây có phải là cách nó được sử dụng?
FlowUI. SimpleUITesting.com

Có đảm bảo ở đâu đó rằng thứ tự của các khóa & giá trị khi được gọi riêng được ghép nối và do đó phù hợp với zip không?
Aaron Zinman

Tại sao tạo một init mới khi bạn chỉ có thể sử dụng func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }. Tôi thấy nó đọc tốt hơn quá. Có một vấn đề hiệu suất?
Marcel

12

Cách sạch nhất là chỉ cần thêm mapvào Từ điển:

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

Kiểm tra xem nó hoạt động:

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)

Đầu ra:

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

1
Vấn đề tôi thấy với cách tiếp cận này là đó SequenceType.mapkhông phải là một chức năng đột biến. Ví dụ của bạn có thể được viết lại để tuân theo hợp đồng hiện tại map, nhưng đó cũng giống như câu trả lời được chấp nhận.
Christopher Pickslay

7

Bạn cũng có thể sử dụng reducethay vì bản đồ. reducecó khả năng làm bất cứ điều gì mapcó thể làm và nhiều hơn nữa!

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]

Sẽ khá dễ dàng để bọc phần mở rộng này, nhưng ngay cả khi không có phần mở rộng, nó cho phép bạn thực hiện những gì bạn muốn với một lệnh gọi.


10
Nhược điểm của phương pháp này là một bản sao hoàn toàn mới của từ điển được tạo ra mỗi khi bạn thêm khóa mới, do đó chức năng này không hiệu quả khủng khiếp (trình tối ưu hóa có thể giúp bạn tiết kiệm).
Vận tốc không khí

Đó là một điểm tốt ... một điều tôi chưa hiểu rõ lắm là nội bộ trong quản lý bộ nhớ của Swift. Tôi đánh giá cao những bình luận như thế này khiến tôi suy nghĩ về nó :)
Aaron Rasmussen

1
Không cần temp var và giảm bây giờ là một phương thức trong Swift 2.0:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey

4

Hóa ra bạn có thể làm điều này. Những gì bạn phải làm là tạo một mảng từ MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>trả về từ keysphương thức từ điển . ( Thông tin ở đây ) Sau đó, bạn có thể ánh xạ mảng này và chuyển các giá trị được cập nhật trở lại từ điển.

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

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

dictionary

Bạn có thể vui lòng thêm giải thích cách chuyển đổi ["foo": 1, "bar": 2] thành [("foo", 1), ("bar", 2)]
Maria Zverina

@MariaZverina Tôi không chắc ý của bạn là gì.
Mick MacCallum

Nếu bạn có thể thực hiện chuyển đổi ở trên, bộ lọc có thể được áp dụng trực tiếp vào mảng kết quả
Maria Zverina

@MariaZverina Gotcha. Thật không may, tôi không biết cách nào để làm điều đó.
Mick MacCallum

3

Theo Tài liệu tham khảo thư viện tiêu chuẩn Swift, bản đồ là một chức năng của mảng. Không dành cho từ điển.

Nhưng bạn có thể lặp lại từ điển của mình để sửa đổi các khóa:

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

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

3

Tôi đang tìm cách để ánh xạ một từ điển ngay vào một mảng được gõ với các đối tượng tùy chỉnh. Tìm thấy giải pháp trong phần mở rộng này:

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

Sử dụng nó như sau:

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

3

Swift 3

Tôi thử một cách dễ dàng trong Swift 3.

Tôi muốn để lập bản đồ [String:? Chuỗi] để [String: Chuỗi] , tôi sử dụng foreach thay vì bản đồ hoặc bản đồ phẳng.

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

Không mong muốn vì nó tạo ra một mảng mới và gắn vào nó
Steve Kuo

2

Swift 5

chức năng bản đồ của từ điển đi kèm với cú pháp này.

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

bạn chỉ có thể đặt giá trị đóng cho cái này

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

Đầu ra sẽ là ::

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

Nó có thể được đưa ra cảnh báo này Result of call to 'map' is unused

Sau đó chỉ cần đặt _ =trước biến.

Có thể nó sẽ giúp bạn Cảm ơn bạn.


1

Tôi giả sử vấn đề là giữ các khóa trong từ điển gốc của chúng tôi và ánh xạ tất cả các giá trị của nó theo một cách nào đó.

Hãy để chúng tôi khái quát và nói rằng chúng tôi có thể muốn ánh xạ tất cả các giá trị sang loại khác .

Vì vậy, những gì chúng tôi muốn bắt đầu là một từ điển và đóng (một chức năng) và kết thúc với một từ điển mới. Vấn đề là, tất nhiên, việc đánh máy nghiêm ngặt của Swift cản trở. Một bao đóng cần chỉ định loại nào đi vào và loại nào xuất hiện, và do đó chúng ta không thể tạo ra một phương thức có một bao đóng chung - ngoại trừ trong ngữ cảnh của một loại chung. Vì vậy, chúng ta cần một chức năng chung.

Các giải pháp khác đã tập trung vào việc thực hiện điều này trong thế giới chung của chính cấu trúc chung của Từ điển, nhưng tôi thấy dễ dàng hơn khi nghĩ về chức năng cấp cao nhất. Ví dụ: chúng ta có thể viết cái này, trong đó K là loại khóa, V1 là loại giá trị của từ điển bắt đầu và V2 là loại giá trị của từ điển kết thúc:

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
}

Đây là một ví dụ đơn giản về cách gọi nó, chỉ để chứng minh rằng cái chung thực sự tự giải quyết vào thời gian biên dịch:

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

Chúng tôi bắt đầu với một từ điển [String:Int]và kết thúc với một từ điển [String:String]bằng cách chuyển đổi các giá trị của từ điển đầu tiên thông qua việc đóng.

(EDIT: Bây giờ tôi thấy rằng điều này thực sự giống như giải pháp của AirspeedVelocity, ngoại trừ việc tôi không thêm tính thanh lịch của trình khởi tạo Từ điển tạo ra Từ điển trong chuỗi zip.)


1

Swift 3

Sử dụng:

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

Sự mở rộng:

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

Tôi chỉ đang cố gắng ánh xạ các giá trị (có khả năng thay đổi loại của chúng), bao gồm tiện ích mở rộng này:

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

Cho bạn có từ điển này:

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

Chuyển đổi giá trị một dòng sau đó trông như thế này:

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

Chuyển đổi giá trị nhiều dòng sau đó trông như thế này:

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

Một cách tiếp cận khác là vào mapmột từ điển và reduce, nơi các chức năng keyTransformvalueTransformlà các chức năng.

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
}

Đó là một khái niệm sau đó là mã thực tế, nhưng dù sao, tôi đã mở rộng nó thành mã chạy.
Tinh vân

0

Swift 3 Tôi đã sử dụng này,

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
}

Sử dụng:

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

Tham chiếu

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.