Лучший способ проверить взаимное членство для набора перечислений
Есть ли хороший способ сгруппировать различные перечисления в наборы для проверки на взаимное членство, не прибегая к большому дублированию кода?
Например, ниже, когда я получаю .coldBeverage
я получил [.cola, .milk, .wine]
и аналогично, если я получу либо .cola
, .milk
, или же .wine
я получил .coldBeverage
,
enum BeverageType {
case coldBeverage
case hotBeverage
}
enum Beverage {
case cola
case milk
case wine
case coffee
case tea
case hotChocolate
}
Конечно, я всегда мог создать переменную для каждого перечисления и ввести взаимные отношения для каждого типа. Было просто любопытно, была ли какая-то другая структура.
т.е.
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...
}
}
2 ответа
Вы можете избежать дублирования, используя статический словарь в одном из перечислений:
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
}()
}
Этот подход не требует дублирования списка записей enum (в дополнение к отображению, которое вы должны сделать где-нибудь). Это также более эффективно, чем последовательный поиск, который может стать значимым для больших или часто используемых перечислений.
Статические переменные оцениваются только один раз, поэтому начиная со второго использования вы получаете выгоду от производительности словарей O(1) в обоих направлениях отношений.
Обратите внимание, что вы можете построить словари наоборот (например, от [BeverageType:[Beverage]] до [Beverage:BeverageType]), а также вы можете поместить статические переменные в каждое перечисление или все в перечисление BeverageType.
Я чувствовал, что напитки должны знать свой BeverageType и, скорее всего, будут расширены до новых напитков, поэтому я решил определить отношения в этом (много к одному) направлении.
Это можно даже обобщить, определив класс двунаправленного словаря (универсальный), который будет использоваться в этих ситуациях, чтобы код базовой платы для инвертированного словаря не загрязнял расширение.
[EDIT] С помощью двунаправленного словаря для отношения, определение становится еще чище:
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] ?? [] }
}
Вы можете использовать одно членство, чтобы определить другое:
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 }
}
}