辞書内のすべてのキーに関数をマップしたいと思います。次のようなものが機能することを期待していましたが、フィルタを辞書に直接適用することはできません。これを達成する最もクリーンな方法は何ですか?
この例では、各値を1ずつ増加させようとしています。ただし、これは例に付随するものです。主な目的は、map()を辞書に適用する方法を見つけることです。
var d = ["foo" : 1, "bar" : 2]
d.map() {
$0.1 += 1
}
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]
Dictionary
はCollection
に準拠しており、その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)
は常にキーを保持するため、これは危険ではありません。
Swift 5を使用すると、問題を解決するために5つのスニペットのいずれかを使用できます。
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]
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]
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]
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]
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]
ここでの回答のほとんどは、ディクショナリ全体をマップする方法(キー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)))
}
}
最もクリーンな方法は、単に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]
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回の関数呼び出しで必要なことを実行できます。
これを行うことができます。あなたがしなければならないのは、辞書keys
メソッドから返されたMapCollectionView<Dictionary<KeyType, ValueType>, KeyType>
から配列を作成することです。 ( 情報はこちら )その後、この配列をマップし、更新された値を辞書に戻すことができます。
var dictionary = ["foo" : 1, "bar" : 2]
Array(dictionary.keys).map() {
dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}
dictionary
Swift Standard Library Referenceによると、mapは配列の関数です。辞書用ではありません。
ただし、辞書を反復処理してキーを変更できます。
var d = ["foo" : 1, "bar" : 2]
for (name, key) in d {
d[name] = d[name]! + 1
}
私は、辞書をカスタムオブジェクトを持つ型付き配列に直接マッピングする方法を探していました。この拡張機能で解決策を見つけました。
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)
})
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
}
}
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
}
}
問題は元の辞書のキーを保持し、何らかの方法でそのすべての値をマップすることだと思います。
一般化して、すべての値をマッピングしたい場合があると言ってみましょう別の型に。
したがって、最初に辞書とクロージャ(関数)を作成し、新しい辞書を作成します。問題は、もちろん、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]
の辞書になりました。
編集
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)
値のマッピング(タイプを変更する可能性があります)にしようとしているのは、この拡張機能を含めることです:
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": ...
別のアプローチは、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
}