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 }
An OptionSet in Swift is a very lightweight object that can be used to represent a fairly large number of boolean values. While you can initialize it with an array literal, it's actually much more like a Set than an array.
A type that presents a mathematical set interface to a bit set.
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 } } } }
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 OptionSet
s 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With