2016-02-21 14 views
27

Chcę dostać z tej tablicy ciągówCzy istnieje deklaratywny sposób przekształcania tablicy w słownik?

let entries = ["x=5", "y=7", "z=10"] 

do tego

let keyValuePairs = ["x" : "5", "y" : "7", "z" : "10"] 

Próbuję użyć map ale problem wydaje się, że kluczowym - para wartości w słowniku nie jest odrębny typ, jest po prostu w moim umyśle, ale nie w typie Słownika, więc nie mogłem w rzeczywistości zapewnić funkcji transformacji, ponieważ nie ma się czego przekształcać. Plus map zwraca tablicę, więc nie da się.

Wszelkie pomysły?

+0

Jeśli ciągi w tablicy są zawsze w tym samym formacie, można przeanalizować wartość klucza, '=' i wartość oraz przypisać te dane do słownika. – Arc676

+0

@ Arc676 to imperatywne podejście, na pewno, faktycznie już to mam ... ale szukam deklaratywnego rozwiązania. –

Odpowiedz

47

Swift 4

Jak wspomniano przez fl034, to można uprościć niektóre z Swift 4, gdzie sprawdzane błąd wersja wygląda następująco:

let foo = entries 
    .map { $0.components(separatedBy: "=") } 
    .reduce(into: [String:Int64]()) { dict, pair in 
     if pair.count == 2, let value = Int64(pair[1]) { 
      dict[pair[0]] = value 
     } 
    } 

jeszcze prostsze, jeśli nie chcą wartości jak Ints:

let foo = entries 
    .map { $0.components(separatedBy: "=") } 
    .reduce(into: [String:String]()) { dict, pair in 
     if pair.count == 2 { 
      dict[pair[0]] = pair[1] 
     } 
    } 

Starsze TL; DR

Minus sprawdzanie błędów, to loo jaw dość dużo jak:

let foo = entries.map({ $0.componentsSeparatedByString("=") }) 
    .reduce([String:Int]()) { acc, comps in 
     var ret = acc 
     ret[comps[0]] = Int(comps[1]) 
     return ret 
    } 

Korzystanie mapie, aby włączyć [String] w ułamku się [[String]] a następnie zbudować słownika [String:Int] że korzystając ze zmniejszenia.

Albo, dodając rozszerzenie Dictionary:

extension Dictionary { 
    init(elements:[(Key, Value)]) { 
     self.init() 
     for (key, value) in elements { 
      updateValue(value, forKey: key) 
     } 
    } 
} 

(Bardzo przydatne rozszerzenie btw, można go używać do wielu operacji map/filtracyjnych na słowniki, naprawdę rodzaj wstydu nie robi” t istnieje domyślnie)

staje się jeszcze prostsze:

let dict = Dictionary(elements: entries 
    .map({ $0.componentsSeparatedByString("=") }) 
    .map({ ($0[0], Int($0[1])!)}) 
) 

oczywiście, można również połączyć dwa połączenia map, ale wolę, aby rozbić individu al przekształca.

Jeśli chcesz dodać sprawdzanie błędów, flatMap może być stosowany zamiast map:

let dict2 = [String:Int](elements: entries 
    .map({ $0.componentsSeparatedByString("=") }) 
    .flatMap({ 
     if $0.count == 2, let value = Int($0[1]) { 
      return ($0[0], value) 
     } else { 
      return nil 
     }}) 
) 

Ponownie, jeśli chcesz, możesz oczywiście połączyć map do flatMap lub podzielić je na prostotę.

let dict2 = [String:Int](elements: entries.flatMap { 
    let parts = $0.componentsSeparatedByString("=") 
    if parts.count == 2, let value = Int(parts[1]) { 
     return (parts[0], value) 
    } else { 
     return nil 
    }} 
) 
+0

Z ciekawości, jakie są skróty "acc", "comps' i" ret "? –

+2

kumulacja, komponenty i zwrot –

+0

Spójrz na moją odpowiedź, ponieważ Swift 4 czyni to wszystko przestarzałym – fl034

9

Jednym ze sposobów na to jest w dwóch etapach z map i reduce z krotce, jako wartość pośrednia, na przykład:

let entries = ["x=5", "y=7", "z=10"] 

let dict = entries.map { (str) -> (String, String) in 
    let elements = str.characters.split("=").map(String.init) 
    return (elements[0], elements[1]) 
    }.reduce([String:String]()) { (var dict, kvpair) in 
     dict[kvpair.0] = kvpair.1 
     return dict 
} 

for key in dict.keys { 
    print("Value for key '\(key)' is \(dict[key]).") 
} 

Wyjścia:

Value for key 'y' is Optional("7"). 
Value for key 'x' is Optional("5"). 
Value for key 'z' is Optional("10"). 

lub z pojedynczym reduce z ta sama moc wyjściowa:

let entries = ["x=5", "y=7", "z=10"] 

let dict = entries.reduce([String:String]()) { (var dict, entry) in 
    let elements = entry.characters.split("=").map(String.init) 
    dict[elements[0]] = elements[1] 
    return dict 
} 

for key in dict.keys { 
    print("Value for key '\(key)' is \(dict[key]).") 
} 
+0

Bardzo dobrze. Zwróć uwagę, że 'str.characters.split {$ 0 ==" = "}' może być również zapisane jako 'str.characters.split (" = ")' –

+0

Również w pierwszym zamknięciu mapy, nie musisz zadeklarować 'var' the' str' param. Może to być po prostu 'entries.map {(str) -> (String, String) w' –

+1

@appzYourLife: Dobre punkty, edytowane, dziękuję. –

1

Lubię @paulgriffiths odpowiedź, ale jeśli masz ekstremalnych niechęć do Runtime błędy, można wziąć go o krok dalej, aby zapewnić każdy ciąg w początkowym tablicy rzeczywiście ma zarówno wymaganych elementów ...

Istotną różnicą w tym kodzie w porównaniu z innymi jest to, że sprawdzam, czy rzeczywiście istnieje "=" w ciągu znaków z elementami po obu stronach. Model flatMap skutecznie odfiltrowuje wszystkie awarie.

extension Dictionary { 
    func withUpdate(key: Key, value: Value) -> Dictionary<Key, Value> { 
     var result = self 
     result[key] = value 
     return result 
    } 
} 

let entries = ["x=5", "y=7", "z=10"] 

let keyValues = entries.flatMap { str -> (String, String)? in 
    let split = str.characters.split("=").map(String.init) 
    return split.count > 1 ? (split[0], split[1]) : nil 
} 

let keyValuePairs = keyValues.reduce([String: String]()) { $0.withUpdate($1.0, value: $1.1) } 
0

I dla każdego, kto lubi przeciążenia i jednej wkładki.

public func +<K, V>(lhs: [K:V], rhs: [K:V]) -> [K:V] { 
    var lhs: [K:V] = lhs 
    for (key, value) in rhs { 
     lhs[key] = value 
    } 
    return lhs 
} 

let array = ["x=5", "y=7", "z=10"] 
let dictionary = array.map({ $0.componentsSeparatedByString("=") }).reduce([:]) { $0 + [$1[0]: $1[1]] } 

print(dictionary) // ["y": "7", "x": "5", "z": "10"] 
3

Dobre odpowiedzi już. Oto dodatkowy sposób z rozszerzeniem typu kolekcji. Możesz przekonwertować dowolny typ kolekcji na słownik, tablicę lub zestaw.

extension CollectionType { 
    func map2Dict<K, V>(@noescape map: ((Self.Generator.Element) -> (K, V)?)) -> [K: V] { 
     var d = [K: V]() 
     for e in self { 
      if let kV = map(e) { 
       d[kV.0] = kV.1 
      } 
     } 

     return d 
    } 

    func map2Array<T>(@noescape map: ((Self.Generator.Element) -> (T)?)) -> [T] { 
     var a = [T]() 
     for e in self { 
      if let o = map(e) { 
       a.append(o) 
      } 
     } 

     return a 
    } 

    func map2Set<T>(@noescape map: ((Self.Generator.Element) -> (T)?)) -> Set<T> { 
     return Set(map2Array(map)) 
    } 
} 

Oto przykład użycia.

let entries = ["x=5", "y=7", "z=10"] 
let dict:[String: String] = entries.map2Dict({ 
let components = $0.componentsSeparatedByString("=") 
    return (components.first!, components.last!) 
}) 

print("dict \(dict)") 
+0

To tworzy nową tablicę i dołącza do niej, niezbyt funkcjonalne i nie może być leniwym. –

0

Jeśli masz dwie równoległe tablice, można użyć zip(), Array() i David Berry's Dictionary extension:

let numbers = [10, 9, 8] 
let strings = ["one", "two", "three", "four"] 

let dictionary = Dictionary(elements: Array(zip(numbers, strings))) 
// == [10: "one", 9: "two", 8: "three"] 

Nie zapomnij dodać rozszerzenie:

extension Dictionary { 
    init(elements:[(Key, Value)]) { 
     self.init() 
     for (key, value) in elements { 
      updateValue(value, forKey: key) 
     } 
    } 
} 
0

Prosty sposób:

let array = ["key1", "key2", "key3"] 
let dict = array.flatMap({["\($0)" : "value"]}) 

wyjściowa:

[(klucz: "klucz1", o wartości: "wartość"), (klucz: "klucz2", o wartości: "wartość"), (klucz: "Key3", wartość " wartość ")]


nieco bardziej złożony sposób:

let array = ["key1", "key2", "key3"] 
let dict = array.enumerated().flatMap({["\($0.element)" : "value\($0.offset)"]}) 

wyjściowa:

[(klawisz "klucz1" wartość "value0"), (klawisz "klucz2" wartość "wartosc1"), (klucz: "Key3" wartość "wartość2")]

5

Zastosowanie Swift 4 nowego reduce(into: Result) metoda:

let keyValuePairs = entries.reduce(into: [String:String]()) { (dict, entry) in 
    let key = String(entry.first!) 
    let value = String(entry.last!) 
    dict[entry.first!] = entry.last! 
} 

oczywiście, podział ciąg mogłaby być lepsza.

Powiązane problemy