Reputation: 2334
Is there a good way to group different enums into sets to test for mutual membership, without resorting to a lot of code duplication?
For example, below, when I get a .coldBeverage
I get [.cola, .milk, .wine]
, and likewise if I get either .cola
, .milk
, or .wine
I get .coldBeverage
.
enum BeverageType {
case coldBeverage
case hotBeverage
}
enum Beverage {
case cola
case milk
case wine
case coffee
case tea
case hotChocolate
}
Of course, I could always create a var on each enum, and enter the reciprocal relationship for each type. Was just curious if there was some other structure.
i.e.
extension BeverageType {
var associatedBeverages: [Beverage] {
switch self {
case .coldBeverage:
return [.cola, .milk, .wine]
case .hotBeverage:
return [.coffee, .tea, .hotChocolate]
}
}
}
extension Beverage {
var beverageType: BeverageType {
switch self:
case .cola:
return .coldBeverage
case .milk:
return .coldBeverage
//etc...
}
}
Upvotes: 0
Views: 108
Reputation: 42139
You could avoid the duplication using a static dictionary in one of the enums:
extension BeverageType
{
var associatedBeverages:[Beverage] { return Beverage.associatedBeverages[self]! }
}
extension Beverage
{
var beverageType:BeverageType { return Beverage.beverageTypes[self]! }
static var beverageTypes:[Beverage:BeverageType]
= [
.cola : .coldBeverage,
.milk : .coldBeverage,
.wine : .coldBeverage,
.coffee : .hotBeverage,
.tea : .hotBeverage,
.hotChocolate : .hotBeverage
]
static var associatedBeverages:[BeverageType:[Beverage]] =
{
var beveragesByType:[BeverageType:[Beverage]] = [:]
Beverage.beverageTypes.forEach
{beveragesByType[$0.1] = (beveragesByType[$0.1] ?? []) + [$0.0]}
return beveragesByType
}()
}
This approach does not require duplicating the list of enum entries (in addition to the mapping, which you have to do somewhere). It is also more efficient than a sequential search which could become significant for large or frequently used enums.
The static variables are evaluated only once, so from the second use onward, you benefit from the O(1) performance of dictionaries in both direction of the relationship.
Note that you could build the dictionaries the other way around (i.e. from [BeverageType:[Beverage]] to [Beverage:BeverageType]) and you could also place the static variables in each enum or all in the BeverageType enum.
I felt that beverages should know their BeverageType and are more likely to be expanded to new drinks so I chose to define the relationship in that (many to one) direction.
This could even be generalized further by defining a bidirectional Dictionary (generic) class to use in these situations so that the boiler plate code for the inverted dictionary doesn't pollute the extension.
[EDIT] With a bidirectional dictionary for the relation, the definition becomes even cleaner:
extension BeverageType
{
var associatedBeverages:[Beverage] { return Beverage.beverageTypes[self] }
}
extension Beverage
{
var beverageType:BeverageType { return Beverage.beverageTypes[self]! }
static var beverageTypes = ManyToOne<Beverage,BeverageType>(
[
.coldBeverage : [.cola, .milk, .wine],
.hotBeverage : [.coffee, .tea, .hotChocolate]
])
}
struct ManyToOne<M:Hashable,O:Hashable>
{
var manyToOne:[M:O] = [:]
var oneToMany:[O:[M]] = [:]
init( _ m2o:[M:O] )
{
manyToOne = m2o
for (many,one) in m2o { oneToMany[one] = (oneToMany[one] ?? []) + [many] }
}
init( _ o2m:[O:[M]])
{
oneToMany = o2m
for (one,many) in o2m { many.forEach{ manyToOne[$0] = one } }
}
subscript(many:M) -> O? { return manyToOne[many] }
subscript(one:O) -> [M] { return oneToMany[one] ?? [] }
}
Upvotes: 1
Reputation: 130191
You can use one membership to define the other:
extension Beverage {
static var beverages: [Beverage] {
return [.cola, .milk, .wine, .coffee, .tea, .hotChocolate]
}
var type: BeverageType {
switch self {
case .cola, .milk, .wine:
return .coldBeverage
case .coffee, .tea, .hotChocolate:
return .hotBeverage
}
}
}
extension BeverageType {
var associatedBeverages: [Beverage] {
return Beverage.beverages.filter { $0.type == self }
}
}
Upvotes: 0