OptionSet (switch possible?)

Hi,

I have an OptionSet, and wanted to implement CustomStringConvertible

Presently I am using if statements with contains function which works.

I was wondering if there was a more elegant way to implement it. (like Switch or any other way)

Thanks.

import Foundation

struct Combination: OptionSet, CustomStringConvertible {
    
    let rawValue: Int
    
    static let option1  = Combination(rawValue: 1 << 0)
    static let option2  = Combination(rawValue: 1 << 1)
    static let option3  = Combination(rawValue: 1 << 2)
    
    var description: String {
        
        var string = String()
        
        if contains(.option1) {
            
            string += " option1"
        }
        
        if contains(.option1) {
            
            string += " option2"
        }
        
        if contains(.option3) {
            
            string += " option3"
        }
        
        return string
    }
}

let combination : Combination = [.option1, .option2]

print(combination)
1 Like

To use switch you would either have to loop:

    var description: String {
        var words: [String] = []
        for option: Combination in [.option1, .option2, .option3] {
            guard self.contains(option) else { continue }
            switch option {
            case .option1: words.append("option1")
            case .option2: words.append("option2")
            case .option3: words.append("option3")
            default: fatalError()
            }
        }
        return words.joined(separator: " ")
    }

Or list every possible combination:

    var description: String {
        switch self {
        case .option1: return "option1"
        case .option2: return "option2"
        case .option3: return "option3"
        case [.option1, .option2]: return "option1 option2"
        case [.option1, .option3]: return "option1 option3"
        case [.option2, .option3]: return "option2 option3"
        default: fatalError()
        }
    }

At the end of the day, your solution is simple, easy to understand and probably better than anything fancier.

This is definitely true, but if you want to have some fun anyway:

var description: String {
  return [(.option1, "option1"),
          (.option2, "option2"),
          (.option3, "option3")]
         .compactMap { (option, name) in contains(option) ? name : nil }
         .joined(separator: " ")
}

Also, there's a typo in the code in the original post (duplicate contains(.option1)).

Edit: And the second implementation above is missing the [.option1, .option2, .option3] case.

2 Likes

I think your current implementation is quite clear (as others have said). To use a switch you'll have to probably use a loop of some sort. This actually seems like a reduce would be useful.

extension OptionSet where RawValue: BinaryInteger, Element == Self  {
  func reduce<T>(_ r: T, _ f: (T, Self) -> T) -> T {
    var r = r
    var i = 0
    while i < rawValue.bitWidth {
      let option = Self.init(rawValue: rawValue & (1 << i))
      if contains(option) {
        r = f(r, option)
      }
      i += 1
    }
    return r
  }
}

var description: String {
  return reduce("") { r, b in
    switch b {
    case .option1: return r + " option1"
    case .option2: return r + " option2"
    case .option3: return r + " option3"
    default: return r
    }
  }
}

I wonder why some of these methods (map, filter, etc...) aren't available on OptionSet

You can iterate over the bits, but I am not sure if this is helpful or even a good idea:

public struct OptionSetIterator<Element: OptionSet>: IteratorProtocol where Element.RawValue: FixedWidthInteger {
    private let value: Element

    public init(element: Element) {
        self.value = element
    }

    private lazy var remainingBits = value.rawValue
    private var bitMask: Element.RawValue = 1

    public mutating func next() -> Element? {
        while remainingBits != 0 {
            defer { bitMask = bitMask &* 2 }
            if remainingBits & bitMask != 0 {
                remainingBits = remainingBits & ~bitMask
                return Element(rawValue: bitMask)
            }
        }
        return nil
    }
}

extension OptionSet where Self.RawValue: FixedWidthInteger {
    public func makeIterator() -> OptionSetIterator<Self> {
        return OptionSetIterator(element: self)
    }
}

Thanks a lot @nick.keets, @jawbroken, @Letan, @tkrajacic

Really interesting ideas, never knew there were so many possibilities !

And thanks for pointing out the typo and the suggestions