How to get a case of enum in one line? - swift

I have a enum with some case, and a array with instances of this enum, for example:
enum MyEnum {
case foo(Int)
case bar(Int)
case baz(Int)
}
let myArray = [
MyEnum.foo(1),
MyEnum.bar(2),
MyEnum.baz(3)
]
Then, I need to compare if determinate element of this array if foo, baz or baz, for example:
myArray.filter { $0 == .foo } // not work, and should need return [MyEnum.foo(1)]
I can use switch:
myArray.filter {
switch $0 {
case .foo(_):
return true
default:
return false
}
} // work, and return [MyEnum.foo(1)]
But, I want a solution less verbose, and learning more about enum.

The shortest I can think of is:
let result = myArray.filter { if case .foo = $0 { return true } else { return false } }

As mentioned before if case is a good solution since swift 2.0. But if you're going to use this same filter many times, then you will need a more expressive way to do this. For instance, you can make this enum equatable hiding your switchCase away from the viewController:
extension MyEnum: Equatable{
static func ==(lhs: MyEnum, rhs: MyEnum) -> Bool{
switch (lhs, rhs) {
case (.foo(_), .foo(_)):
return true
case (.bar(_), .bar(_)):
return true
case (.baz(_), .baz(_)):
return true
default:
return false
}
}
}
and then:
myArray.filter {
$0 == .foo(1)
}

If you really want to do it in one line, you can use reflection:
myArray.filter { Mirror(reflecting: $0).children.first!.label! == "foo" }

Related

How can I compare two enums with OR operator?

I need to compare if any of the two cases meets the requirement, I need to take decision else need to do something else. I tried many ways and this is one of them but every way giving error.
if case .locked = itemStatus || case .hasHistoryLocked = itemStatus {
print("YES")
} else {
print("NO")
}
A switch is common pattern for matching a series of cases. See The Swift Programming Language: Enumerations: Matching Enumeration Values with a Switch Statement.
E.g.:
switch itemStatus {
case .locked, .hasHistoryLocked:
print("YES")
default:
print("NO")
}
If you want to add this in an if or guard statement, you can wrap the above in a computed property. E.g.,
extension ItemStatus {
var isLocked: Bool {
switch self {
case .locked, .hasHistoryLocked:
return true
default:
return false
}
}
}
Then you can do things like:
func doSomethingIfUnlocked() {
guard !itemStatus.isLocked else {
return
}
// proceed with whatever you wanted if it was unlocked
}
Alternatively, you can add Equatable conformance for this type. So, imagine ItemStatus was defined like so:
enum ItemStatus {
case locked
case hasHistoryLocked
case unlocked(Int)
}
Now, if this was your type, you could just add Equatable conformance:
enum ItemStatus: Equatable {
case locked
case hasHistoryLocked
case unlocked(Int)
}
If it was not your type and you can not simply edit the original declaration, you could instead add Equatable conformance:
extension ItemStatus: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.locked, .locked), (.hasHistoryLocked, .hasHistoryLocked): // obviously, add all cases without associated values here
return true
case (.unlocked(let lhsValue), .unlocked(let rhsValue)) where lhsValue == rhsValue: // again, add similar patterns for all cases with associated values
return true
default:
return false
}
}
}
However you add Equatable conformance to ItemStatus, you can then do things like:
func doSomethingIfUnlocked() {
guard itemStatus != .locked, itemStatus != .hasHistoryLocked else {
return
}
// proceed with whatever you wanted if it was unlocked
}
As your comment I see that you don't want to use Equatable to check enum. There is another way to check it using Enum rawValue
So it just like you get the rawValue of enum from the key and compare it with your itemStatus
enum Test : Int {
case locked = 0
case hasHistoryLocked = 1
case anything = 2
}
let itemStatus = 0
if itemStatus == Test.locked.rawValue || itemStatus == Test.hasHistoryLocked.rawValue {
print("Yes")
} else {
print("No")
}

How to pass an enum case into a function that uses 'if case' to check for a property's case

I have an enum where each case has different (or none) associated values. The enum is not equatable.
I'm using on several places if case to check if a value is of a specific case.
enum MyEnum {
case justACase
case numberCase(Int)
case stringCase(String)
case objectCase(NonEquatableObject)
}
let myArray: [MyEnum] = [.justACase, .numberCase(100), .stringCase("Hello Enum"), .justACase]
if case .justACase = myArray[0] {
print("myArray[0] is .justACase")
}
if case .numberCase = myArray[1] {
print("myArray[1] is .numberCase")
}
But I would like to extract that into its own method where I pass on the case I want to check for. I imagine something like the following. (Please write in the comments if something is not clear.)
func checkCase(lhsCase: /*???*/, rhsCase: MyEnum) -> Bool {
if case lhsCase = rhsCase {
return true
} else {
return false
}
}
// Usage:
if checkCase(lhsCase: .justACase, rhsCase: myArray[0]) {
print("myArray[0] is .justACase")
}
In my example above lhsCase should not be of type MyEnum I assume, as this would mean I'd try to compare two properties (See code just below) and would require my enums to be equatables. Which they are not.
I'm not sure if this is even possible that way.
If the following would work it would also solve my problem but it does not.
if case myArray[3] = myArray[0] {
print("myArray[0] is .justACase")
}
The answer of Shadowrun is a good start. Through the CasePaths library I found EnumKit, which was partially the inspiration for CasePaths. However it gives much simpler methods to compare cases. See my implementation below.
Using this library comes with some nice bonuses, it allows enums that have associated values to be made equatable by just comparing the cases and ignoring the values. This might not always be desired but come in quite handy in a lot of cases. I use it to compare ReSwift Actions with Associated values in my tests.
import Foundation
import EnumKit
class NonEquatableObject {
}
enum MyEnum: CaseAccessible { // <-- conform to CaseAccessible
case justACase
case numberCase(Int)
case stringCase(String)
case objectCase(NonEquatableObject)
}
let myArray: [MyEnum] = [.justACase, .numberCase(100), .stringCase("Hello Enum"), .justACase]
if case .justACase = myArray[0] {
print("myArray[0] is .justACase")
}
if case .numberCase = myArray[1] {
print("myArray[1] is .numberCase")
}
func checkCase(lhsCase: MyEnum, rhsCase: MyEnum) -> Bool {
if case lhsCase = rhsCase {
return true
} else {
return false
}
}
// Usage:
if checkCase(lhsCase: .justACase, rhsCase: myArray[0]) { //<-- allows to pass variables or just the cases (unfortunately not without associated value.)
print("myArray[0] is .justACase")
}
if case myArray[3] = myArray[0] { //<-- allows this here too
print("myArray[0] is .justACase")
}
// Bonus: Adding equatable if associated values are not equatable. Looking at the case only.
extension MyEnum: Equatable {
static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
lhs.matches(case: rhs)
}
}
if myArray[3] == myArray[0] {
print("myArray[3] == myArray[0]")
}
There's a library CasePaths that might do what you want: something like this:
struct NonEO {
var a: Any
}
enum MyEnum {
case justACase
case numberCase(Int)
case stringCase(String)
case nonEO(NonEO)
}
let myArray: [MyEnum] = [.justACase, .nonEO(NonEO(a: 42)), .stringCase("Hello Enum"), .justACase, .nonEO(NonEO(a: "Thing"))]
func sameCase<T>(casePath: CasePath<MyEnum, T>,
lhs: MyEnum, rhs: MyEnum) -> Bool {
(casePath.extract(from: lhs) != nil)
&& casePath.extract(from: rhs) != nil
}
sameCase(casePath: /MyEnum.justACase, lhs: myArray[0], rhs: myArray[1]) // FALSE
sameCase(casePath: /MyEnum.justACase, lhs: myArray[0], rhs: myArray[3]) // TRUE
sameCase(casePath: /MyEnum.nonEO, lhs: myArray[1], rhs: myArray[4]) // TRUE
sameCase(casePath: /MyEnum.nonEO(.init(a: 42)), lhs: myArray[1], rhs: myArray[4]) // FALSE

Enum pattern matching as a parameter to a function call

I've setup a playground with an example:
enum CarType : Equatable {
case wheeled(wheels: Int)
case flying
public static func ==(lhs: CarType, rhs: CarType) -> Bool {
return lhs.enumName == rhs.enumName
}
var enumName: String {
let stuff = "\(self)".split(separator: "(").first!
return String(describing: stuff)
}
}
var typesPresentAtMyParty = [CarType.wheeled(wheels:4), .wheeled(wheels:4), .flying]
let aKnownType = CarType.flying
if case aKnownType = typesPresentAtMyParty[2] {
print("Was the type")
}
func isPresent(type: CarType, inArray: [CarType]) -> Bool {
return inArray.filter {
if case type = $0 {
return true
}
return false
}.first != nil
}
func isWheeled(inArray: [CarType]) -> Bool {
return inArray.filter {
if case .wheeled = $0 {
return true
}
return false
}.first != nil
}
isPresent(type: .flying, inArray: typesPresentAtMyParty)
isPresent(type: .wheeled, inArray: typesPresentAtMyParty)
The last line here does not compile. While i can do if case .wheeled = $0 ignoring associated type as a check, i cannot find a way of doing the same in a function call isPresent(type: CarType, inArray: [CarType]), when sending isPresent(type: .wheeled, inArray: typesPresentAtMyParty)
Is there a way of writing a function that takes only the valid pattern matching part of the enum as a parameter?
It is not possible to pass partially constructed enums to a function. Partially constructed enums are not valid values, and they only work in pattern matching because the compiler has a concrete value to work with - the one from the right side of the pattern.
These being said, you could easily rewrite your functions to better, more swiftier versions.
Firstly, you don't need isPresent, you can simply use contains:
typesPresentAtMyParty.contains { $0 == .flying }
typesPresentAtMyParty.contains { if case . wheeled = $0 { return true } else { return false } }
Similarly, isWheeled can be shortened (and renamed, for better semantics):
func isWheeled(_ carType: CarType) -> Bool {
if case . wheeled = carType { return true } else { return false }
}
which can pe passed to contains:
let hasWeeled = typesPresentAtMyParty.contains(where: isWheeled)

How to check Swift nested enums for equality of the outer enum only?

I'm trying to use nested enums to describe my model, in a way that makes illegal states impossible and provides categorisation at the top level. Right now, my (simplified) code is:
enum SportsEvent {
enum RunningEvent {
case sprint
case marathon
}
enum ThrowingEvent {
case hammer
case javelin
case discus
}
case running(event: RunningEvent)
case throwing(event: ThrowingEvent)
func isSameCategory(as other: SportsEvent) -> Bool {
return false
}
}
let sprint = SportsEvent.running(event: .sprint)
let javelin = SportsEvent.throwing(event: .javelin)
let hammer = SportsEvent.throwing(event: .hammer)
sprint.isSameCategory(as: javelin) // False
hammer.isSameCategory(as: javelin) // True
It feels like it should be trivial with an if case ... and a wildcard, but I'm not seeing how to achieve that. I'm hoping a giant switch statement isn't necessary, as my actual model is more complex.
I think you need a switch-statement, with a “compound case” listing all
possible “same value combinations” of the outer enumeration,
plus a default case:
func isSameCategory(as other: SportsEvent) -> Bool {
switch (self, other) {
case (.running, .running),
(.throwing, .throwing):
return true
default:
return false
}
}
Or (attribution goes to #Hamish):
func isSameCategory(as other: SportsEvent) -> Bool {
switch (self, other) {
case (.running, .running),
(.throwing, .throwing):
return true
case (.running, _),
(.throwing, _):
return false
}
}
which has the advantage that the compiler checks that all cases are covered. For an enumeration with n cases that makes 2 * n
cases in the switch statement (which is better than n * n if you checked all possible combinations).
Depending on your use case, you might be able to convert SportEvent to a protocol:
enum RunningEvent {
case sprint
case marathon
}
enum ThrowingEvent {
case hammer
case javelin
case discus
}
enum SportEventCategory {
case running
case throwing
}
protocol SportEvent {
var category: SportEventCategory { get }
}
extension RunningEvent: SportEvent {
var category: SportEventCategory {
return .running
}
}
extension ThrowingEvent: SportEvent {
var category: SportEventCategory {
return .throwing
}
}
let sportEvent1: SportEvent = RunningEvent.sprint
let sportEvent2: SportEvent = ThrowingEvent.hammer
print(sportEvent1.category == sportEvent2.category)
or even as one flat enum:
enum SportEvent {
enum Category {
case running
case throwing
}
case sprint
case marathon
case hammer
case javelin
case discus
var category: Category {
switch self {
case .sprint, .marathon, .hammer:
return .running
case .javelin, .discus:
return .throwing
}
}
}
let sportEvent1: SportEvent = .sprint
let sportEvent2: SportEvent = .marathon
print(sportEvent1.category == sportEvent2.category)

How to look for enum in enums list?

I want to determine if an enum is present in an enums list.
Intuitively I would do this:
if myEnum == (.one || .two) { return true }
else { return false }
Of course it doesn't work.
I know that I could do:
if myEnum == .one || myEnum == .two { return true }
else { return false }
Or
if [EnumType.one, EnumType.two].contains(myEnum) { return true }
else { return false }
But I just want to be fancy today :)
I am thinking about using filter but it seems overkill.
Do you have an idea?
Thanks a lot.
Thierry
You can do
//: Playground - noun: a place where people can play
import Cocoa
enum MyEnum {
case one
case two
case three
case four
}
let list: [MyEnum] = [.one, .two, .three, .four]
if list.contains(.one) {
// Contains
} else {
// Doesn't Contain
}
If you have associated data you have to make your enum be Equatable for this to work though. For example:
//: Playground - noun: a place where people can play
import Cocoa
enum MyEnum: Equatable {
case one
case two
case three
case four
case other(String)
static func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
switch (lhs, rhs) {
case (.one, .one),
(.two, .two),
(.three, .three),
(.four, .four):
return true
case (.other(let lhsString), .other(let rhsString)) where lhsString == rhsString:
return true
default:
return false
}
}
}
let list: [MyEnum] = [.one, .two, .three, .four, .other("test")]
if list.contains(.one) {
} else {
}
I would do a switch on each one and then have a default for if you can't find either of those types.
switch myEnum {
case .one:
print("One is here baby")
case .two:
print("Two is here baby")
default:
print("Can't find the case??????")
}
That's what OptionSet are for. It's technically a struct, but in usage, look very close to enum:
struct MyOptions : OptionSet {
var rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
static let one = MyOptions(rawValue: 1)
static let two = MyOptions(rawValue: 2)
static let three = MyOptions(rawValue: 4)
}
let option: MyOptions = [.one, .two]
if option.contains([.one, .two]) {
print("Has one and two")
}
if !option.intersection([.one, .three]).isEmpty {
print("Has one or three")
}
I would use a switch as well and group the enum cases which are handled with common logic as follows:
enum MyEnum {
case one
case two
case three
case four
}
switch myEnum {
case .one, .two:
//deal with enum cases .one and .two
default:
//deal with all other cases
}
}
If you are trying to match arbitrary strings to various cases in your enum then you can do something like this (using Swift 3).
enum CompassPoint:String {
case north
case south
case east
case west
static func has(key: String) -> Bool {
for f in iterateEnum(CompassPoint.self) {
if(f.rawValue == key) {
return true;
}
}
return false;
}
private static func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
var i = 0
return AnyIterator {
let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
if next.hashValue != i { return nil }
i += 1
return next
}
}
}
CompassPoint.has(key: "south") // true
CompassPoint.has(key: "something") // false
Shoutout to #rintaro for his function for iterating over enums (found below).
https://stackoverflow.com/a/28341290/5270038