Use Dictionary(grouping:by:) para reunir valores Swift sintéticos por chave e, em seguida, use mapValues e reduce para calcular cada total. Este tutorial para Mac também mostra a acumulação direta e verifica entradas vazias, totais zero e estouro de inteiros.

A tarefa consiste em transformar um array em memória de rótulos de formas neutros e quantidades inteiras não negativas em um único dicionário [String: Int]. Todas as entradas são literais sintéticos. O cálculo não lê registros externos, não faz solicitações de rede e não salva resultados.

Defina o que significa um total por chave

Antes de escolher os métodos da coleção, defina o contrato do resultado. Aqui, cada elemento de entrada contribui com sua quantidade exatamente uma vez para o total de seu rótulo. Os rótulos usam a igualdade comum de strings do Swift; o programa não remove espaços nem converte maiúsculas e minúsculas. As chaves de um dicionário devem estar em conformidade com Hashable, e o Swift aceita strings como chaves. Consulte a documentação de Dictionary da Apple.

A entrada ilustrativa contém dois valores de círculo, dois de quadrado e um de triângulo. Os totais pretendidos são simples o bastante para conferir à mão:

Rótulo Quantidades de entrada Total esperado
circle 4, 6 10
square 3, 5 8
triangle 0 0

Este exemplo usa as seguintes regras:

  • Uma entrada vazia produz um dicionário vazio.
  • Um rótulo que aparece com quantidade zero permanece presente com total zero.
  • Quantidades negativas são permitidas e contribuem por meio da adição comum.
  • Um rótulo ausente da entrada também fica ausente do resultado.
  • Cada soma intermediária deve caber em Int; caso contrário, a função lança um erro e não retorna nenhum total.

A distinção entre um rótulo ausente e um total zero é importante. Um rótulo ausente significa que não havia nenhuma entrada correspondente. Um rótulo presente com valor zero pode representar um zero explícito ou quantidades que se anulam. Mantenha essa distinção, a menos que quem lê o resultado precise especificamente de outra convenção.

Execute o exemplo completo em um Mac

Use uma cadeia de ferramentas Swift instalada. O guia de instalação para macOS do Swift.org documenta a configuração da cadeia de ferramentas e os comandos para criar e executar um pacote executável. Quando a cadeia estiver pronta, abra o Terminal e crie um pequeno projeto local:

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

Abra o arquivo de origem executável gerado dentro de Sources e substitua seu conteúdo pelo programa completo abaixo. O arquivo gerado pode estar dentro de uma subpasta com o nome do alvo. Mantenha o manifesto do pacote como foi gerado; este programa não precisa de dependências nem de importações adicionais, pois seus tipos e métodos de cálculo pertencem à biblioteca padrão do Swift.

struct SyntheticValue: Sendable {
    let key: String
    let quantity: Int
}

enum TotalError: Error {
    case overflow(key: String)
}

func checkedAdd(_ total: Int, _ value: SyntheticValue) throws -> Int {
    let addition = total.addingReportingOverflow(value.quantity)
    guard !addition.overflow else {
        throw TotalError.overflow(key: value.key)
    }
    return addition.partialValue
}

func groupThenTotal(_ values: [SyntheticValue]) throws -> [String: Int] {
    let groups = Dictionary(grouping: values, by: { $0.key })
    return try groups.mapValues { group in
        try group.reduce(0) { total, value in
            try checkedAdd(total, value)
        }
    }
}

func totalDirectly(_ values: [SyntheticValue]) throws -> [String: Int] {
    return try values.reduce(into: [String: Int]()) { totals, value in
        let previous = totals[value.key, default: 0]
        totals[value.key] = try checkedAdd(previous, value)
    }
}

let sample: [SyntheticValue] = [
    SyntheticValue(key: "circle", quantity: 4),
    SyntheticValue(key: "square", quantity: 3),
    SyntheticValue(key: "circle", quantity: 6),
    SyntheticValue(key: "triangle", quantity: 0),
    SyntheticValue(key: "square", quantity: 5)
]

let expected = ["circle": 10, "square": 8, "triangle": 0]

 do {
    let groupedTotals = try groupThenTotal(sample)
    let directTotals = try totalDirectly(sample)
    precondition(groupedTotals == expected)
    precondition(directTotals == expected)

    let emptyGrouped = try groupThenTotal([])
    let emptyDirect = try totalDirectly([])
    precondition(emptyGrouped.isEmpty && emptyDirect.isEmpty)

    let cancellation = [
        SyntheticValue(key: "circle", quantity: 2),
        SyntheticValue(key: "circle", quantity: -2)
    ]
    let zeroGrouped = try groupThenTotal(cancellation)
    let zeroDirect = try totalDirectly(cancellation)
    precondition(zeroGrouped == ["circle": 0])
    precondition(zeroDirect == ["circle": 0])

    for key in groupedTotals.keys.sorted() {
        if let total = groupedTotals[key] {
            print("\(key): \(total)")
        }
    }

    let tooLarge = [
        SyntheticValue(key: "circle", quantity: Int.max),
        SyntheticValue(key: "circle", quantity: 1)
    ]
    do {
        _ = try totalDirectly(tooLarge)
        preconditionFailure("Expected overflow to be reported")
    } catch TotalError.overflow(let key) {
        print("Overflow check: rejected \(key)")
    }
} catch {
    fatalError("Unexpected calculation failure: \(error)")
}

No diretório do pacote, execute o programa usando o comando documentado no guia para macOS do Swift.org:

swift run SyntheticTotals

A saída esperada do programa, além das mensagens de compilação, é:

circle: 10
square: 8
triangle: 0
Overflow check: rejected circle

O programa inclui verificações dos totais do exemplo, de entrada vazia, de anulação até zero e de um estouro intencional. Esses resultados esperados são derivados do exemplo e das APIs documentadas. O código foi inspecionado, mas não foi executado durante a preparação porque o ambiente disponível não tinha um compilador Swift.

Como funciona o agrupamento seguido de redução?

A primeira função separa deliberadamente a tarefa em etapas visíveis. Dictionary(grouping: values, by: { $0.key }) cria um dicionário cujos valores são arrays de elementos correspondentes. A Apple documenta que cada grupo resultante não é vazio e preserva a ordem dos elementos na sequência de origem. Consulte init(grouping:by:).

Para o exemplo, o tipo intermediário é [String: [SyntheticValue]]. O grupo de círculos contém as quantidades 4 e 6; o grupo de quadrados contém 3 e 5; o grupo de triângulos contém 0. Essa representação intermediária é útil quando outra parte de um cálculo local também precisa inspecionar os membros de cada grupo.

Em seguida, mapValues mantém as chaves do dicionário e transforma cada array de grupo em um único inteiro. Essa preservação das chaves é o comportamento documentado de mapValues(_:). Ela também explica por que a entrada do triângulo permanece, embora seu total seja zero.

Dentro dessa transformação, reduce(0) começa em zero e passa cada elemento sucessivo para a closure de combinação. A closure retorna o próximo total acumulado. A Apple documenta a redução sequencial e especifica que reduzir uma sequência vazia retorna o valor inicial. Consulte reduce(::).

Há dois casos vazios diferentes aqui. Um array de quantidades vazio, reduzido a partir de zero, resultaria em zero. No entanto, agrupar uma entrada vazia não cria grupos, então a função completa retorna um dicionário vazio, em vez de inventar um rótulo com total zero.

Quando você deve acumular os totais diretamente?

Use totalDirectly quando os totais finais forem o único resultado necessário. Seu acumulador começa como um dicionário [String: Int] vazio. Para cada elemento de entrada, ele consulta o total anterior daquele rótulo, soma a quantidade e armazena o total atualizado.

A consulta com valor padrão fornece zero quando uma chave está ausente. Apenas ler um valor padrão não cria uma entrada; a atribuição na linha seguinte armazena o total calculado. A documentação do subscrito com valor padrão da Apple explica tanto a consulta alternativa quanto as atualizações no próprio local para valores com semântica de valor.

reduce(into:) dá à closure acesso mutável ao acumulador. A Apple recomenda essa forma em vez de reduce comum ao construir um resultado com comportamento de cópia na escrita, como um dicionário. Consulte reduce(into:_:).

O que a próxima etapa precisa Escolha Motivo
Membros dos grupos e seus totais Agrupar e depois reduzir Os arrays intermediários ficam disponíveis para inspeção ou reutilização.
Apenas um inteiro por rótulo Acumulação direta Evita criar os arrays intermediários dos grupos.
Saída de texto repetível Qualquer opção, depois ordenar as chaves A ordem da saída é uma decisão separada da agregação.

A distinção de armazenamento intermediário decorre do código: o agrupamento mantém todos os elementos de entrada em arrays de grupos, enquanto a acumulação direta mantém um total corrente por chave distinta. Esta é uma comparação de implementação, não uma afirmação de desempenho medida. O exemplo não fornece nenhum benchmark de tempo.

Como lidar com estouro e saída estável?

checkedAdd chama addingReportingOverflow, que retorna um resultado parcial e um indicador de estouro. A Apple especifica que o valor parcial contém a soma completa somente quando esse indicador é falso. Quando ocorre estouro, o valor parcial retornado é truncado. Portanto, o programa lança um erro antes de armazená-lo. Consulte addingReportingOverflow(_:).

Esse contrato verifica cada adição na ordem de entrada dentro de cada chave. Por exemplo, quantidades Int.max, 1 e -1 para um rótulo são rejeitadas na segunda adição, embora a soma matemática final caiba em Int. Para aceitar essas entradas, é necessária uma estratégia aritmética diferente; aceitar o resultado intermediário truncado mudaria o contrato.

Se vários grupos estourarem, a implementação com agrupamento não garante qual erro de rótulo aparecerá primeiro: a ordem de iteração do dicionário é imprevisível. A implementação direta encontra os elementos na ordem da sequência de origem. Ambas rejeitam um cálculo com estouro, mas a primeira chave informada pode ser diferente.

Ordenar as chaves do resultado torna previsível a exibição dos totais. Os dicionários Swift não têm ordem definida, então imprimir um dicionário diretamente não deve ser usado para estabelecer uma ordem de apresentação. A Apple descreve essa questão de ordenação na documentação de Dictionary. O exemplo ordena as chaves somente após a agregação, sem alterar o cálculo.

Foundation ou a concorrência do Swift mudam este cálculo?

Foundation não é necessária para este contrato de valores. A Apple documenta NSDictionary como uma alternativa quando é preciso semântica de referência. Aqui, um dicionário Swift tipado já representa o mapeamento necessário de rótulos para totais inteiros. Consulte a documentação de NSDictionary do Foundation.

O cálculo é síncrono e mantém seu acumulador mutável dentro da função. SyntheticValue no exemplo está explicitamente em conformidade com Sendable; isso expressa um contrato de valores relacionado à concorrência, não uma solicitação para executar a redução de forma concorrente. O guia de concorrência do Swift explica os valores transferíveis e os requisitos para estruturas cujas propriedades armazenadas sejam transferíveis.

Para este pequeno cálculo de desktop, chame qualquer uma das funções diretamente e inspecione o dicionário retornado. Escolha o agrupamento quando os membros dos grupos continuarem úteis, ou a acumulação direta quando os totais forem suficientes. Preserve as regras para entrada vazia e total zero, rejeite explicitamente o estouro aritmético e ordene somente ao apresentar o resultado.

Fontes