Florian
Florian

Reputation: 5405

How do you enumerate OptionSetType in Swift?

I have a custom OptionSetType struct in Swift. How can I enumerate all values of an instance?

This is my OptionSetType:

struct WeekdaySet: OptionSetType {
    let rawValue: UInt8

    init(rawValue: UInt8) {
        self.rawValue = rawValue
    }

    static let Sunday        = WeekdaySet(rawValue: 1 << 0)
    static let Monday        = WeekdaySet(rawValue: 1 << 1)
    static let Tuesday       = WeekdaySet(rawValue: 1 << 2)
    static let Wednesday     = WeekdaySet(rawValue: 1 << 3)
    static let Thursday      = WeekdaySet(rawValue: 1 << 4)
    static let Friday        = WeekdaySet(rawValue: 1 << 5)
    static let Saturday      = WeekdaySet(rawValue: 1 << 6)
}

I would like to something like this:

let weekdays: WeekdaySet = [.Monday, .Tuesday]
for weekday in weekdays {
    // Do something with weekday
}

Upvotes: 46

Views: 13151

Answers (5)

Alexander Ignatov
Alexander Ignatov

Reputation: 21

Using the approach made in this mini-library (https://github.com/allexks/Options) you can just make your weekdays of a regular enum type:

enum Weekday: CaseIterable {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}

This way you can create an OptionSet easily:

let weekdays: Options<Weekday> = [.monday, .tuesday]

Then in order to iterate you can use the following convenince property provided by the library:

for weekday in weekdays.decomposed {
    // Do something with weekday
}

Upvotes: 2

milo
milo

Reputation: 477

Based on the previous answers I created a generic Swift 4 solution with IteratorProtocol:

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

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

    private lazy var remainingBits = value.rawValue
    private var bitMask = 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
    }
}

Then in OptionSet extension implement makeIterator()

assuming your OptionSets will be Int:

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

Right now every time you create an OptionSet, just conform it to Sequence.

struct WeekdaySet: OptionSet, Sequence {
    let rawValue: Int

    ...
}

You should now be able to iterate over it:

let weekdays: WeekdaySet = [.monday, .tuesday]
for weekday in weekdays {
    // Do something with weekday
}

I'd also create a typealias to be explicit on what is used:

typealias SequenceOptionSet = OptionSet & Sequence

Upvotes: 19

ByteByByte
ByteByByte

Reputation: 1

/// Day rawValues used in WeekdaySet. Day proper names capitalized.
enum Day: UInt8, CaseIterable {
    case Sunday    = 0b00000001
    case Monday    = 0b00000010
    case Tuesday   = 0b00000100
    case Wednesday = 0b00001000
    case Thursday  = 0b00010000
    case Friday    = 0b00100000
    case Saturday  = 0b01000000
    var description: String {
        return "\(self)"
    }
}

/// Seven days of the week represented with binary options.
struct WeekdaySet: OptionSet {
    
    /// WeekdaySet initialized with Day (not with Weekday)
    static let Sunday    = WeekdaySet(.Sunday)
    static let Monday    = WeekdaySet(.Monday)
    static let Tuesday   = WeekdaySet(.Tuesday)
    static let Wednesday = WeekdaySet(.Wednesday)
    static let Thursday  = WeekdaySet(.Thursday)
    static let Friday    = WeekdaySet(.Friday)
    static let Saturday  = WeekdaySet(.Saturday)
    
    /// WeekdaySet initialized with Weekday (not with Day)
    static let all: WeekdaySet = [.Sunday, .Monday, .Tuesday, .Wednesday, .Thursday, .Friday, .Saturday]
    static let weekend: WeekdaySet = [.Saturday, .Sunday]
    static let midweek: WeekdaySet = [.Tuesday, .Wednesday, .Thursday]
    static let humpday: WeekdaySet = .Wednesday
    
    /// OptionSet conformance
    let rawValue: UInt8
    init(rawValue: UInt8) {
        self.rawValue = rawValue
    }
    
    /// Init using the enum Day
    init (_ day: Day) {
        self.rawValue = day.rawValue
    }
    
}

extension WeekdaySet: CaseIterable {

    static var allCases: [WeekdaySet] {
        [
            .Sunday,
            .Monday,
            .Tuesday,
            .Wednesday,
            .Thursday,
            .Friday,
            .Saturday,
        ]
    }
    
    /// Computed instance property to filter static allCases
    var array: [WeekdaySet] {
        get {
            return WeekdaySet.allCases.filter { self.contains($0) }
        }
    }
}

extension WeekdaySet: Sequence {
 
    typealias Iterator = AnyIterator<WeekdaySet>
    
    func makeIterator() -> Iterator {
        var iterator = array.makeIterator()
        return AnyIterator {
            return iterator.next()
        }
    }
}

extension WeekdaySet: CustomStringConvertible {
    var description: String {
        if self.array.count < 2 {
            return Day(rawValue: self.rawValue)?.description ?? ""
            
        }
        var results: [String] = []
        for weekday in self.array {
            results.append(Day(rawValue: weekday.rawValue)?.description ?? "")
        }
        return String(describing: results)
    }
}

/// A example set of weekdays
let customSet: WeekdaySet = [.Monday, .Tuesday]

/// Example usages:
print("Does the example set contain humpday?", customSet.contains(.humpday))

for weekday in customSet {
    print("Is \(weekday) midweek?", WeekdaySet.midweek.contains(weekday))
}

print("Thursday:", WeekdaySet.Thursday)
print("Weekend names:", WeekdaySet.weekend)
print("All names", WeekdaySet.all)

// Printed results:
// Does the example set contain humpday? false
// Is Monday midweek? false
// Is Tuesday midweek? true
// Thursday: Thursday
// Weekend names: ["Sunday", "Saturday"]
// All names ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]

Upvotes: -2

Martin R
Martin R

Reputation: 539815

As of Swift 4, there are no methods in the standard library to enumerate the elements of an OptionSetType (Swift 2) resp. OptionSet (Swift 3, 4).

Here is a possible implementation which simply checks each bit of the underlying raw value, and for each bit which is set, the corresponding element is returned. The "overflow multiplication" &* 2 is used as left-shift because << is only defined for the concrete integer types, but not for the IntegerType protocol.

Swift 2.2:

public extension OptionSetType where RawValue : IntegerType {

    func elements() -> AnySequence<Self> {
        var remainingBits = self.rawValue
        var bitMask: RawValue = 1
        return AnySequence {
            return AnyGenerator {
                while remainingBits != 0 {
                    defer { bitMask = bitMask &* 2 }
                    if remainingBits & bitMask != 0 {
                        remainingBits = remainingBits & ~bitMask
                        return Self(rawValue: bitMask)
                    }
                }
                return nil
            }
        }
    }
}

Example usage:

let weekdays: WeekdaySet = [.Monday, .Tuesday]
for weekday in weekdays.elements() {
    print(weekday)
}

// Output:
// WeekdaySet(rawValue: 2)
// WeekdaySet(rawValue: 4)

Swift 3:

public extension OptionSet where RawValue : Integer {

    func elements() -> AnySequence<Self> {
        var remainingBits = rawValue
        var bitMask: RawValue = 1
        return AnySequence {
            return AnyIterator {
                while remainingBits != 0 {
                    defer { bitMask = bitMask &* 2 }
                    if remainingBits & bitMask != 0 {
                        remainingBits = remainingBits & ~bitMask
                        return Self(rawValue: bitMask)
                    }
                }
                return nil
            }
        }
    }
}

Swift 4:

public extension OptionSet where RawValue: FixedWidthInteger {

    func elements() -> AnySequence<Self> {
        var remainingBits = rawValue
        var bitMask: RawValue = 1
        return AnySequence {
            return AnyIterator {
                while remainingBits != 0 {
                    defer { bitMask = bitMask &* 2 }
                    if remainingBits & bitMask != 0 {
                        remainingBits = remainingBits & ~bitMask
                        return Self(rawValue: bitMask)
                    }
                }
                return nil
            }
        }
    }
}

Upvotes: 49

Scott Gardner
Scott Gardner

Reputation: 8739

Here you go. I also added a convenience initializer to cut down on some of the boilerplate:

enum Day: Int {
  case Sun, Mon, Tue, Wed, Thu, Fri, Sat
}

struct WeekdaySet: OptionSetType {

  let rawValue: UInt8

  init(rawValue: UInt8) {
    self.rawValue = rawValue
  }

  init(_ rawValue: UInt8) {
    self.init(rawValue: rawValue)
  }

  static let Sunday = WeekdaySet(1 << 0)
  static let Monday = WeekdaySet(1 << 1)
  static let Tuesday = WeekdaySet(1 << 2)
  static let Wednesday = WeekdaySet(1 << 3)
  static let Thursday = WeekdaySet(1 << 4)
  static let Friday = WeekdaySet(1 << 5)
  static let Saturday = WeekdaySet(1 << 6)
  static let AllDays = [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]

  subscript(indexes: Day...) -> [WeekdaySet] {
    var weekdaySets = [WeekdaySet]()

    for i in indexes {
      weekdaySets.append(WeekdaySet.AllDays[i.rawValue])
    }

    return weekdaySets
  }

}

for weekday in WeekdaySet()[Day.Mon, Day.Tue] {
  print(weekday)
}

Upvotes: 1

Related Questions