Массив массива объектов для словаря в 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

Хорошо, 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"]
69
19 июля '16 в 13:28
источник

В 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.

67
20 окт. '17 в 0:24
источник

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

40
08 мая '18 в 18:21
источник

Вы можете написать собственный инициализатор для типа 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
19 июля '16 в 13:06
источник

Вы можете использовать функцию уменьшения. Сначала я создал назначенный инициализатор для класса 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
}
1
19 июля '16 в 14:05
источник
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
    }
}
0
19 марта '19 в 15:18
источник

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

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})
0
01 июня '18 в 19:42
источник