Usa Dictionary(grouping:by:) per raccogliere valori Swift sintetici in base alla chiave, quindi usa mapValues e reduce per calcolare il totale di ciascun gruppo. Questo tutorial per Mac mostra anche l'accumulo diretto e i controlli per input vuoto, totali pari a zero e overflow degli interi.

Il compito consiste nel trasformare un array in memoria di etichette di forme neutre e quantità intere positive in un unico dizionario [String: Int]. Tutti gli input sono valori letterali sintetici. Il calcolo non legge record esterni, non effettua richieste di rete e non salva risultati.

Definire il significato di un totale per chiave

Prima di scegliere i metodi per le raccolte, definisci il contratto del risultato. In questo caso, ogni elemento di input contribuisce esattamente una volta con la propria quantità al totale della sua etichetta. Le etichette usano la normale uguaglianza tra stringhe Swift; il programma non esegue operazioni di rimozione degli spazi né conversioni tra maiuscole e minuscole. Le chiavi dei dizionari devono conformarsi a Hashable e Swift supporta le stringhe come chiavi. Consulta la documentazione di Dictionary di Apple.

L'input di esempio contiene due valori circle, due valori square e un valore triangle. I totali previsti sono abbastanza semplici da verificare a mano:

Etichetta Quantità di input Totale previsto
circle 4, 6 10
square 3, 5 8
triangle 0 0

Questo esempio segue queste regole:

  • Un input vuoto produce un dizionario vuoto.
  • Un'etichetta presente con quantità zero rimane nel risultato con totale zero.
  • Sono ammesse quantità negative, che contribuiscono tramite la normale addizione.
  • Un'etichetta assente dall'input non compare nel risultato.
  • Ogni somma intermedia deve rientrare in Int; altrimenti, la funzione genera un errore e non restituisce alcun totale.

La distinzione tra un'etichetta assente e un totale pari a zero è importante. Un'etichetta mancante indica che non c'erano input corrispondenti. Un'etichetta presente con valore zero può rappresentare uno zero esplicito oppure quantità che si annullano. Mantieni questa distinzione, a meno che chi legge il risultato non abbia specificamente bisogno di una convenzione diversa.

Eseguire l'esempio completo su Mac

Usa una toolchain Swift installata. La guida all'installazione per macOS di Swift.org illustra la configurazione della toolchain e i comandi per creare ed eseguire un pacchetto eseguibile. Quando la toolchain è pronta, apri Terminal e crea un piccolo progetto locale:

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

Apri il file sorgente eseguibile generato nella cartella Sources e sostituiscine il contenuto con il programma completo riportato di seguito. Il file generato potrebbe trovarsi in una sottocartella con il nome del target. Mantieni il manifest del pacchetto così com'è stato generato; questo programma non richiede dipendenze aggiuntive né import, perché i suoi tipi e i metodi di calcolo appartengono alla libreria standard di 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)")
}

Dalla directory del pacchetto, esegui il programma usando il comando documentato nella guida per macOS di Swift.org:

swift run SyntheticTotals

L'output previsto del programma, esclusi i messaggi di compilazione, è:

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

Il programma include controlli per i totali dell'esempio, l'input vuoto, l'annullamento fino a zero e un overflow intenzionale. Sono risultati attesi, ricavati dall'esempio e dalle API documentate. Il codice è stato esaminato, ma non eseguito durante la preparazione perché l'ambiente disponibile non disponeva di un compilatore Swift.

Come funziona il raggruppamento seguito dalla riduzione?

La prima funzione separa deliberatamente il compito in fasi ben visibili. Dictionary(grouping: values, by: { $0.key }) crea un dizionario i cui valori sono array di elementi corrispondenti. Apple documenta che ogni gruppo risultante non è vuoto e conserva l'ordine degli elementi nella sequenza sorgente. Consulta init(grouping:by:).

Per l'esempio, il tipo intermedio è [String: [SyntheticValue]]. Il gruppo circle contiene le quantità 4 e 6; il gruppo square contiene 3 e 5; il gruppo triangle contiene 0. Questa rappresentazione intermedia è utile quando un'altra parte di un calcolo locale deve esaminare anche gli elementi di ciascun gruppo.

Poi, mapValues mantiene le chiavi del dizionario e trasforma ogni array di gruppo in un singolo intero. La conservazione delle chiavi è il comportamento documentato di mapValues(_:). Questo spiega anche perché l'elemento triangle rimane presente anche se il suo totale è zero.

All'interno di questa trasformazione, reduce(0) parte da zero e passa ogni elemento successivo alla closure di combinazione. La closure restituisce il totale aggiornato. Apple documenta la riduzione sequenziale e specifica che ridurre una sequenza vuota restituisce il valore iniziale. Consulta reduce(::).

In questo caso ci sono due situazioni diverse di input vuoto. Ridurre da zero un array vuoto di quantità restituirebbe zero. Tuttavia, raggruppando un input vuoto non si creano gruppi, quindi la funzione completa restituisce un dizionario vuoto invece di inventare un'etichetta con totale zero.

Quando conviene accumulare direttamente i totali?

Usa totalDirectly quando il risultato richiesto è costituito solo dai totali finali. Il suo accumulatore inizia come dizionario [String: Int] vuoto. Per ogni elemento di input, cerca il totale precedente dell'etichetta, aggiunge la quantità e memorizza il totale aggiornato.

La ricerca con valore predefinito fornisce zero quando manca una chiave. La sola lettura del valore predefinito non crea una voce; l'assegnazione nella riga successiva memorizza il totale calcolato. La documentazione della subscript con valore predefinito di Apple spiega sia la ricerca con valore alternativo sia gli aggiornamenti in loco per i valori con semantica di valore.

reduce(into:) dà alla sua closure accesso mutabile all'accumulatore. Apple consiglia questa forma al posto di reduce ordinario quando si costruisce un risultato con comportamento copy-on-write, come un dizionario. Consulta reduce(into:_:).

Cosa serve nel passaggio successivo Scegli Motivo
Gli elementi dei gruppi e i relativi totali Raggruppa, poi riduci Gli array intermedi sono disponibili per l'ispezione o il riutilizzo.
Solo un intero per etichetta Accumulo diretto Evita di creare gli array intermedi dei gruppi.
Output testuale ripetibile Entrambi, poi ordina le chiavi L'ordinamento dell'output è una decisione distinta dall'aggregazione.

La differenza nell'archiviazione intermedia deriva dal codice: il raggruppamento conserva tutti gli elementi di input negli array dei gruppi, mentre l'accumulo diretto conserva un totale aggiornato per ogni chiave distinta. Si tratta di un confronto tra implementazioni, non di un'affermazione sulle prestazioni basata su misurazioni. L'esempio non fornisce benchmark temporali.

Come gestire l'overflow e mantenere stabile l'output?

checkedAdd chiama addingReportingOverflow, che restituisce un risultato parziale e un flag di overflow. Apple specifica che il valore parziale contiene la somma completa solo quando il flag è false. Quando si verifica un overflow, il valore parziale restituito è troncato. Perciò il programma genera un errore prima di memorizzarlo. Consulta addingReportingOverflow(_:).

Questo contratto controlla ogni addizione nell'ordine di input all'interno di ciascuna chiave. Per esempio, le quantità Int.max, 1 e -1 per una stessa etichetta vengono rifiutate alla seconda addizione, anche se la loro somma matematica finale rientra in Int. Per supportare questi input serve una strategia aritmetica diversa; accettare il risultato intermedio troncato cambierebbe il contratto.

Se vanno in overflow più gruppi, l'implementazione che usa il raggruppamento non garantisce quale errore relativo a un'etichetta apparirà per primo: l'ordine di iterazione del dizionario è imprevedibile. L'implementazione diretta esamina gli elementi nell'ordine della sequenza sorgente. Entrambe rifiutano un calcolo che va in overflow, ma la prima chiave segnalata può essere diversa.

Ordinare le chiavi del risultato rende prevedibile la visualizzazione dei totali. I dizionari Swift non hanno un ordine definito, quindi non bisogna usare la stampa diretta di un dizionario per stabilire l'ordine di presentazione. Apple descrive questo aspetto nella documentazione di Dictionary. L'esempio ordina le chiavi solo dopo l'aggregazione, lasciando invariato il calcolo.

Foundation o la concorrenza di Swift cambiano questo calcolo?

Foundation non è necessaria per questo contratto dei valori. Apple documenta NSDictionary come alternativa quando servono semantiche di riferimento. In questo caso, un dizionario Swift tipizzato rappresenta già la mappatura richiesta tra etichette e totali interi. Consulta la documentazione di NSDictionary di Foundation.

Il calcolo è sincrono e mantiene l'accumulatore mutabile all'interno della funzione. L'esempio fa conformare esplicitamente SyntheticValue a Sendable; questo esprime un contratto dei valori relativo alla concorrenza, non una richiesta di eseguire la riduzione in parallelo. La guida alla concorrenza di Swift illustra i valori sendable e i requisiti per le strutture le cui proprietà memorizzate sono sendable.

Per questo piccolo calcolo desktop, chiama direttamente una delle due funzioni ed esamina il dizionario restituito. Scegli il raggruppamento quando è utile conservare gli elementi dei gruppi, oppure l'accumulo diretto quando bastano i totali. Mantieni le regole per input vuoto e totali pari a zero, rifiuta esplicitamente l'overflow aritmetico e ordina solo quando presenti il risultato.

Fonti