Как сгруппировать по ключу, который может быть nil, и получить словарь с необязательным типом ключа? - PullRequest
1 голос
/ 31 марта 2020

Допустим, у меня есть такая модель:

enum UKCountry : String {
    case england, scotland, wales, northernIreland

    init?(placeName: String) {
        // magic happens here
    }
}

struct LocalPopulation {
    let place: String
    let population: Int
}

init(placeName:) берет произвольную строку и выясняет, где она находится. Например, init(placeName: "London") дает .england. Как это происходит, не важно.

У меня также есть [LocalPopulation], который я хочу обработать. В частности, я хочу получить [UKCountry: [LocalPopulation]]. Я мог бы сделать это:

let populations: [LocalPopulation] = [...]
let dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place)! })

Но не все места в populations - это места в Великобритании, поэтому UKCountry.init вернет nil, и будет sh. Обратите внимание, что я не хочу ни одного места, которое не находится в Великобритании в результате. Я мог бы filter сделать это заранее:

let populations: [LocalPopulation] = [...]
let filteredPopulations = populations.filter { UKCountry(placeName: $0.place) != nil }
let dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place)! })

Но это означает, что нужно запустить UKCountry.init дважды. В зависимости от того, как это реализовано, это может быть дорогостоящим. Другой способ, о котором я подумал:

let populations: [LocalPopulation] = [...]
var dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place) })
dict[nil] = nil
let result = dict as! [UKCountry: [LocalPopulation]]

Но это немного "процедурно" ... Что еще раздражает, во всех вышеупомянутых попытках я использовал !. Я знаю, что иногда ! неизбежно, но я бы хотел избежать этого, когда смогу.

Как я могу сделать это быстро?

Ответы [ 2 ]

1 голос
/ 31 марта 2020

Во-первых, можно легко создавать словари из сгруппированных пар ключ-значение:

public extension Dictionary {
  /// Group key-value pairs by their keys.
  ///
  /// - Parameter pairs: Either `Swift.KeyValuePairs<Key, Self.Value.Element>`
  ///   or a `Sequence` with the same element type as that.
  /// - Returns: `[ KeyValuePairs.Key: [KeyValuePairs.Value] ]`
  init<Value, KeyValuePairs: Sequence>(grouping pairs: KeyValuePairs)
  where
    KeyValuePairs.Element == (key: Key, value: Value),
    Self.Value == [Value]
  {
    self =
      Dictionary<Key, [KeyValuePairs.Element]>(grouping: pairs, by: \.key)
      .mapValues { $0.map(\.value) }
  }

  /// Group key-value pairs by their keys.
  ///
  /// - Parameter pairs: Like `Swift.KeyValuePairs<Key, Self.Value.Element>`,
  ///   but with unlabeled elements.
  /// - Returns: `[ KeyValuePairs.Key: [KeyValuePairs.Value] ]`
  init<Value, KeyValuePairs: Sequence>(grouping pairs: KeyValuePairs)
  where
    KeyValuePairs.Element == (Key, Value),
    Self.Value == [Value]
  {
    self.init( grouping: pairs.map { (key: $0, value: $1) } )
  }
}

( См. этот ответ для обеих перегрузок в действии .)

Затем создайте последовательность ключ-значение, такую ​​как KeyValuePairs<Key, Value.Element>, где Key и Value принадлежат к вашему желаемому типу [ UKCountry: [LocalPopulation] ], но вам не нужно беспокоиться о помеченных элементах.

Dictionary(
  grouping: populations.compactMap { population in
    UKCountry(placeName: population.place).map { ($0, population) }
  }
)
0 голосов
/ 31 марта 2020

Вы можете использовать reduce для преобразования [LocalPopulation] в [UKCountry:[LocalPopulation]], также отфильтровывая все элементы, которых нет в Великобритании.

let dict = populations.reduce(into: [UKCountry:[LocalPopulation]](), { currentResult, population in
    if let ukCountry = UKCountry(placeName: population.place) {
        currentResult[ukCountry, default: []].append(population)
    }
})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...