somu
(somu)
May 29, 2018, 10:42am
1
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
Letan
(Letanyan Arumugam)
May 29, 2018, 12:10pm
4
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
tkrajacic
(Thomas Krajacic)
May 29, 2018, 12:23pm
5
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)
}
}
somu
(somu)
May 29, 2018, 2:08pm
6
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