Reputation: 33967
Given the following:
struct Weekdays: OptionSetType {
let rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
static let Monday = Weekdays(rawValue: 1)
static let Tuesday = Weekdays(rawValue: 2)
static let Wednesday = Weekdays(rawValue: 4)
static let Thursday = Weekdays(rawValue: 8)
static let allOptions: [Weekdays] = [.Monday, .Tuesday, .Wednesday, .Thursday]
}
I can convert an array of Ints
into a Weekdays
object by doing this:
let arr = [1, 4]
let weekdays = arr.reduce(Weekdays()) { $0.union(Weekdays(rawValue: $1)) }
My question is, how do I take a Weekdays
object and convert it into an array of Ints
?
Upvotes: 4
Views: 2911
Reputation: 365
Not exactly answering the question, but might be useful to others. Based on Martin's answer I extract back the component objects:
extension FixedWidthInteger {
init(bitComponents : [Self]) {
self = bitComponents.reduce(0, +)
}
var bitComponents : [Self] {
(0 ..< Self.bitWidth).map { 1 << $0 } .filter { self & $0 != 0 }
}
}
extension OptionSet where RawValue: FixedWidthInteger, Self == Self.Element {
var components : [Self] { rawValue.bitComponents.map(Self.init) }
}
Upvotes: 3
Reputation: 1581
You can improve the context of your extension by defining it conditionally on OptionSet
.
extension OptionSet where RawValue: UnsignedInteger {
var individualCases: [Self] {
return (0..<(8 * MemoryLayout<RawValue>.size))
.map { bitsToShift in RawValue(1 << bitsToShift) } // Get every possible single-bit flag
.filter { (powerOfTwo: RawValue) -> Bool in rawValue & powerOfTwo != 0 } // filter out only the cases the receiver contains
.map { Self(rawValue: $0) } // create the `OptionSet` (single bit) type
}
}
let weekdays = Weekdays(rawValue: 0b11111)
weekdays.individualCases.map { $0.rawValue } // [1, 2, 4, 8, 16]
A warning: On my 13" 2019 MacBookPro, I had to provide all of the explicit types above to keep the methods type checking under 1500ms in Swift 5.0.
Thanks to MartinR for the inspiration to loop of the memory layout size.
To complete the example, I've updated the weekday type to Swift 5 below, and explicitly used the UInt8
type to make the individualCases
more efficient. With UInt
, it would loop over the first map
and filter
64 times each, with UInt8
it only loops 8 times.
struct Weekdays: OptionSet {
let rawValue: UInt8
static let Monday = Weekdays(rawValue: 1)
static let Tuesday = Weekdays(rawValue: 2)
static let Wednesday = Weekdays(rawValue: 4)
static let Thursday = Weekdays(rawValue: 8)
static let Friday = Weekdays(rawValue: 16)
}
Upvotes: 1
Reputation: 539815
(Not necessarily better, but a different way to look at it and slightly more general).
OptionSetType
inherits from RawRepresentable
and therefore can be
converted from and to the associated raw type, which in your case is
Int
.
So the "missing link" is a conversion between the raw value (e.g. 5
)
and an integer array of the bitwise components (e.g. [1, 4]
).
This can be done with an Int
extension method:
extension Int {
init(bitComponents : [Int]) {
self = bitComponents.reduce(0, combine: (+))
}
func bitComponents() -> [Int] {
return (0 ..< 8*sizeof(Int)).map( { 1 << $0 }).filter( { self & $0 != 0 } )
}
}
Then your conversion from an array to a Weekdays
object becomes
let arr : [Int] = [1, 4]
let weekdays = Weekdays(rawValue: Int(bitComponents: arr))
print(weekdays)
// app.Weekdays(rawValue: 5)
and the reverse conversion
let array = weekdays.rawValue.bitComponents()
print(array)
// [1, 4]
Advantages:
allOptions:
is not needed.Int
as a raw value).One could also try to define the conversions as a protocol extension,
e.g. of IntegerType
, so that the same works with other integer raw types as well. However, this seems to be a bit complicated/ugly
because the left shift operator <<
is not part of the
IntegerType
(or any) protocol.
Update for Swift 3:
extension Int {
init(bitComponents : [Int]) {
self = bitComponents.reduce(0, +)
}
func bitComponents() -> [Int] {
return (0 ..< 8*MemoryLayout<Int>.size).map( { 1 << $0 }).filter( { self & $0 != 0 } )
}
}
Upvotes: 10
Reputation: 33967
As I was writing the question, I figured it out:
let array = Weekdays.allOptions.filter { weekdays.contains($0) }.map { $0.rawValue }
Is there a better way?
Upvotes: 1