Swift의 사전에 map ()을 적용하는 가장 깨끗한 방법은 무엇입니까?


154

사전의 모든 키에 함수를 매핑하고 싶습니다. 다음과 같은 것이 효과가 있기를 희망했지만 필터를 사전에 직접 적용 할 수는 없습니다. 이것을 달성하는 가장 깨끗한 방법은 무엇입니까?

이 예제에서는 각 값을 1 씩 늘리려 고합니다. 그러나이 예제에서는 부수적입니다. 주 목적은 map ()을 사전에 적용하는 방법을 알아내는 것입니다.

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 가지 방법을 보여주는 내 대답 을 참조하십시오 .
이마 노우 프티

답변:


281

스위프트 4+

좋은 소식! Swift 4에는 mapValues(_:)동일한 키는 있지만 값이 다른 사전의 사본을 구성 하는 방법이 포함되어 있습니다. 그것은 또한 포함 filter(_:)반환 과부하 Dictionaryinit(uniqueKeysWithValues:)init(_:uniquingKeysWith:)을 만들 초기화를 Dictionary튜플 임의의 순서로한다. 즉, 키와 값을 모두 변경하려면 다음과 같이 말할 수 있습니다.

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

누락 된 요소에 대한 기본값을 대체하고, 값을 그룹화 (컬렉션을 배열의 사전으로 변환, 일부 함수를 통해 콜렉션을 맵핑 한 결과로 키가 지정된) 등으로 사전을 병합하는 새로운 API도 있습니다.

이러한 기능을 소개 한 제안서 SE-0165에 대해 토론 하면서이 스택 오버플로 답변을 여러 번 제기했으며, 많은 수의 업 보트가 수요를 입증하는 데 도움이되었다고 생각합니다. Swift를 개선하는 데 도움을 주셔서 감사합니다.


6
그것은 확실히 불완전하지만, 우리는 완전이 선의 원수가되게해서는 안됩니다. map충돌하는 키를 생성 할 수 있는 A 는 여전히 map많은 상황에서 유용 합니다. 반면에, 값만 매핑하는 것은 map사전에 대한 자연스러운 해석이라고 주장 할 수 있습니다. 원본 포스터의 예제와 광산은 값만 수정하고 개념적 map으로는 배열이 아닌 값만 수정합니다.
브렌트 로얄-고든

2
마지막 코드 블록에 누락 된 것 같습니다 try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim 0시 19 분

9
스위프트 2.1 업데이트? GetDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson

4
Swift 2.2 Argument labels '(_:)' do not match any available overloads에서는 마지막 확장을 구현할 때 얻고 있습니다. 그것을 해결하는 방법을 잘 모르겠다 : /
Erken

2
@Audioy이 코드 조각은 Dictionary이전 예제 중 하나에 추가 된 이니셜 라이저에 따라 다릅니다 . 둘 다 포함 시키십시오.
브렌트 로얄 고든

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]

#삼. 사용 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)))
    }

}

.. 어떻게 사용됩니까? swift Mind를 처음 접한 적이 있습니까?
FlowUI. SimpleUITesting.com

클로저 내부에서 myDictionary.map을 시도했을 때 정기적으로 다른 맵을 수행해야했습니다. 이것은 효과가 있지만 이것이 사용되는 방식입니까?
FlowUI. SimpleUITesting.com

별도로 호출 할 때 키와 값의 순서가 쌍을 이루어 지퍼에 적합하다는 보장이 있습니까?
Aaron Zinman

그냥 사용할 수있을 때 왜 새로운 초기화를 작성하십시오 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
Swift 2.0에서는 temp var 및 reduce가 필요하지 않습니다.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)]로 변환하는 방법에 대한 설명을 추가해 주
시겠습니까?

@MariaZverina 무슨 말인지 잘 모르겠습니다.
Mick MacCallum

위의 변환을 수행 할 수 있으면 필터를 결과 배열에 직접 적용 할 수 있습니다.
Maria Zverina

@MariaZverina Gotcha. 불행히도 나는 그것을 할 수있는 방법을 모른다.
Mick MacCallum 2018 년

3

Swift Standard Library Reference에 따르면 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에서 쉬운 방법을 시도합니다.

나는 매핑 할 : [문자열? 문자열][문자열 : 문자열] , 내가 대신지도 또는 평면지도의 대해 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의 엄격한 타이핑이 방해가된다는 것입니다. 클로저는 어떤 타입이 들어가고 어떤 타입이 나올지를 지정해야하므로, 일반적인 문맥을 제외하고 는 일반적인 클로저 를 취하는 메소드를 만들 수 없습니다 . 따라서 일반적인 함수가 필요합니다.

다른 솔루션은 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].

(편집 : 이제는 AirspeedVelocity의 솔루션과 사실상 동일하다는 것을 알았습니다. 단, 사전을 우편 번호에서 제외시키는 사전 초기화 프로그램의 우아함을 추가하지 않았습니다.)


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여기서 함수 keyTransformvalueTransform함수는입니다.

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

스위프트 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.