Använd Dictionary(grouping:by:) för att samla syntetiska Swift-värden efter nyckel och använd sedan mapValues och reduce för att beräkna varje totalsumma. Den här Mac-handledningen visar också direkt ackumulering och kontroller för tom indata, nollsumma och heltalsspill.
Uppgiften är att omvandla en array i minnet med neutrala formetiketter och heltalskvantiteter till en enda [String: Int]-ordbok. Alla indata är syntetiska literaler. Beräkningen läser inga externa poster, gör inga nätverksanrop och sparar inga resultat.
Definiera vad en totalsumma per nyckel innebär
Definiera resultatets kontrakt innan du väljer samlingsmetoder. Här bidrar varje indataelement exakt en gång med sin kvantitet till totalsumman för sin etikett. Etiketter använder vanlig Swift-stränglikhet; programmet trimmar inte blanksteg och ändrar inte skiftläge. Ordboksnycklar måste uppfylla Hashable, och Swift stöder strängar som nycklar. Se Apples dokumentation om Dictionary.
Den illustrativa indatan innehåller två cirkelvärden, två kvadratvärden och ett triangelvärde. De avsedda totalsummorna är enkla nog att kontrollera för hand:
| Etikett | Indatakvantiteter | Förväntad totalsumma |
|---|---|---|
circle |
4, 6 |
10 |
square |
3, 5 |
8 |
triangle |
0 |
0 |
Det här exemplet följer följande regler:
- Tom indata ger en tom ordbok.
- En etikett som förekommer med kvantiteten noll finns kvar med totalsumman noll.
- Negativa kvantiteter tillåts och bidrar genom vanlig addition.
- En etikett som saknas i indatan saknas i resultatet.
- Varje mellansumma måste rymmas i
Int; annars kastar funktionen ett fel och returnerar inga totalsummor.
Skillnaden mellan en etikett som saknas och en totalsumma på noll är viktig. En saknad etikett innebär att det inte fanns någon matchande indata. En etikett som finns med värdet noll kan representera ett uttryckligt nollvärde eller kvantiteter som tar ut varandra. Behåll den skillnaden om inte resultatets mottagare uttryckligen behöver en annan konvention.
Kör hela exemplet på en Mac
Använd en installerad Swift-verktygskedja. Swift.orgs installationsguide för macOS beskriver hur verktygskedjan konfigureras och vilka kommandon som används för att skapa och köra ett körbart paket. När verktygskedjan är klar öppnar du Terminal och skapar ett litet lokalt projekt:
mkdir SyntheticTotals
cd SyntheticTotals
swift package init --name SyntheticTotals --type executable
Öppna den genererade källfilen för den körbara filen under Sources och ersätt dess innehåll med hela programmet nedan. Den genererade filen kan ligga i en undermapp med samma namn som målet. Behåll paketmanifestet som det genererades; programmet behöver inga tillagda beroenden eller importer eftersom dess typer och beräkningsmetoder ingår i Swifts standardbibliotek.
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)")
}
Kör den körbara filen från paketkatalogen med kommandot som dokumenteras i Swift.orgs macOS-guide:
swift run SyntheticTotals
Det förväntade programutdata, bortsett från byggmeddelanden, är:
circle: 10
square: 8
triangle: 0
Overflow check: rejected circle
Programmet innehåller kontroller av de genomräknade totalsummorna, tom indata, annullering till noll och ett avsiktligt spill. Resultaten är förväntade utifrån exemplet och de dokumenterade API:erna. Koden granskades men kördes inte under förberedelserna eftersom den tillgängliga miljön saknade en Swift-kompilator.
Hur fungerar gruppering följt av reduktion?
Den första funktionen delar avsiktligt uppgiften i tydliga steg. Dictionary(grouping: values, by: { $0.key }) skapar en ordbok där värdena är arrayer med matchande element. Apple dokumenterar att varje resulterande grupp är icke-tom och bevarar elementens ordning från källsekvensen. Se init(grouping:by:).
För exemplet är den mellanliggande typen [String: [SyntheticValue]]. Cirkelgruppen innehåller kvantiteterna 4 och 6; kvadratgruppen innehåller 3 och 5; triangelgruppen innehåller 0. Den här mellanliggande representationen är användbar när en annan del av en lokal beräkning också behöver granska medlemmarna i varje grupp.
Därefter behåller mapValues ordbokens nycklar och omvandlar varje grupparray till ett enda heltal. Att nycklarna bevaras är det dokumenterade beteendet för mapValues(_:). Det förklarar också varför triangelposten finns kvar trots att dess totalsumma är noll.
Inuti den här omvandlingen börjar reduce(0) på noll och skickar varje efterföljande element till kombineringsstängningen. Stängningen returnerar nästa löpande totalsumma. Apple dokumenterar sekventiell reduktion och anger att reduktion av en tom sekvens returnerar startvärdet. Se reduce(::).
Här finns två olika tomma fall. En tom kvantitetsarray som reduceras från noll skulle ge noll. Men gruppering av tom indata skapar inga grupper, så hela funktionen returnerar en tom ordbok i stället för att hitta på en etikett med totalsumman noll.
När bör du ackumulera totalsummor direkt?
Använd totalDirectly när de slutliga totalsummorna är det enda resultatet som behövs. Dess ackumulator börjar som en tom [String: Int]-ordbok. För varje indataelement letar den upp föregående totalsumma för etiketten, lägger till kvantiteten och sparar den uppdaterade totalsumman.
Uppslagningen med standardvärde ger noll när en nyckel saknas. Att bara läsa ett standardvärde skapar ingen post; tilldelningen på nästa rad sparar den beräknade totalsumman. Apples dokumentation om subskript med standardvärde förklarar både reservvärde vid uppslagning och uppdateringar på plats för värden med värdesemantik.
reduce(into:) ger dess stängning muterbar åtkomst till ackumulatorn. Apple rekommenderar den här formen framför vanlig reduce när du bygger ett resultat med copy-on-write-beteende, till exempel en ordbok. Se reduce(into:_:).
| Vad nästa steg behöver | Välj | Skäl |
|---|---|---|
| Gruppmedlemmar och deras totalsummor | Gruppera och reducera sedan | De mellanliggande arrayerna är tillgängliga för granskning eller återanvändning. |
| Endast ett heltal per etikett | Direkt ackumulering | Det undviker att skapa mellanliggande grupparrayer. |
| Upprepningsbart textutdata | Båda, och sortera sedan nycklarna | Utdataordningen är ett separat beslut från aggregeringen. |
Skillnaden i mellanlagring framgår av koden: gruppering behåller alla indataelement i grupparrayer, medan direkt ackumulering behåller en löpande totalsumma per unik nyckel. Detta är en jämförelse av implementationerna, inte ett uppmätt prestandapåstående. Exemplet innehåller inget tidstest.
Hur hanterar du spill och stabil utdata?
checkedAdd anropar addingReportingOverflow, som returnerar ett delresultat och en spillflagga. Apple anger att delvärdet innehåller den fullständiga summan endast när flaggan är false. Vid spill trunkeras det returnerade delvärdet. Programmet kastar därför ett fel innan det sparas. Se addingReportingOverflow(_:).
Det här kontraktet kontrollerar varje addition i indatans ordning inom varje nyckel. Till exempel avvisas kvantiteterna Int.max, 1 och -1 för en etikett vid den andra additionen, även om deras matematiska slutsumma ryms i Int. Sådana indata kräver en annan aritmetisk strategi; att godta den trunkerade mellansumman skulle ändra kontraktet.
Om flera grupper får spill garanterar grupperingsimplementationen inte vilken etiketts fel som visas först: ordbokens iterationsordning är oförutsägbar. Den direkta implementationen går igenom elementen i källordning. Båda avvisar en beräkning med spill, men nyckeln som rapporteras först kan skilja sig.
Sortering av resultatets nycklar gör de visade totalsummorna förutsägbara. Swift-ordböcker saknar ordning, så direkt utskrift av en ordbok bör inte användas för att fastställa presentationsordningen. Apple beskriver denna ordningsbegränsning i sin dokumentation om Dictionary. Exemplet sorterar nycklarna först efter aggregeringen och lämnar beräkningen oförändrad.
Ändrar Foundation eller Swift-concurrency den här beräkningen?
Foundation behövs inte för det här värdekontraktet. Apple beskriver NSDictionary som ett alternativ när referenssemantik krävs. Här representerar en typad Swift-ordbok redan den nödvändiga mappningen från etiketter till heltalstotaler. Se Foundations dokumentation om NSDictionary.
Beräkningen är synkron och håller sin muterbara ackumulator inne i funktionen. Exemplet låter uttryckligen SyntheticValue uppfylla Sendable; det uttrycker ett värdekontrakt med koppling till samtidighet och innebär inte att reduktionen ska köras samtidigt. Swifts guide till samtidighet förklarar skickbara värden och kraven på strukturer vars lagrade egenskaper är skickbara.
För den här lilla skrivbordsberäkningen anropar du någon av funktionerna direkt och granskar den returnerade ordboken. Välj gruppering när gruppmedlemmarna fortfarande är användbara, eller direkt ackumulering när totalsummorna räcker. Behåll reglerna för tom indata och nollsumma, avvisa aritmetiskt spill uttryckligen och sortera endast när resultatet presenteras.
Källor
- Installera 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.