Para suprimir casos consecutivos de enum em Swift ignorando os valores associados, compare uma chave de caso separada e mantenha o primeiro valor completo de cada sequência. Este exemplo local para Mac preserva a ordem e permite que um caso apareça novamente após outro caso.

Defina exatamente o que conta como duplicata

Suponha que uma pré-visualização gráfica para desktop produza uma sequência sintética de etapas: ocioso, desenhando uma forma nomeada e exibindo uma pré-visualização nomeada. Você quer uma lista compacta de transições entre casos. Duas etapas consecutivas de desenho contam como duplicatas mesmo quando os nomes das formas são diferentes.

Enums do Swift podem carregar diferentes valores associados para cada caso. Esses valores fazem parte do valor do enum, então ignorá-los exige uma regra de comparação explícita. O guia da linguagem Swift explica essa distinção em Enumerations.

Para esta tarefa, use o seguinte contrato:

  • Mantenha o primeiro valor de entrada.
  • Suprima cada valor posterior cujo caso corresponda ao caso da sequência anterior.
  • Preserve o valor associado completo de cada elemento mantido.
  • Mantenha novamente um caso quando ele vier depois de um caso diferente.
  • Preserve a ordem de entrada; uma entrada vazia produz um resultado vazio.

Veja o comportamento pretendido para uma entrada ilustrativa. Todos os nomes são sintéticos.

Valor de entrada Decisão Motivo
.idle Manter Primeiro valor
.drawing("Circle") Manter O caso mudou
.drawing("Square") Suprimir Ainda está desenhando
.ready("Preview A") Manter O caso mudou
.ready("Preview B") Suprimir Ainda está pronto
.drawing("Triangle") Manter Desenho vem depois de pronto
.idle Manter O caso mudou
.idle Suprimir Continua ocioso

Os valores de desenho mantidos são, portanto, \"Circle\" e \"Triangle\". O valor associado \"Square\" é excluído deliberadamente. Se a visualização para desktop precisar exibir todos os nomes de formas atualizados, este contrato descartará alterações de que essa visualização precisa; escolha uma comparação sensível ao valor associado para esse consumidor.

Execute um exemplo Swift local completo no seu Mac

Use um Mac com um ambiente de desenvolvimento Swift funcional. Swift.org recomenda o Xcode para desenvolvimento em plataformas Apple e documenta um fluxo de trabalho de executável de linha de comando usando o Swift Package Manager. Siga Install Swift — macOS se precisar preparar as ferramentas.

No Terminal, crie um pequeno pacote executável em um novo diretório de trabalho:

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

Substitua o conteúdo do arquivo de origem executável gerado pelo programa completo a seguir. O programa usa tipos da biblioteca padrão do Swift e não requer importações nem dependências de pacote. A entrada e a saída permanecem na memória; ele imprime as etapas sintéticas mantidas.

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

Execute o pacote no diretório dele:

swift run EnumCaseDemo

A saída esperada do programa, após quaisquer mensagens de compilação, é:

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

Esses exemplos foram conferidos com base na documentação consultada; não foram executados aqui porque o ambiente de pesquisa não tem um executável Swift. As precondições incluídas verificam os valores associados esperados no resultado, a entrada vazia, valores associados consecutivos diferentes e um caso que retorna após uma transição.

Por que uma chave de caso separada funciona

A propriedade kind converte cada valor completo do enum em uma chave pequena, sem valor associado. Tanto .drawing("Circle") quanto .drawing("Square") produzem a chave .drawing. A função de filtragem compara essas chaves enquanto acrescenta o step original, preservando assim os valores associados mantidos.

A função precisa lembrar apenas uma chave. No início, previousKind é nil, permitindo a passagem do primeiro elemento. Depois de manter um elemento, ela lembra o tipo desse elemento. Os elementos suprimidos têm o mesmo tipo, portanto atualizar a chave lembrada para eles não faria diferença.

Esta implementação percorre a entrada uma vez. Para este enum fixo e suas chaves de tamanho constante, ela faz uma comparação de chave de caso por elemento subsequente e usa estado auxiliar constante, além do array retornado. A documentação de Sequence do Swift descreve a iteração sequencial e o modelo usual de percurso linear.

O PreviewStep completo também está em conformidade com Equatable, permitindo que as precondições verifiquem resultados completos, inclusive as strings mantidas. O Swift oferece suporte à igualdade sintetizada para enums elegíveis cujos valores associados estejam em conformidade com Equatable; consulte Protocols. Manter a comparação de casos em Kind dá a esta operação de filtragem uma regra explícita própria, sem redefinir a igualdade em todo o programa.

O switch exaustivo em kind também oferece um ponto útil de verificação para manutenção. Ao adicionar um novo caso, adicione a chave e o mapeamento correspondentes. Evite um mapeamento genérico que agrupe acidentalmente um caso novo com um existente. O Swift documenta a alternância exaustiva de enums em Enumerations.

Use a mesma comparação com Combine

Se o código do seu Mac já usa um publisher de Combine, a Apple fornece removeDuplicates(by:) para uma comparação personalizada. O predicado retorna true quando o segundo valor deve contar como duplicata. A Apple oferece explicitamente suporte a esse operador quando a comparação desejada difere da igualdade comum do tipo de saída. Ele está disponível no macOS 10.15 e versões posteriores. Consulte removeDuplicates(by:).

O trecho a seguir é uma extensão, não um programa independente. Acrescente-o ao exemplo completo acima em um Mac com Combine disponível. Ele reutiliza a definição existente de PreviewStep e o array input:

import Combine

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

Isso imprime as mesmas cinco etapas mantidas, com o prefixo \"Combine: \". O predicado compara as chaves de caso, e o publisher transmite os valores originais do enum que foram mantidos. O próprio exemplo de operador da Apple segue o mesmo princípio, comparando uma propriedade e ignorando outra. removeDuplicates(by:)

Mantenha a assinatura retornada ativa pelo período necessário da assinatura. A Apple documenta que desalocar o valor retornado por sink(receiveValue:) encerra a assinatura. Esta sobrecarga se aplica a publishers cujo tipo de falha é Never, como nos exemplos de publishers de sequência da Apple. sink(receiveValue:)

Verifique os limites antes de adaptar o código

Use a função de array quando já tiver uma lista local finita. Use o operador de Combine quando os valores já chegarem por meio de um publisher. As duas versões aqui expressam o mesmo contrato de transição entre casos.

Requisito Comparação ou abordagem apropriada
Manter o primeiro valor completo de cada sequência de casos Compare kind, como mostrado
Manter alterações nas strings associadas Compare os valores completos usando a igualdade comum
Manter o último valor completo de cada sequência Armazene um valor pendente e descarregue-o em uma transição de caso e no final
Manter um caso que retorna após outro caso Compare sequências consecutivas em vez de manter uma lista global de casos já vistos
Filtrar arrays separados de forma independente Chame a função separadamente; cada chamada começa com estado de comparação vazio

Manter o último valor associado exige um algoritmo diferente porque este programa acrescenta o primeiro valor imediatamente. Da mesma forma, dividir uma sequência lógica em arrays separados redefine o estado de comparação: um valor de desenho no fim de um lote e outro no início do próximo sobreviverão às chamadas separadas. Se os lotes tiverem de se comportar como uma única sequência, preserve a chave anterior entre os limites dos lotes.

O Foundation fornece coleções e outras funcionalidades básicas para apps de plataformas Apple, mas este cálculo é implementado inteiramente com tipos do Swift. A variante com publisher exige especificamente Combine; adicionar import Foundation não importa esse operador. A Apple documenta esses frameworks separadamente em Foundation e removeDuplicates(by:).

A função síncrona de array também não precisa de mecanismos de concorrência: o estado de comparação pertence a cada chamada. Se mais tarde você introduzir um estado de apresentação compartilhado no desktop, defina o isolamento desse estado separadamente. A Apple documenta MainActor como um ator singleton cujo executor equivale à fila de despacho principal; a supressão de duplicatas, por si só, não seleciona um ator. MainActor

Para a tarefa local mostrada aqui, a escolha decisiva é qual valor associado sobrevive a uma sequência. Depois de escolher o primeiro valor associado, uma chave de caso separada torna o comportamento fácil de ler e verificar: mantenha o valor original em uma transição de caso, suprima chaves consecutivas correspondentes e deixe passar os casos que retornam.

Fontes