Mam tablicę Person
obiektów:
class Person {
let name:String
let position:Int
}
a tablica to:
let myArray = [p1,p1,p3]
Chcę mapować myArray
jako słownik [position:name]
klasycznego rozwiązania:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
Czy jest jakiś elegancki sposób przez Swift to zrobić z podejściem funkcjonalnym map
, flatMap
... lub inny nowoczesny styl Swift
ios
arrays
swift
dictionary
iOSGeek
źródło
źródło
[position:name]
czy[position:[name]]
? Jeśli masz dwie osoby na tej samej pozycji, Twój słownik zachowa tylko ostatnią napotkaną w Twojej pętli ... Mam podobne pytanie, na które próbuję znaleźć rozwiązanie, ale chcę, aby wynik był podobny[1: [p1, p3], 2: [p2]]
Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Odpowiedzi:
Okay
map
nie jest tego dobrym przykładem, ponieważ jest to to samo, co zapętlanie, możeszreduce
zamiast tego użyć, wymagało to połączenia każdego obiektu i przekształcenia go w pojedynczą wartość: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"]
W wersji Swift 4 lub nowszej użyj poniższej odpowiedzi, aby uzyskać jaśniejszą składnię.
źródło
Ponieważ
Swift 4
możesz wykonać podejście @ Tj3n bardziej czysto i wydajniej, używającinto
wersjireduce
It, pozbywa się tymczasowego słownika i wartości zwracanej, dzięki czemu jest szybszy i łatwiejszy do odczytania.Przykładowa konfiguracja kodu:
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)]
Into
parametr jest przekazywany pusty słownik typu wynikowego:let myDict = myArray.reduce(into: [Int: String]()) { $0[$1.position] = $1.name }
Bezpośrednio zwraca słownik typu przekazanego w
into
:print(myDict) // [2: "c", 0: "h", 4: "b"]
źródło
$0
reprezentuje: jest toinout
słownik, do którego „zwracamy” - chociaż tak naprawdę nieinout
powracamy , ponieważ modyfikowanie go jest równoważne zwracaniu z powodu jego -ości.Od wersji Swift 4 możesz to zrobić bardzo łatwo. Istnieją dwa nowe inicjatory, które tworzą słownik z sekwencji krotek (pary klucza i wartości). Jeśli gwarantujemy, że klucze są unikatowe, możesz wykonać następujące czynności:
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"]
Jeśli jakikolwiek klucz zostanie zduplikowany, spowoduje to błąd w czasie wykonywania. W takim przypadku możesz użyć tej wersji:
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"]
Zachowuje się jak pętla for. Jeśli nie chcesz „zastępować” wartości i trzymać się pierwszego mapowania, możesz użyć tego:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=>
[1: "Franz", 2: "Heinz"]
Swift 4.2 dodaje trzeci inicjator, który grupuje elementy sekwencji w słowniku. Klucze słownika są wyprowadzane przez zamknięcie. Elementy z tym samym kluczem są umieszczane w tablicy w tej samej kolejności, co w sekwencji. Pozwala to na osiągnięcie podobnych rezultatów jak powyżej. Na przykład:
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)]
źródło
[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]
. Nie jest to oczekiwany wynik, ale jeśli chcesz, można go dalej odwzorować na słownik płaski.Możesz napisać niestandardowy inicjator dla
Dictionary
typu, na przykład z krotek:extension Dictionary { public init(keyValuePairs: [(Key, Value)]) { self.init() for pair in keyValuePairs { self[pair.0] = pair.1 } } }
a następnie użyj
map
dla swojej tablicyPerson
:var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
źródło
A co z rozwiązaniem opartym na 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 } } }
Tak to wykorzystasz:
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"]
źródło
Możesz użyć funkcji zmniejszania. Najpierw utworzyłem wyznaczony inicjalizator dla klasy Person
class Person { var name:String var position:Int init(_ n: String,_ p: Int) { name = n position = p } }
Później zainicjowałem tablicę wartości
let myArray = [Person("Bill",1), Person("Steve", 2), Person("Woz", 3)]
I wreszcie zmienna słownikowa ma wynik:
let dictionary = myArray.reduce([Int: Person]()){ (total, person) in var totalMutable = total totalMutable.updateValue(person, forKey: total.count) return totalMutable }
źródło
To jest to, czego używałem
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}
Byłoby miło, gdyby istniał sposób na połączenie ostatnich 2 wierszy, tak aby
peopleByPosition
mógł to być pliklet
.Moglibyśmy stworzyć rozszerzenie Array, które to robi!
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 } }
Wtedy możemy po prostu zrobić
let peopleByPosition = persons.mapToDict(by: {$0.position})
źródło
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 } }
źródło
Może coś takiego?
myArray.forEach({ myDictionary[$0.position] = $0.name })
źródło