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