测试一组枚举的相互成员资格的最佳方法

Best way to test mutual membership for set of enums

有没有一种好方法可以将不同的枚举分组到集合中以测试相互成员关系,而无需诉诸大量代码重复?

例如,在下面,当我得到 .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 
}

当然,我总是可以在每个枚举上创建一个 var,并输入每个类型的倒数关系。只是好奇是否还有其他结构。

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...
    }

}

您可以在其中一个枚举中使用静态字典来避免重复:

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[[=10=].1] = (beveragesByType[[=10=].1] ?? []) + [[=10=].0]}
      return beveragesByType
   }()       
}

此方法不需要复制枚举条目列表(除了映射,您必须在某处执行此操作)。它也比顺序搜索更有效,顺序搜索对于大型或频繁使用的枚举可能变得很重要。

静态变量只计算一次,因此从第二次使用开始,您将受益于字典在关系的两个方向上的 O(1) 性能。

请注意,您可以以相反的方式构建字典(即从 [BeverageType:[Beverage]] 到 [Beverage:BeverageType]),您还可以将静态变量放在每个枚举中或全部放在 BeverageType 枚举中.

我觉得饮料应该知道它们的 BeverageType 并且更有可能扩展到新饮料,所以我选择在那个(多对一)方向上定义关系。

这甚至可以通过定义一个双向字典(通用)class 来进一步概括,以便在这些情况下使用,这样倒排字典的样板代码就不会污染扩展。

[编辑] 使用关系的双向字典,定义变得更加清晰:

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[[=11=]] = 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 { [=10=].type == self }
    }
}