Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a 'next' property to a CaseIterable enum in Swift

Tags:

enums

swift

I'm trying to add a next var to an enum. I am able to do so for a specific enum but would like to extend it generically so that I can obtain the 'next' enum case from an enum value just by specifying an enum with a protocol, ex CaseNextIterable

enum MyEnum: CaseIterable { // 'next' here is possible thanks to 'CaseIterable' protocol
    case a, b, c
    // returns the next case, or first if at end of sequence
    // ie. a.next == b, c.next == a
    var next: Self {
        var r: Self!
        for c in Self.allCases + Self.allCases { // not efficient
            if r != nil {
                r = c
                break
            }
            if c == self {
                r = self
            }
        }
        return r
    }
}
like image 220
andrewz Avatar asked Dec 31 '22 04:12

andrewz


1 Answers

You can extend CaseIterable constraining Self to Equatable. Then you just need to find the index after the firstIndex of your CaseItareble enumeration and return the element at that position. If the index is equal to the endIndex of all cases just return the first element.

extension CaseIterable where Self: Equatable {
    private var allCases: AllCases { Self.allCases }
    var next: Self {
        let index = allCases.index(after: allCases.firstIndex(of: self)!)
        guard index != allCases.endIndex else { return allCases.first! }
        return allCases[index]
    }
}

Another option is to constrain AllCases to BidirectionalCollection. This will allow you to get the last element of you enumeration, check if it is equal to self and return the first element without the need to iterate your whole collection:

extension CaseIterable where Self: Equatable, AllCases: BidirectionalCollection {
    var allCases: AllCases { Self.allCases }
    var next: Self {
        guard allCases.last != self else { return allCases.first! }
        return allCases[allCases.index(after: allCases.firstIndex(of: self)!)]
    }
}

expanding on CaseIterable next and previous properties:

extension CaseIterable {
    typealias Index = AllCases.Index
    var first: Self { allCases.first! }
    private var allCases: AllCases { Self.allCases }
    private static func index(after i: Index) -> Index { allCases.index(after: i) }
}

extension CaseIterable where AllCases: BidirectionalCollection {
    var last: Self { allCases.last! }
    private static func index(before i: Index) -> Index { allCases.index(before: i) }
}

extension CaseIterable where Self: Equatable {
    var index: Index { Self.firstIndex(of: self) }
    private static func firstIndex(of element: Self) -> Index { allCases.firstIndex(of: element)! }
}

extension CaseIterable where Self: Equatable, AllCases: BidirectionalCollection {
    var previous: Self { first == self ? last : allCases[Self.index(before: index)] }
    var next: Self { last == self ? first : allCases[Self.index(after: index)] }
}

Playground testing;

enum Enum: CaseIterable {
    case a,b,c
}

let value: Enum = .c

let next = value.next  // a
let next2 = next.next  // b
let next3 = next2.next // c

let previous = value.previous      // b
let previous2 = previous.previous  // a
let previous3 = previous2.previous // c
like image 118
Leo Dabus Avatar answered Jun 03 '23 01:06

Leo Dabus