Utilisez Dictionary(grouping:by:) pour regrouper des valeurs Swift synthétiques par clé, puis mapValues et reduce pour calculer le total de chaque groupe. Ce tutoriel pour Mac présente également l’accumulation directe et les vérifications pour une entrée vide, des totaux nuls et un dépassement d’entier.

L’objectif est de convertir un tableau en mémoire d’étiquettes de formes neutres et de quantités entières en un seul dictionnaire [String: Int]. Toutes les entrées sont des littéraux synthétiques. Le calcul ne lit aucun enregistrement externe, n’effectue aucune requête réseau et n’enregistre aucun résultat.

Définir ce que signifie un total par clé

Avant de choisir les méthodes de collection, définissez le contrat du résultat. Ici, chaque élément d’entrée contribue exactement une fois, avec sa quantité, au total de son étiquette. Les étiquettes utilisent l’égalité ordinaire des chaînes Swift ; le programme n’effectue ni suppression d’espaces ni conversion de casse. Les clés d’un dictionnaire doivent être conformes à Hashable, et Swift accepte les chaînes comme clés. Consultez la documentation sur Dictionary d’Apple.

L’entrée d’illustration contient deux valeurs de cercle, deux valeurs de carré et une valeur de triangle. Les totaux attendus sont assez simples pour être vérifiés à la main :

Étiquette Quantités d’entrée Total attendu
circle 4, 6 10
square 3, 5 8
triangle 0 0

Cet exemple suit les règles suivantes :

  • Une entrée vide produit un dictionnaire vide.
  • Une étiquette associée à une quantité nulle reste présente, avec un total nul.
  • Les quantités négatives sont autorisées et s’ajoutent normalement.
  • Une étiquette absente de l’entrée est absente du résultat.
  • Chaque somme intermédiaire doit tenir dans Int ; sinon, la fonction lève une erreur et ne renvoie aucun total.

La distinction entre une étiquette absente et un total nul est importante. Une étiquette manquante signifie qu’aucune entrée correspondante n’existait. Une étiquette présente avec une valeur nulle peut représenter un zéro explicite ou des quantités qui s’annulent. Conservez cette distinction, sauf si le lecteur du résultat a spécifiquement besoin d’une autre convention.

Exécuter l’exemple complet sur un Mac

Utilisez une chaîne d’outils Swift installée. Le guide d’installation pour macOS de Swift.org décrit la configuration de la chaîne d’outils ainsi que les commandes permettant de créer et d’exécuter un package exécutable. Une fois la chaîne d’outils prête, ouvrez Terminal et créez un petit projet local :

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

Ouvrez le fichier source exécutable généré sous Sources et remplacez son contenu par le programme complet ci-dessous. Le fichier généré peut se trouver dans un sous-dossier portant le nom de la cible. Conservez le manifeste du package tel qu’il a été généré ; ce programme n’a besoin d’aucune dépendance ou import supplémentaire, car ses types et ses méthodes de calcul appartiennent à la bibliothèque standard de 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)")
}

Depuis le répertoire du package, exécutez le programme avec la commande indiquée dans le guide macOS de Swift.org :

swift run SyntheticTotals

À l’exception des messages de compilation, la sortie attendue du programme est :

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

Le programme vérifie les totaux de l’exemple, l’entrée vide, l’annulation à zéro et un dépassement de capacité intentionnel. Ces résultats attendus découlent de l’exemple et des API documentées. Le code a été examiné, mais n’a pas été exécuté pendant la préparation, car l’environnement disponible ne disposait pas d’un compilateur Swift.

Comment fonctionnent le regroupement puis la réduction ?

La première fonction sépare délibérément la tâche en étapes visibles. Dictionary(grouping: values, by: { $0.key }) crée un dictionnaire dont les valeurs sont des tableaux d’éléments correspondants. Apple indique que chaque groupe obtenu est non vide et conserve l’ordre des éléments de la séquence source. Consultez init(grouping:by:).

Pour l’exemple, le type intermédiaire est [String: [SyntheticValue]]. Le groupe du cercle contient les quantités 4 et 6 ; celui du carré contient 3 et 5 ; celui du triangle contient 0. Cette représentation intermédiaire est utile si une autre partie d’un calcul local doit également examiner les éléments de chaque groupe.

Ensuite, mapValues conserve les clés du dictionnaire et transforme chaque tableau de groupe en un entier unique. La conservation des clés est le comportement documenté de mapValues(_:). Elle explique aussi pourquoi l’entrée du triangle subsiste même si son total est nul.

Dans cette transformation, reduce(0) commence à zéro et transmet chaque élément successif à la fermeture de combinaison. Celle-ci renvoie le total courant suivant. Apple documente la réduction séquentielle et précise que réduire une séquence vide renvoie la valeur initiale. Consultez reduce(::).

Il y a ici deux cas vides distincts. Un tableau de quantités vide réduit à partir de zéro donnerait zéro. Cependant, le regroupement d’une entrée vide ne crée aucun groupe ; la fonction complète renvoie donc un dictionnaire vide au lieu d’inventer une étiquette avec un total nul.

Quand faut-il accumuler directement les totaux ?

Utilisez totalDirectly lorsque seuls les totaux finaux sont nécessaires. Son accumulateur commence par un dictionnaire [String: Int] vide. Pour chaque élément d’entrée, il recherche le total précédent de cette étiquette, ajoute la quantité et enregistre le total mis à jour.

La recherche avec valeur par défaut fournit zéro lorsqu’une clé est absente. Le simple fait de lire une valeur par défaut ne crée pas d’entrée ; l’affectation de la ligne suivante enregistre le total calculé. La documentation sur les indices à valeur par défaut d’Apple explique à la fois la recherche de remplacement et les mises à jour en place pour les valeurs ayant une sémantique de valeur.

reduce(into:) donne à sa fermeture un accès modifiable à l’accumulateur. Apple recommande cette forme plutôt que reduce ordinaire pour construire un résultat avec un comportement de copie sur écriture, comme un dictionnaire. Consultez reduce(into:_:).

Besoin de l’étape suivante Choisir Raison
Les éléments des groupes et leurs totaux Regrouper, puis réduire Les tableaux intermédiaires peuvent être examinés ou réutilisés.
Un seul entier par étiquette Accumulation directe Elle évite de créer les tableaux de groupes intermédiaires.
Une sortie textuelle reproductible L’une ou l’autre méthode, puis trier les clés L’ordre de sortie est indépendant de l’agrégation.

La différence de stockage intermédiaire découle du code : le regroupement conserve tous les éléments d’entrée dans des tableaux de groupes, tandis que l’accumulation directe ne conserve qu’un total courant par clé distincte. Il s’agit d’une comparaison des implémentations, et non d’une affirmation de performance mesurée. L’exemple ne fournit aucun test de temps d’exécution.

Comment gérer les dépassements et obtenir une sortie stable ?

checkedAdd appelle addingReportingOverflow, qui renvoie une valeur partielle et un indicateur de dépassement. Apple précise que la valeur partielle contient la somme complète uniquement lorsque cet indicateur est faux. En cas de dépassement, la valeur partielle renvoyée est tronquée. Le programme lève donc une erreur avant de l’enregistrer. Consultez addingReportingOverflow(_:).

Ce contrat vérifie chaque addition dans l’ordre des entrées, au sein de chaque clé. Par exemple, les quantités Int.max, 1 et -1 pour une même étiquette sont rejetées lors de la deuxième addition, même si leur somme mathématique finale tient dans Int. La prise en charge de telles entrées nécessite une autre stratégie arithmétique ; accepter le résultat intermédiaire tronqué modifierait le contrat.

Si plusieurs groupes dépassent la capacité, l’implémentation avec regroupement ne garantit pas quelle étiquette apparaîtra en premier dans l’erreur : l’ordre d’itération du dictionnaire est imprévisible. L’implémentation directe rencontre les éléments dans l’ordre de la source. Les deux rejettent un calcul qui dépasse la capacité, mais la première clé signalée peut différer.

Le tri des clés du résultat rend les totaux affichés prévisibles. Les dictionnaires Swift ne sont pas ordonnés ; il ne faut donc pas compter sur l’affichage direct d’un dictionnaire pour établir un ordre de présentation. Apple décrit cette limite concernant l’ordre dans sa documentation sur Dictionary. L’exemple ne trie les clés qu’après l’agrégation, sans modifier le calcul.

Foundation ou la concurrence Swift modifient-elles ce calcul ?

Foundation n’est pas nécessaire pour ce contrat de valeurs. Apple présente NSDictionary comme une solution de remplacement lorsque la sémantique de référence est requise. Ici, un dictionnaire Swift typé représente déjà la correspondance nécessaire entre les étiquettes et les totaux entiers. Consultez la documentation sur NSDictionary de Foundation.

Le calcul est synchrone et conserve son accumulateur modifiable à l’intérieur de la fonction. Le type SyntheticValue de l’exemple est explicitement conforme à Sendable ; cela exprime un contrat de valeurs lié à la concurrence, et non une demande d’exécuter la réduction en parallèle. Le guide sur la concurrence de Swift explique les valeurs transférables et les exigences applicables aux structures dont les propriétés stockées sont transférables.

Pour ce petit calcul sur ordinateur, appelez directement l’une ou l’autre fonction et examinez le dictionnaire renvoyé. Choisissez le regroupement si les éléments des groupes restent utiles, ou l’accumulation directe si les totaux suffisent. Conservez les règles relatives à l’entrée vide et au total nul, rejetez explicitement les dépassements arithmétiques et ne triez que lors de la présentation du résultat.

Sources