What is a convenient way to work with OptionSet? - swift

I work on a project with many bitwise option sets and each of them contains many options with all option e.g:
struct MyOption: OptionSet {
let rawValue: Int
static let a = Self(rawValue: 1 << 0)
static let b = Self(rawValue: 1 << 1)
static let c = Self(rawValue: 1 << 2)
...
static let last = Self(rawValue: 1 << N)
static let all: Self = [.a, .b, .c, ..., .last]
}
It requires to maintain much of similar code so is it any way to eliminate hardcoded bitwise shift operations and the all option?

You can use next OptionSet's extension which implements all option and a convenient initializer:
extension OptionSet where RawValue == Int {
static var all: Self {
Self.init(rawValue: Int.max)
}
init(_ shift: Int) {
self.init(rawValue: 1 << shift)
}
}
Then you can re-write your option set:
struct Option: OptionSet {
let rawValue: Int
static let a = Self(0)
static let b = Self(1)
static let c = Self(2)
}
Option.a.rawValue // 1
Option.b.rawValue // 2
Option.c.rawValue // 4
let options: Option = [.a, .b]
Option.all.contains(options) // true

Related

Find the name that was associated to a value of an OptionSet

So I have an OptionSet:
struct Ability: OptionSet {
let rawValue: Int
static let create = Ability(rawValue: 1 << 0)
static let read = Ability(rawValue: 1 << 1)
static let update = Ability(rawValue: 1 << 2)
static let delete = Ability(rawValue: 1 << 3)
init(rawValue: Int) {
self.rawValue = rawValue
}
}
I'm just writing one as an example here. In reality my code doesn't know what OptionSet it has exactly, it only knows it gets an OptionSet. What I want to do is be able to get the name of the option from its rawValue as a String.
So basically I want to write a function with the following signature:
func convertOptionToString<OS: OptionSet>(optionSet: OS) -> String
Where convertOptionToString(Ability.read) would return "read"
Of course I know that OptionSets can also hold multiple values, but I already know how I would deal with that situation, so that's not a problem I need help with.
I am hoping that this problem can be solved using some kind of smart Reflection, but I haven't been able to find out how yet. Anybody want to give it a go?
Update your struct to conform to CustomStringConvertible and implement the description property:
struct Ability: OptionSet, CustomStringConvertible {
let rawValue: Int
static let create = Ability(rawValue: 1 << 0)
static let read = Ability(rawValue: 1 << 1)
static let update = Ability(rawValue: 1 << 2)
static let delete = Ability(rawValue: 1 << 3)
init(rawValue: Int) {
self.rawValue = rawValue
}
var description: String {
var vals = [String]()
if self.contains(.create) {
vals.append("create")
}
if self.contains(.read) {
vals.append("read")
}
if self.contains(.update) {
vals.append("update")
}
if self.contains(.delete) {
vals.append("delete")
}
return vals.joined(separator: ",")
}
}
print(Ability.read)
let opts: Ability = [ .read, .delete ]
print(opts)
Output:
read
read,delete

Is there a way to test an OptionSet with a switch statement?

Defining a simple OptionSet:
public struct TestSet : OptionSet, Hashable
{
public let rawValue: Int
public init(rawValue:Int){ self.rawValue = rawValue}
public var hashValue: Int {
return self.rawValue
}
public static let A = TestSet(rawValue: 1 << 0)
public static let B = TestSet(rawValue: 1 << 1)
public static let C = TestSet(rawValue: 1 << 2)
}
Call it:
let ostest : TestSet = [.A, .B]
switch ostest{
case .A: print("A")
case .B: print("B")
case .C: print("C")
default: print("Default")
}
if ostest.contains(.A){
print("Contains A")
}
if ostest.contains(.B){
print("Contains B")
}
Output is:
Default
Contains A
Contains B
Is there a way to check if OptionSets contain a value or combination of values with a switch statement? It would be much cleaner than a series of if-contains statements.
You can't do this directly because switch is using Equatable and, I think, is using SetAlgebra.
However, you can wrap the OptionSet with something like:
public struct TestSetEquatable<T: OptionSet>: Equatable {
let optionSet: T
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.optionSet.isSuperset(of: rhs.optionSet)
}
}
Which lets you do:
let ostest : TestSet = [.A, .C]
switch TestSetEquatable(optionSet: ostest) {
case TestSetEquatable(optionSet: [.A, .B]):
print("-AB")
fallthrough
case TestSetEquatable(optionSet: [.A, .C]):
print("-AC")
fallthrough
case TestSetEquatable(optionSet: [.A]):
print("-A")
fallthrough
default:
print("-")
}
This prints:
-AC
-A
- // from the fall through to default
Opinion: I'm not inclined to do use this code myself but if I had to, this is what I would do.

Swift 3.0 OptionSet Bit Test

With the following OptionSet:
struct StatusOptions : OptionSet {
let rawValue: Int
static let CountdownDuration0 = StatusOptions(rawValue: 1 << 0)
static let CountdownDuration1 = StatusOptions(rawValue: 1 << 1)
static let CountdownDuration2 = StatusOptions(rawValue: 1 << 2)
static let CountdownDuration3 = StatusOptions(rawValue: 1 << 3)
static let CountdownDuration4 = StatusOptions(rawValue: 1 << 4)
static let CountdownDuration5 = StatusOptions(rawValue: 1 << 5)
static let HomeMode = StatusOptions(rawValue: 1 << 6)
static let AwayMode = StatusOptions(rawValue: 1 << 7)
static let Disarmed: StatusOptions = []
static let ArmedHome: StatusOptions = .HomeMode
static let ArmedAway: StatusOptions = .AwayMode
static let ArmedBoth: StatusOptions = [.HomeMode, .AwayMode]
static let ArmingCountdown: StatusOptions = [.CountdownDuration0, .CountdownDuration1, .CountdownDuration2, .CountdownDuration3, .CountdownDuration4, .CountdownDuration5]
}
How do I compare a byte
var statusFlags: UInt8 = 0b00000000
To the Options?
The following is what I'd think would work, but causes a playground error:
if (statusFlags & .Disarmed) {
print("Disarmed")
}
Thanks
Since .Disarmed is zero, you say:
if statusFlags == .Disarmed
or even more simply:
if statusFlags.isEmpty
For other values, you say, e.g.:
if statusFlags.contains(.CountdownDuration3)
That is the whole point of an OptionSet. You use set operations, not arithmetic bit operations.
But for this to work, so that you can take advantage of OptionSet's yummy goodness, statusFlags needs to be a StatusOptions. You can do that by way of the StatusOptions rawValue: initializer:
let statusFlags = StatusOptions(rawValue:0b00000000)
statusFlags.isEmpty // true
(Note that in Swift 3, a zero bitmask like .Disarmed probably wouldn't even be used. Zero is just an empty set.)

Is there way to define compare (`==`) function automatically for `struct` in Swift?

Let's assume we have a pretty big struct in Swift:
struct SuperStruct {
var field1: Int = 0
var field2: String = ""
// lots of lines...
var field512: Float = 0.0
}
.. and then we need to implement Equatable protocol:
extension SuperStruct: Equatable {
}
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
return
lhs.field1 == rhs.field1 &&
lhs.field2 == rhs.field2 &&
// lots of lines...
lhs.field512 == rhs.field512
}
... and we need to write lots of lines of stupid code.
Is there a way "to ask" compiler "to do" it for us?
The following answer shows one possible solution; possibly not a recommended one (however possibly of interest for future readers of this question).
If you have a large number of properties which all belong to a somewhat limited of number different types, you could use a Mirror of your structure instances and iterate over over the structures' properties; for each attempting conversion to the different types that you know your properties to be.
I've edited the previous answer (to something I believe is quite much neater), after watching the following WWDC 2015 session (thanks Leo Dabus!):
WWDC 2015 session 408. Recommended.
I'll leave the initial answer in the bottom of this answer as well, as it shows an alternative, less protocol-oriented approach, to make use of this Mirror solution.
Mirror & protocol-oriented solution:
/* Let a heterogeneous protocol act as "pseudo-generic" type
for the different (property) types in 'SuperStruct' */
protocol MyGenericType {
func isEqualTo(other: MyGenericType) -> Bool
}
extension MyGenericType where Self : Equatable {
func isEqualTo(other: MyGenericType) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}
/* Extend types that appear in 'SuperStruct' to MyGenericType */
extension Int : MyGenericType {}
extension String : MyGenericType {}
extension Float : MyGenericType {}
// ...
/* Finally, 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }
for i in 0..<mLhs.count {
guard let valLhs = mLhs[i].value as? MyGenericType, valRhs = mRhs[i].value as? MyGenericType else {
print("Invalid: Properties 'lhs.\(mLhs[i].label!)' and/or 'rhs.\(mRhs[i].label!)' are not of 'MyGenericType' types.")
return false
}
if !valLhs.isEqualTo(valRhs) {
return false
}
}
return true
}
Example usage:
/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
Previous Mirror solution:
/* 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }
for i in 0..<mLhs.count {
switch mLhs[i].value {
case let valLhs as Int:
guard let valRhs = mRhs[i].value as? Int where valRhs == valLhs else {
return false
}
case let valLhs as String:
guard let valRhs = mRhs[i].value as? String where valRhs == valLhs else {
return false
}
case let valLhs as Float:
guard let valRhs = mRhs[i].value as? Float where valRhs == valLhs else {
return false
}
/* ... extend with one case for each type
that appear in 'SuperStruct' */
case _ : return false
}
}
return true
}
Example usage:
/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
In Swift 4.1, Equatable/Hashable types now synthesize conformance to Equatable/Hashable if all of the types' members are Equatable/Hashable
SE-0185
Synthesizing Equatable and Hashable conformance
Developers have to write large amounts of boilerplate code to support equatability and hashability of complex types. This proposal offers a way for the compiler to automatically synthesize conformance to Equatable and Hashable to reduce this boilerplate, in a subset of scenarios where generating the correct implementation is known to be possible.
https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md
You could make the struct Codable and compare the JSON encoded Data. Not efficient, but could be useful for some applications (e.g. unit tests).
struct SuperStruct: Encodable {
var field1: Int = 0
// ....
var field512: Float = 0.0
}
let s1 = SuperStruct()
let s2 = SuperStruct()
let encoder = JSONEncoder()
let data1 = try! encoder.encode(s1)
let data2 = try! encoder.encode(s2)
let result = (data1 == data2)
If you like this you could tidy it up into a protocol extension of Encodable.
No, it doesn't. At least not in any way that's not excessively complicated and based on use (abuse?) of runtime introspection. See dfri's answer for something that technically works, but that is way more complicated than just writing an == implementation that directly compares all fields.
As for your opinions on what "should" be available in Swift, you're more likely to see some effect if you share them with Apple or with the Swift open source community.

Map OptionSetType to Array

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?
(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:
The explicit definition of allOptions: is not needed.
It can be applied to other option set types (which have 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 } )
}
}
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) }
}
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?
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)
}