MH175
MH175

Reputation: 2334

Best way to test mutual membership for set of enums

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

Answers (2)

Alain T.
Alain T.

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

Sulthan
Sulthan

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

Related Questions