Reputation: 273540
Let's say I have a model like this:
enum UKCountry : String {
case england, scotland, wales, northernIreland
init?(placeName: String) {
// magic happens here
}
}
struct LocalPopulation {
let place: String
let population: Int
}
The init(placeName:)
takes an arbitrary string and figures out where it is. For example init(placeName: "London")
gives .england
. How it does this is not important.
I also have a [LocalPopulation]
I want to process. Specifically, I want to get a [UKCountry: [LocalPopulation]]
. I could do this:
let populations: [LocalPopulation] = [...]
let dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place)! })
But not all places in the populations
is a place in the UK, so UKCountry.init
would return nil
, and it would crash. Note that I do not want any place that is not in the UK in the result. I could filter
it before hand:
let populations: [LocalPopulation] = [...]
let filteredPopulations = populations.filter { UKCountry(placeName: $0.place) != nil }
let dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place)! })
But that means running UKCountry.init
twice. Depending on how it is implemented, this could be costly. The other way I thought of is:
let populations: [LocalPopulation] = [...]
var dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place) })
dict[nil] = nil
let result = dict as! [UKCountry: [LocalPopulation]]
But that's a bit too "procedural"... What's more annoying is, in all the above attempts, I have used !
. I know sometimes !
is unavoidable, but I'd like to avoid it whenever I can.
How can I do this Swiftily?
Upvotes: 2
Views: 768
Reputation:
First, allow dictionaries to easily be created from grouped key-value pairs:
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) } )
}
}
Then, create a key-value sequence that's like KeyValuePairs<Key, Value.Element>
, where Key
and Value
belong to your desired [ UKCountry: [LocalPopulation] ]
type – but you don't need to bother with labeled elements.
Dictionary(
grouping: populations.compactMap { population in
UKCountry(placeName: population.place).map { ($0, population) }
}
)
Upvotes: 1
Reputation: 54755
You can use a reduce
to transform your [LocalPopulation]
into a [UKCountry:[LocalPopulation]]
by also filtering out all elements which aren't in the UK.
let dict = populations.reduce(into: [UKCountry:[LocalPopulation]](), { currentResult, population in
if let ukCountry = UKCountry(placeName: population.place) {
currentResult[ukCountry, default: []].append(population)
}
})
Upvotes: 0