development

객체 배열을 Swift의 Dictionary에 매핑

big-blog 2020. 12. 13. 10:08
반응형

객체 배열을 Swift의 Dictionary에 매핑


Person의 개체 배열이 있습니다.

class Person {
   let name:String
   let position:Int
}

배열은 다음과 같습니다.

let myArray = [p1,p1,p3]

클래식 솔루션 myArray의 사전 이되도록 매핑하고 싶습니다 [position:name].

var myDictionary =  [Int:String]()

for person in myArray {
    myDictionary[person.position] = person.name
}

기능적 접근 방식 map, flatMap... 또는 다른 현대적인 Swift 스타일 을 사용하는 Swift의 우아한 방법이 있습니까?


Okay map는 이것의 좋은 예가 아닙니다. 루핑과 똑같기 때문에 reduce대신 사용할 수 있습니다. 각 객체를 결합하여 단일 값으로 변환해야합니다.

let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
    var dict = dict
    dict[person.position] = person.name
    return dict
}

//[2: "b", 3: "c", 1: "a"]

In 버전을 Swift 4사용하면 @ Tj3n의 접근 방식을 더 깨끗하고 효율적으로 할 수 있습니다 . 임시 사전과 반환 값을 제거하여 더 빠르고 쉽게 읽을 수 있습니다.intoreduce

struct Person { 
    let name: String
    let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]
let myDict = myArray.reduce(into: [Int: String]()) {
    $0[$1.position] = $1.name
}
print(myDict)

참고 : Swift 3에서는 작동하지 않습니다.


Swift 4 부터는 이것을 매우 쉽게 할 수 있습니다. 있다 이 개 새로운 튜플의 순서에서 (키와 값의 쌍을) 사전을 구축 이니셜. 키가 고유하다고 보장되는 경우 다음을 수행 할 수 있습니다.

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })

=> [1: "Franz", 2: "Heinz", 3: "Hans"]

키가 중복되면 런타임 오류와 함께 실패합니다. 이 경우이 버전을 사용할 수 있습니다.

let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 1)]

Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }

=> [1: "Hans", 2: "Heinz"]

이것은 for 루프로 작동합니다. 값을 "덮어 쓰지"않고 첫 번째 매핑을 고수하지 않으려면 다음을 사용할 수 있습니다.

Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }

=> [1: "Franz", 2: "Heinz"]

Swift 4.2시퀀스 요소를 사전으로 그룹화 하는 세 번째 이니셜 라이저를 추가합니다 . 사전 키는 클로저에 의해 파생됩니다. 동일한 키를 가진 요소는 시퀀스와 동일한 순서로 배열에 배치됩니다. 이를 통해 위와 유사한 결과를 얻을 수 있습니다. 예를 들면 :

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }

=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }

=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]


Dictionary예를 들어 튜플에서 유형에 대한 사용자 정의 이니셜 라이저를 작성할 수 있습니다 .

extension Dictionary {
    public init(keyValuePairs: [(Key, Value)]) {
        self.init()
        for pair in keyValuePairs {
            self[pair.0] = pair.1
        }
    }
}

다음 map배열에 사용 하십시오 Person.

var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})

축소 기능을 사용할 수 있습니다. 먼저 Person 클래스에 대해 지정된 이니셜 라이저를 만들었습니다.

class Person {
  var name:String
  var position:Int

  init(_ n: String,_ p: Int) {
    name = n
    position = p
  }
}

나중에 값 배열을 초기화했습니다.

let myArray = [Person("Bill",1), 
               Person("Steve", 2), 
               Person("Woz", 3)]

마지막으로 사전 변수의 결과는 다음과 같습니다.

let dictionary = myArray.reduce([Int: Person]()){
  (total, person) in
  var totalMutable = total
  totalMutable.updateValue(person, forKey: total.count)
  return totalMutable
}

아마 이런 건가요?

myArray.forEach({ myDictionary[$0.position] = $0.name })

이것은 내가 사용하고있는 것입니다

struct Person {
    let name:String
    let position:Int
}
let persons = [Person(name: "Franz", position: 1),
               Person(name: "Heinz", position: 2),
               Person(name: "Hans", position: 3)]

var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}

그 때문에 지난 2 개 라인을 결합 할 수있는 방법이 있다면 좋을 것이다 peopleByPosition될 수 있습니다 let.

그렇게하는 Array를 확장 할 수 있습니다!

extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

그럼 우리는 할 수 있습니다

let peopleByPosition = persons.mapToDict(by: {$0.position})

extension Array {
    func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
        var map = [T: Element]()
        self.forEach{ map[block($0)] = $0 }
        return map
    }
}

KeyPath 기반 솔루션은 어떻습니까?

extension Array {

    func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
        return reduce(into: [:]) { dictionary, element in
            let key = element[keyPath: key]
            let value = element[keyPath: value]
            dictionary[key] = value
        }
    }
}

사용 방법은 다음과 같습니다.

struct HTTPHeader {

    let field: String, value: String
}

let headers = [
    HTTPHeader(field: "Accept", value: "application/json"),
    HTTPHeader(field: "User-Agent", value: "Safari"),
]

let allHTTPHeaderFields = headers.dictionary(withKey: \.field, value: \.value)

// allHTTPHeaderFields == ["Accept": "application/json", "User-Agent": "Safari"]

참고 URL : https://stackoverflow.com/questions/38454952/map-array-of-objects-to-dictionary-in-swift

반응형