web-dev-qa-db-ja.com

Swiftの辞書にmap()を適用する最もクリーンな方法は何ですか?

辞書内のすべてのキーに関数をマップしたいと思います。次のようなものが機能することを期待していましたが、フィルタを辞書に直接適用することはできません。これを達成する最もクリーンな方法は何ですか?

この例では、各値を1ずつ増加させようとしています。ただし、これは例に付随するものです。主な目的は、map()を辞書に適用する方法を見つけることです。

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

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

Swift 4

良いニュースです! Swift 4には、同じキーで異なる値を持つ辞書のコピーを作成するmapValues(_:)メソッドが含まれています。また、Dictionaryを返すfilter(_:)オーバーロードと、タプルの任意のシーケンスからDictionaryを作成するinit(uniqueKeysWithValues:)およびinit(_:uniquingKeysWith:)初期化子も含まれています。つまり、キーと値の両方を変更する場合は、次のように言うことができます。

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

また、ディクショナリーをマージし、欠落している要素をデフォルト値に置き換え、値をグループ化する(コレクションを配列のディクショナリーに変換し、コレクションを何らかの関数にマッピングした結果をキーとする)などの新しいAPIもあります。

これらの機能を紹介した提案 SE-0165 の議論の中で、私はこのStack Overflowの回答を何度か取り上げましたが、膨大な数の賛成票が需要を示すのに役立ったと思います。 Swiftの改善にご協力いただきありがとうございます。

Swift 3

mapは、SequenceおよびCollectionの拡張メソッドです。次のように定義されます。

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

DictionaryCollectionに準拠しており、そのElement typealiasは(キー、値)タプルであるため、次のようなことができるはずです。

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

ただし、実際にはDictionary- typed変数には割り当てられません。 mapメソッドは、辞書などの他の型であっても、常に配列([T])を返すように定義されています。 2タプルの配列をDictionaryに変換するコンストラクターを作成すると、すべてが正しくなります。

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

コンストラクターの明示的な呼び出しを避けるために、mapの辞書固有バージョンを作成することもできます。ここには、filterの実装も含まれています。

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

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

別の名前を使用しました。そうしないと、戻り値のみが2つのメソッドを区別するため、コードが非常に曖昧になる可能性があります。

次のように使用します。

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

ちなみに、多くの目的のために、代わりに値のみをマッピングしたい場合があります。

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

その理由は、2つが同じキーにマップされると、mapPairsはペアを失う可能性があるためです。一方、map(Value -> OutValue)は常にキーを保持するため、これは危険ではありません。

241

Swift 5を使用すると、問題を解決するために5つのスニペットのいずれかを使用できます。


#1。 DictionarymapValues(_:)メソッドの使用

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。 Dictionarymapメソッドと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。 Dictionaryreduce(_:_:)メソッドまたは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。 Dictionarysubscript(_: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。 Dictionarysubscript(_:)添え字の使用

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]
35
Imanou Petit

ここでの回答のほとんどは、ディクショナリ全体をマップする方法(キーand値)に焦点を当てていますが、実際には質問は値のみをマップすることを望んでいました。これは重要な違いです。値をマッピングすると同じ数のエントリを保証できるのに対し、キーと値の両方をマッピングするとキーが重複する可能性があるためです。

以下は、変数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)))
    }

}
18

最もクリーンな方法は、単に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]
11
Joseph Mark

Mapの代わりに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]

これを拡張機能でラップするのはかなり簡単ですが、拡張機能がなくても、1回の関数呼び出しで必要なことを実行できます。

7
Aaron Rasmussen

これを行うことができます。あなたがしなければならないのは、辞書keysメソッドから返されたMapCollectionView<Dictionary<KeyType, ValueType>, KeyType>から配列を作成することです。 ( 情報はこちら )その後、この配列をマップし、更新された値を辞書に戻すことができます。

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

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

dictionary
4
Mick MacCallum

Swift St​​andard Library Referenceによると、mapは配列の関数です。辞書用ではありません。

ただし、辞書を反復処理してキーを変更できます。

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

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

私は、辞書をカスタムオブジェクトを持つ型付き配列に直接マッピングする方法を探していました。この拡張機能で解決策を見つけました。

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
Antoine

Swift

Swift 3.で簡単な方法を試します。

[String:String?][String:String]にマッピングしたい場合、マップまたはフラットマップの代わりにforEachを使用します。

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

Swift

使用法:

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
dimpiax

問題は元の辞書のキーを保持し、何らかの方法でそのすべての値をマップすることだと思います。

一般化して、すべての値をマッピングしたい場合があると言ってみましょう別の型に

したがって、最初に辞書とクロージャ(関数)を作成し、新しい辞書を作成します。問題は、もちろん、Swiftの厳密なタイピングが邪魔になることです。クロージャーは、どのタイプに入るか、どのタイプが出るかを指定する必要があるため、ジェネリックのコンテキストを除いて、generalクロージャーを取るメソッドを作成することはできません。したがって、汎用関数が必要です。

他のソリューションは、Dictionaryジェネリック構造体自体のジェネリックワールド内でこれを行うことに集中していますが、トップレベル関数の観点から考えると簡単です。たとえば、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]の辞書になりました。

編集

1
matt

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)

参照

0
Zaid Pathan

値のマッピング(タイプを変更する可能性があります)にしようとしているのは、この拡張機能を含めることです:

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
Dschee

別のアプローチは、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
}
0
NebulaFox