Swiftの辞書にmap()を適用する最もきれいな方法は何ですか?


154

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

この例では、各値を1ずつインクリメントしようとしていますが、これは例では偶然です。主な目的は、map()を辞書に適用する方法を理解することです。

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

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

1
辞書にはマップ機能がないため、何をしようとしているのか明確ではありません。
David Berry

7
はい-辞書にはマップ機能がないことを知っています。質問は、これを辞書全体を反復する必要なしに、クロージャーを使用してどのように達成できるかと言い換えることができます。
マリアズヴェリーナ2014年

2
それはa mapが行うことなので、辞書全体を繰り返し処理する必要があります。あなたが求めているのは、拡張/クロージャーの背後にあるイテレーションを隠す方法ですので、使用するたびにそれを見る必要はありません。
ジョセフマーク

1
Swift 4について、問題を解決するための最大5つの異なる方法を示す私の回答を参照しください。
Imanou Petit 2017

回答:


281

スウィフト4+

朗報!Swift 4には、mapValues(_:)キーは同じで値が異なる辞書のコピーを作成するメソッドが含まれています。それはまた、filter(_:)戻り過負荷Dictionary、及びinit(uniqueKeysWithValues:)及びinit(_:uniquingKeysWith:)作成するイニシャライザDictionaryタプルの任意の配列から。つまり、キーと値の両方を変更したい場合は、次のように言うことができます。

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

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

これらの機能を導入した提案であるSE-0165の議論中に、私はこのスタックオーバーフローの回答を数回取り上げました。Swiftの改善にご協力いただきありがとうございます。


6
それは確かに不完全ですが、完璧を善の敵にしてはなりません。A map矛盾キーを作り出すことができるなお有用であるmap多くの状況インチ (一方、値だけをマッピングすることは、map辞書では自然な解釈であると主張できます。元のポスターの例とマイニングの両方が値を変更するだけであり、概念的にmapは、配列は値のみを変更し、インデックスは変更しません。)
ブレントロイヤルゴルドン

2
最後のコードブロックに次のように欠落しているようですtryreturn 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
エリクソンによる

4
Swift 2.2 Argument labels '(_:)' do not match any available overloadsでは、最後の拡張機能を実装するときに取得しています。それを解決する方法が本当にわからない:/
Erken

2
@AudioyそのコードスニペットDictionaryは、前の例の1つで追加された初期化子に依存します。両方を含めるようにしてください。
ブレントロイヤルゴルドン

65

Swift 5では、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 2016

個別に呼び出されたときのキーと値の順序がペアになっていて、zipに適しているという保証はどこかにありますか?
アーロンジンマン2017年

だけを使用できるのに、なぜ新しい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 }。私はそれも読みやすいと思います。パフォーマンスの問題はありますか?
マルセル

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 2015年

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]

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


10
このアプローチの欠点は、新しいキーを追加するたびに辞書の新しいコピーが作成されるため、この機能は恐ろしく非効率的です(オプティマイザ節約できる可能性があります)。
対気速度速度

それは良い点です...私がまだよく理解していないことの1つは、Swiftのメモリ管理の内部です。私はそれについて私にそれを考えさせるこのようなコメントに感謝します:)
アーロン・ラスムセン

1
一時VARは必要と削減は今スウィフト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>、dictionaries 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どういう意味かわかりません。
Mick MacCallum 14年

上記の変換を実行できる場合、フィルターを結果の配列に直接適用できます
Maria Zverina

@MariaZverina Gotcha。残念ながら、私はその方法を知りません。
Mick MacCallum、2014年

3

Swift標準ライブラリリファレンスによると、mapは配列の関数です。辞書用ではありません。

しかし、辞書を反復してキーを変更することができます。

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を使用しています。

    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

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

一般化して、すべての値を別の型にマップしたい場合があるとしましょう。

まず、辞書とクロージャー(関数)から始めて、新しい辞書を作成します。もちろん問題は、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]クロージャを介して第1の辞書の値を変換することによって。

(編集:これはAirspeedVelocityのソリューションと実質的に同じであることがわかりますが、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

もう1つのアプローチは、関数とが関数でmapある辞書とへのアプローチです。reducekeyTransformvalueTransform

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.