Как реализовать свойство 'next' для перечисления CaseIterable в Swift - PullRequest
3 голосов
/ 16 апреля 2020

Я пытаюсь добавить next var в перечисление. Я могу сделать это для определенного перечисления c, но хотел бы расширить его, чтобы я мог получить «следующий» случай перечисления из значения перечисления, просто указав перечисление с протоколом, например 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
    }
}

Ответы [ 2 ]

5 голосов
/ 16 апреля 2020

Вы можете расширить CaseIterable, ограничив Self до Equatable. Тогда вам просто нужно найти индекс после firstIndex вашего CaseItareble перечисления и вернуть элемент в этой позиции. Если индекс равен endIndex во всех случаях, просто верните первый элемент.

extension CaseIterable where Self: Equatable {
    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]
    }
}

Другой вариант - ограничить AllCases значением BidirectionalCollection. Это позволит вам получить последний элемент вашего перечисления, проверить, равен ли он самому себе, и вернуть первый элемент без необходимости перебора всей вашей коллекции:

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)!)]
    }
}

, расширяется на CaseIterable next и предыдущие свойства:

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

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

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

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

Тестирование игровой площадки;

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
0 голосов
/ 30 апреля 2020

Я добавляю это в дополнение к ответу Лео Дабуса, на случай, если людям тоже понадобится расширение previous.


edit:

Я добавил оба next и previous.

Основное различие заключается в ожидаемом поведении.

Для out of band:

  • Это решение возвращает nul
  • Решение Leo Dabus работает как chained list
extension CaseIterable where Self: Equatable {
    var allCases: AllCases { Self.allCases }

    /// Using `next` on an empty enum of on the last element returns `nil`
    var next: Self? {
        guard let currentIndex = allCases.firstIndex(of: self) else { return nil }

        let index = allCases.index(after: currentIndex)

        // ensure we don't go past the last element
        guard index != allCases.endIndex else { return nil }
        return allCases[index]
    }

    var previous: Self? {
        guard let currentIndex = allCases.firstIndex(of: self) else { return nil }

        // ensure we don't go before the first element
        guard currentIndex != allCases.startIndex else { return nil }
        let index = allCases.index(currentIndex, offsetBy: -1)

        return allCases[index]
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...