Gebruik Dictionary(grouping:by:) om synthetische Swift-waarden op sleutel te verzamelen, gebruik vervolgens mapValues en reduce om elk totaal te berekenen. Deze Mac-zelfstudie toont ook directe accumulatie en controles voor lege invoer, nul-totaals en integer-overloop.
De taak is om een in-memory-array van neutrale vormlabels en gehele hoeveelheden om te zetten in één [String: Int]-dictionary. Alle invoer zijn synthetische literalen. De berekening leest geen externe records, doet geen netwerkverzoeken en slaat geen resultaten op.
Definieer wat één totaal per sleutel betekent
Voordat de verzamelmethoden worden gekozen, definieer het resultaatcontract. Hier draagt elk invoerelement zijn hoeveelheid precies één keer bij aan het totaal voor zijn label. Labels gebruiken gewone Swift-stringgelijkheid; het programma voert geen trimming of case-conversie uit. Dictionary-sleutels moeten voldoen aan Hashable, en Swift ondersteunt strings als sleutels. Zie Apple's Dictionary-documentatie.
De illustrerende invoer bevat twee cirkelwaarden, twee vierkante waarden en één driehoekwaarde. De beoogde totalen zijn eenvoudig genoeg om met de hand te controleren:
| Label | Invoerhoeveelheden | Verwacht totaal |
|---|---|---|
circle |
4, 6 |
10 |
square |
3, 5 |
8 |
triangle |
0 |
0 |
Dit voorbeeld gebruikt de volgende regels:
- Lege invoer produceert een lege dictionary.
- Een label dat voorkomt met een nulhoeveelheid blijft aanwezig met een nultotaal.
- Negatieve hoeveelheden zijn toegestaan en dragen bij via gewone optelling.
- Een label dat ontbreekt in de invoer, ontbreekt in het resultaat.
- Elke tussensom moet in
Intpassen; anders gooit de functie een fout en retourneert geen totalen.
Het verschil tussen een afwezig label en een nultotaal is belangrijk. Een ontbrekend label betekent dat er geen overeenkomende invoer was. Een aanwezig label met nul kan een expliciete nul of hoeveelheden die elkaar opheffen vertegenwoordigen. Behoud dat onderscheid tenzij de lezer van het resultaat specifiek een ander conventie nodig heeft.
Voer het complete voorbeeld uit op een Mac
Gebruik een geïnstalleerde Swift-toolchain. Swift.org's macOS-installatiegids documenteert de toolchain-instelling en de commando's voor het maken en uitvoeren van een uitvoerbaar pakket. Zodra de toolchain klaar is, open Terminal en maak een klein lokaal project:
mkdir SyntheticTotals
cd SyntheticTotals
swift package init --name SyntheticTotals --type executable
Open het gegenereerde uitvoerbare bronbestand onder Sources en vervang de inhoud door het complete programma hieronder. Het gegenereerde bestand kan zich in een doelgenoemde submap bevoudigen. Houd het pakketmanifest zoals gegenereerd; dit programma heeft geen extra afhankelijkheden of imports nodig omdat zijn typen en berekeningsmethoden tot Swift's standaardbibliotheek behoren.
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)")
}
Vanaf de pakketmap, voer het uitvoerbare bestand uit met het commando gedocumenteerd in Swift.org's macOS-gids:
swift run SyntheticTotals
De verwachte programmatoutput, behalve build-berichten, is:
circle: 10
square: 8
triangle: 0
Overflow check: rejected circle
Het programma bevat controles voor de uitgerekende totalen, lege invoer, opheffing tot nul en een bewuste overloop. Dit zijn verwachte resultaten afgeleid van het voorbeeld en gedocumenteerde API's. De code is geïnspecteerd maar niet uitgevoerd tijdens de voorbereiding omdat de beschikbare omgeving geen Swift-compiler had.
Hoe werkt groeperen gevolgd door reduceren?
De eerste functie scheidt de taak bewust in zichtbare stadia. Dictionary(grouping: values, by: { $0.key }) creëert een dictionary wiens waarden arrays zijn van overeenkomende elementen. Apple documenteert dat elke resulterende groep niet-leeg is en de volgorde van de elementen uit de bronseries behoudt. Zie init(grouping:by:).
Voor het voorbeeld is het intermediaire type [String: [SyntheticValue]]. De cirkelgroep bevat hoeveelheden 4 en 6; de vierkante groep bevat 3 en 5; de driehoeksgroep bevat 0. Deze intermediaire representatie is nuttig als een ander deel van een lokale berekening ook de leden van elke groep moet inspecteren.
Vervolgens houdt mapValues de sleutels van de dictionary en transformeert elke groep-array in een enkel integer. Dat behoud van sleutels is het gedocumenteerde gedrag van mapValues(_:). Het verklaart ook waarom de driehoeksentry overleet zelfs al is het totaal nul.
Binnen die transformatie begint reduce(0) met nul en geeft elk opvolgend element door aan de combinatiesluiting. De sluiting retourneert de volgende lopende totaal. Apple documenteert sequentiële reductie en specificeert dat het reduceren van een lege reeks de beginwaarde retourneert. Zie reduce(::).
Er zijn hier twee verschillende lege gevallen. Een lege hoeveelheidsarray gereduceerd vanaf nul zou nul opleveren. Echter, het groeperen van een lege invoer creëert geen groepen, dus de complete functie retourneert een lege dictionary in plaats van een label met een nultotaal te inventariseren.
Wanneer moet u totalen direct accumuleren?
Gebruik totalDirectly wanneer de eindtotalen het enige vereiste resultaat zijn. De accumulator begint als een lege [String: Int]-dictionary. Voor elk invoerelement zoekt het het vorige totaal van dat label op, telt de hoeveelheid erbij op en slaat het bijgewerkte totaal op.
De standaard-zoekopdracht levert nul wanneer een sleutel ontbreekt. Alleen het lezen van een standaard creëert geen entry; de toewijzing in de volgende regel slaat het berekende totaal op. Apple's standaardwaarde-subscriptdocumentatie legt zowel fallback-zoekopdracht als in-place-updates voor waarden met waardesemantiek uit.
reduce(into:) geeft de sluiting veranderlijke toegang tot de accumulator. Apple adviseert deze vorm boven gewone reduce bij het bouwen van een resultaat met copy-on-write-gedrag, zoals een dictionary. Zie reduce(into:_:).
| Wat de volgende stap nodig heeft | Kies | Reden |
|---|---|---|
| Groepsleden en hun totalen | Groeperen, dan reduceren | De intermediaire arrays zijn beschikbaar voor inspectie of hergebruik. |
| Alleen één integer per label | Directe accumulatie | Het vermijdt het construeren van de intermediaire groepsarrays. |
| Herhaalbare tekstuitvoer | Beide, dan sleutels sorteren | Uitvoervolgorde is een afzonderlijke beslissing van aggregatie. |
Het onderscheid in intermediaire opslag volgt uit de code: groeperen bewaart alle invoerelementen in groepsarrays, terwijl directe accumulatie één lopend totaal per distincte sleutel bewaart. Dit is een implementatievergelijking, geen gemeten prestatieclaim. Het voorbeeld biedt geen timing-benchmark.
Hoe ga je om met overloop en stabiele uitvoer?
checkedAdd roept addingReportingOverflow aan, wat een gedeeltelijk resultaat en een overloopvlag retourneert. Apple specificeert dat de gedeeltelijke waarde de complete som alleen bevat wanneer die vlag false is. Bij overloop is de geretourneerde gedeeltelijke waarde afgekapt. Het programma gooit daarom een fout voordat het opslaat. Zie addingReportingOverflow(_:).
Dit contract controleert elke optelling in invoervolgorde binnen elke sleutel. Bijvoorbeeld hoeveelheden Int.max, 1 en -1 voor één label worden geweigerd bij de tweede optelling, zelfs al past hun wiskundige eindsom in Int. Het ondersteunen van dergelijke invoer vereist een andere rekenstrategie; het accepteren van het afgekapte intermediaire resultaat zou het contract veranderen.
Als meerdere groepen overloop hebben, belooft de groeperingsimplementatie niet welk label's fout het eerst verschijnt: dictionary-iteratievolgorde is onvoorspelbaar. De directe implementatie ontmoet elementen in bronvolgorde. Beide verwerpen een overlopende berekening, maar hun eerste gerapporteerde sleutel kan verschillen.
Het sorteren van de resultaatssleutels maakt de weergegeven totalen voorspelbaar. Swift-dictionaries zijn ongeordend, dus het direct printen van een dictionary mag niet gebruikt worden om een presentatievolgorde vast te leggen. Apple beschrijft deze orderingsgrens in zijn Dictionary-documentatie. Het voorbeeld sorteert sleutels alleen na aggregatie, waardoor de berekening ongewijzigd blijft.
Veranderen Foundation of Swift concurrency deze berekening?
Foundation is onnodig voor dit waardecontract. Apple documenteert NSDictionary als alternatief wanneer referentiesemantiek vereist is. Hier vertegenwoordigt een getypte Swift-dictionary al de vereiste mapping van labels naar gehele totalen. Zie Foundation's NSDictionary-documentatie.
De berekening is synchroon en houdt zijn veranderlijke accumulator binnen de functie. De voorbeeld-SyntheticValue voldoet expliciet aan Sendable; dit uit een concurrency-gerelateerd waardecontract, geen verzoek om de reductie gelijktijdig uit te voeren. Swift's Concurrency-gids legt sendable-waarden en de vereisten voor structuren wiens opgeslagen eigenschappen sendable zijn uit.
Voor deze kleine desktopberekening, roep een van de functies direct aan en inspecteer de geretourneerde dictionary. Kies groeperen wanneer groepsleden nuttig blijven, of directe accumulatie wanneer totalen voldoende zijn. Behoud de regels voor lege invoer en nultotalen, wees rekenkundige overloop expliciet af, en sorteer alleen bij het presenteren van het resultaat.
Bronnen
- Install Swift — macOS — Swift.org.
- Dictionary — Apple Developer Documentation.
- init(grouping:by:) — Apple Developer Documentation.
- mapValues(_:) — Apple Developer Documentation.
- reduce(::) — Apple Developer Documentation.
- reduce(into:_:) — Apple Developer Documentation.
- subscript(_:default:) — Apple Developer Documentation.
- addingReportingOverflow(_:) — Apple Developer Documentation.
- NSDictionary — Apple Developer Documentation.
- Concurrency — The Swift Programming Language.