Aby pomijać kolejne przypadki wyliczenia Swift z ignorowaniem wartości skojarzonych, porównuj osobny klucz przypadku i zachowuj pierwszą pełną wartość w każdym ciągu. Ten lokalny przykład na Macu zachowuje kolejność i pozwala ponownie wystąpić danemu przypadkowi po innym przypadku.

Określ dokładnie, co uznajesz za duplikat

Załóżmy, że podgląd grafiki na komputerze generuje przykładową sekwencję kroków: bezczynność, rysowanie nazwanego kształtu i wyświetlanie nazwanego podglądu. Chcesz utworzyć zwięzłą listę zmian przypadków. Dwa kolejne kroki rysowania uznajesz za duplikaty, nawet jeśli różnią się nazwami kształtów.

Wyliczenia Swift mogą mieć różne wartości skojarzone z poszczególnymi przypadkami. Wartości te są częścią wartości wyliczenia, więc ich ignorowanie wymaga jawnej reguły porównywania. Przewodnik po języku Swift wyjaśnia tę różnicę w sekcji Wyliczenia.

W tym zadaniu przyjmij następujące zasady:

  • Zachowaj pierwszą wartość wejściową.
  • Pomijaj każdą kolejną wartość, której przypadek jest taki sam jak przypadek poprzedzający w bieżącym ciągu.
  • Zachowuj pełną wartość skojarzoną każdego pozostawionego elementu.
  • Ponownie zachowuj dany przypadek, gdy występuje po innym przypadku.
  • Zachowuj kolejność wejściową; puste wejście daje pusty wynik.

Oto oczekiwane zachowanie dla przykładowego wejścia. Wszystkie nazwy są fikcyjne.

Wartość wejściowa Decyzja Powód
.idle Zachowaj Pierwsza wartość
.drawing("Circle") Zachowaj Zmiana przypadku
.drawing("Square") Pomiń Nadal rysowanie
.ready("Preview A") Zachowaj Zmiana przypadku
.ready("Preview B") Pomiń Nadal gotowość
.drawing("Triangle") Zachowaj Rysowanie następuje po gotowości
.idle Zachowaj Zmiana przypadku
.idle Pomiń Nadal bezczynność

Zachowane wartości rysowania to zatem \"Circle\" i \"Triangle\". Ładunek \"Square\" zostaje celowo pominięty. Jeśli widok na komputerze musi wyświetlać każdą zaktualizowaną nazwę kształtu, ta reguła odrzuci zmiany potrzebne temu widokowi; dla tego odbiorcy wybierz porównywanie uwzględniające ładunek.

Uruchom kompletny lokalny przykład Swift na Macu

Użyj Maca z działającym środowiskiem programistycznym Swift. Swift.org zaleca Xcode do tworzenia aplikacji na platformy Apple i opisuje przepływ pracy z plikiem wykonywalnym wiersza poleceń przy użyciu Swift Package Manager. Jeśli chcesz przygotować narzędzia, skorzystaj z instrukcji Instalacja Swift — macOS.

W aplikacji Terminal utwórz mały pakiet wykonywalny w nowym katalogu roboczym:

mkdir EnumCaseDemo
cd EnumCaseDemo
swift package init --name EnumCaseDemo --type executable

Zastąp zawartość wygenerowanego pliku źródłowego programu wykonywalnego poniższym kompletnym programem. Wykorzystuje on typy ze standardowej biblioteki Swift i nie wymaga importów ani zależności pakietów. Dane wejściowe i wyjściowe pozostają w pamięci; program wypisuje zachowane przykładowe kroki.

enum PreviewStep: Equatable {
    case idle
    case drawing(String)
    case ready(String)

    enum Kind: Equatable {
        case idle
        case drawing
        case ready
    }

    var kind: Kind {
        switch self {
        case .idle:
            return .idle
        case .drawing(_):
            return .drawing
        case .ready(_):
            return .ready
        }
    }

    var label: String {
        switch self {
        case .idle:
            return "idle"
        case .drawing(let shape):
            return "drawing: \(shape)"
        case .ready(let name):
            return "ready: \(name)"
        }
    }
}

func suppressConsecutiveCases(
    _ steps: [PreviewStep]
) -> [PreviewStep] {
    var result: [PreviewStep] = []
    var previousKind: PreviewStep.Kind? = nil

    for step in steps {
        let currentKind = step.kind
        if let previous = previousKind,
           previous == currentKind {
            continue
        }

        result.append(step)
        previousKind = currentKind
    }

    return result
}

let input: [PreviewStep] = [
    .idle,
    .drawing("Circle"),
    .drawing("Square"),
    .ready("Preview A"),
    .ready("Preview B"),
    .drawing("Triangle"),
    .idle,
    .idle
]

let expected: [PreviewStep] = [
    .idle,
    .drawing("Circle"),
    .ready("Preview A"),
    .drawing("Triangle"),
    .idle
]

let output = suppressConsecutiveCases(input)

precondition(output == expected)
precondition(suppressConsecutiveCases([]).isEmpty)
precondition(
    suppressConsecutiveCases([
        .drawing("Circle"), .drawing("Square")
    ]) == [.drawing("Circle")]
)
precondition(
    suppressConsecutiveCases([
        .idle, .drawing("Circle"), .idle
    ]) == [.idle, .drawing("Circle"), .idle]
)

for step in output {
    print(step.label)
}

Uruchom pakiet z jego katalogu:

swift run EnumCaseDemo

Oczekiwany wynik programu, po ewentualnych komunikatach kompilacji:

idle
drawing: Circle
ready: Preview A
drawing: Triangle
idle

Przykłady sprawdzono pod kątem zgodności z pobraną dokumentacją; nie uruchomiono ich tutaj, ponieważ środowisko badawcze nie ma pliku wykonywalnego Swift. Dołączone warunki wstępne sprawdzają oczekiwane zachowane ładunki, puste wejście, kolejne różniące się ładunki oraz przypadek powracający po zmianie.

Dlaczego osobny klucz przypadku działa

Właściwość kind zamienia każdą pełną wartość wyliczenia na mały klucz bez ładunku. Zarówno .drawing("Circle"), jak i .drawing("Square") dają klucz .drawing. Funkcja filtrująca porównuje te klucze, dodając jednocześnie oryginalne step, dzięki czemu zachowane wartości skojarzone pozostają nienaruszone.

Funkcja musi zapamiętać tylko jeden klucz. Na początku previousKind ma wartość nil, co pozwala przepuścić pierwszy element. Po zachowaniu elementu zapamiętuje jego rodzaj. Pomijane elementy mają ten sam rodzaj, więc aktualizacja zapamiętanego klucza niczego by nie zmieniła.

Ta implementacja przechodzi przez dane wejściowe raz. W przypadku tego stałego wyliczenia i jego kluczy o stałym rozmiarze wykonuje jedno porównanie kluczy dla każdego kolejnego elementu i używa stałego stanu pomocniczego oraz zwracanej tablicy. Dokumentacja Sequence w Swift opisuje iterację sekwencyjną i typowy model liniowego przechodzenia.

Pełny typ PreviewStep jest również zgodny z Equatable, co pozwala warunkom wstępnym sprawdzać kompletne wyniki, w tym zachowane ciągi znaków. Swift obsługuje syntezę równości dla kwalifikujących się wyliczeń, których wartości skojarzone są zgodne z Equatable; zobacz Protokoły. Porównywanie przypadków w Kind nadaje tej operacji filtrowania własną, jawną regułę bez zmieniania równości w całym programie.

Wyczerpujący switch w kind jest też przydatnym punktem kontrolnym przy konserwacji kodu. Gdy dodasz nowy przypadek, dodaj odpowiadający mu klucz i mapowanie. Unikaj mapowania ogólnego, które przypadkowo przypisze nowemu przypadkowi klucz już istniejącego przypadku. Swift opisuje wyczerpujące przełączanie po wyliczeniach w sekcji Wyliczenia.

Użyj tego samego porównania z Combine

Jeśli kod na Macu korzysta już z wydawcy Combine, Apple udostępnia removeDuplicates(by:) do niestandardowego porównywania. Predykat zwraca true, gdy druga wartość powinna zostać uznana za duplikat. Apple wyraźnie wspiera ten operator, gdy wymagane porównanie różni się od zwykłej równości typu wyjściowego. Jest dostępny w macOS 10.15 i nowszych. Zobacz removeDuplicates(by:).

Poniższy fragment jest rozszerzeniem, a nie samodzielnym programem. Dodaj go do kompletnego przykładu powyżej na Macu z dostępnym Combine. Korzysta z istniejącej definicji PreviewStep i tablicy input:

import Combine

let subscription = input.publisher
    .removeDuplicates { previous, current in
        previous.kind == current.kind
    }
    .sink { step in
        print("Combine: \(step.label)")
    }

Wypisuje te same pięć zachowanych kroków z prefiksem \"Combine: \". Predykat porównuje klucze przypadków, a wydawca przekazuje dalej oryginalne zachowane wartości wyliczenia. Własny przykład operatora Apple stosuje tę samą zasadę: porównuje jedną właściwość, ignorując inną. removeDuplicates(by:)

Zachowaj zwróconą subskrypcję przez wymagany czas jej trwania. Apple dokumentuje, że zwolnienie wartości zwróconej przez sink(receiveValue:) kończy subskrypcję. To przeciążenie dotyczy wydawców, których typ błędu to Never, jak w przykładach Apple z wydawcami sekwencji. sink(receiveValue:)

Sprawdź przypadki brzegowe przed dostosowaniem kodu

Użyj funkcji dla tablicy, jeśli masz już skończoną lokalną listę. Użyj operatora Combine, jeśli wartości już napływają przez wydawcę. Obie wersje przedstawiają tę samą regułę zmian przypadków.

Wymaganie Odpowiednie porównanie lub podejście
Zachowaj pierwszą pełną wartość w każdym ciągu przypadków Porównuj kind, jak pokazano
Zachowaj zmiany ciągów skojarzonych Porównuj pełne wartości przy użyciu zwykłej równości
Zachowaj ostatnią pełną wartość w każdym ciągu Buforuj oczekującą wartość i dodawaj ją przy zmianie przypadku oraz na końcu
Zachowaj powracający przypadek po innym przypadku Porównuj kolejne ciągi, zamiast prowadzić globalną listę już widzianych przypadków
Filtruj oddzielne tablice niezależnie Wywołuj funkcję osobno; każde wywołanie zaczyna się z pustym stanem porównania

Zachowanie ostatniego ładunku wymaga innego algorytmu, ponieważ ten program natychmiast dodaje pierwszą wartość. Podobnie podzielenie jednej logicznej sekwencji na oddzielne tablice resetuje stan porównania: wartość rysowania na końcu jednej partii i kolejna na początku następnej przetrwają osobne wywołania. Jeśli partie mają zachowywać się jak jedna sekwencja, zachowuj poprzedni klucz między granicami partii.

Foundation udostępnia kolekcje i inne podstawowe funkcje dla aplikacji na platformy Apple, ale to obliczenie jest zaimplementowane wyłącznie przy użyciu typów Swift. Wariant wydawcy wymaga konkretnie Combine; dodanie import Foundation nie importuje tego operatora. Apple opisuje te platformy osobno w dokumentacji Foundation i removeDuplicates(by:).

Synchroniczna funkcja dla tablicy również nie wymaga mechanizmów współbieżności: jej stan porównania należy do każdego wywołania. Jeśli później wprowadzisz współdzielony stan prezentacji na komputerze, osobno określ jego izolację. Apple opisuje MainActor jako singleton aktora, którego executor jest równoważny głównej kolejce dyspozytorskiej; samo pomijanie duplikatów nie wybiera aktora. MainActor

W opisanym tu lokalnym zadaniu decydujący jest wybór ładunku, który przetrwa dany ciąg. Gdy wybierzesz pierwszy ładunek, osobny klucz przypadku sprawia, że zachowanie jest łatwe do odczytania i zweryfikowania: zachowuj oryginalną wartość przy zmianie przypadku, pomijaj zgodne kolejne klucze i przepuszczaj powracające przypadki.

Źródła