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
- Instalar Swift — macOS — Swift.org.
- Dictionary — Documentação para desenvolvedores da Apple.
- init(grouping:by:) — Documentação para desenvolvedores da Apple.
- mapValues(_:) — Documentação para desenvolvedores da Apple.
- reduce(::) — Documentação para desenvolvedores da Apple.
- reduce(into:_:) — Documentação para desenvolvedores da Apple.
- subscript(_:default:) — Documentação para desenvolvedores da Apple.
- addingReportingOverflow(_:) — Documentação para desenvolvedores da Apple.
- NSDictionary — Documentação para desenvolvedores da Apple.
- Concorrência — A linguagem de programação Swift.