Массив массива объектов для словаря в Swift - программирование

Массив массива объектов для словаря в Swift

У меня есть массив объектов 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
}

Есть ли какой-либо элегантный способ Swift сделать это с помощью функционального подхода map, flatMap... или другого современного стиля Swift

4b9b3361

Ответ 1

Хорошо, 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"]

В Swift 4 или выше используйте приведенный ниже ответ для более ясного синтаксиса.

Ответ 2

В Swift 4 вы можете сделать @Tj3n более чисто и эффективно, используя into версию reduce. Он избавляется от временного словаря и возвращаемого значения, поэтому его быстрее и проще читать.

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.

Ответ 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"]

Это ведет себя как ваш цикл. Если вы не хотите "перезаписывать" значения и придерживаться первого сопоставления, вы можете использовать это:

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

Ответ 4

Вы можете написать собственный инициализатор для типа 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)})

Ответ 5

Вы можете использовать функцию уменьшения. Сначала я создал назначенный инициализатор для класса 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
}

Ответ 6

Может как то так?

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

Ответ 7

Это то, что я использовал

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

Ответ 8

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

Ответ 9

Как насчет решения на основе 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"]