Use Dictionary(grouping:by:) to collect synthetic Swift values by key, then use mapValues and reduce to calculate each total. This Mac tutorial also shows direct accumulation and checks for empty input, zero totals and integer overflow.
The task is to turn an in-memory array of neutral shape labels and whole-number quantities into one [String: Int] dictionary. All inputs are synthetic literals. The calculation reads no external records, makes no network requests and saves no results.
Define what one total per key means
Before choosing the collection methods, define the result contract. Here, every input element contributes its quantity exactly once to the total for its label. Labels use ordinary Swift string equality; the program performs no trimming or case conversion. Dictionary keys must conform to Hashable, and Swift supports strings as keys. See Apple’s Dictionary documentation.
The illustrative input contains two circle values, two square values and one triangle value. The intended totals are simple enough to check by hand:
| Label | Input quantities | Expected total |
|---|---|---|
circle |
4, 6 |
10 |
square |
3, 5 |
8 |
triangle |
0 |
0 |
This example uses the following rules:
- Empty input produces an empty dictionary.
- A label that appears with a zero quantity remains present with a zero total.
- Negative quantities are allowed and contribute through ordinary addition.
- A label absent from the input is absent from the result.
- Every intermediate sum must fit in
Int; otherwise, the function throws and returns no totals.
The distinction between an absent label and a zero total matters. A missing label means there was no matching input. A present label with zero can represent an explicit zero or quantities that cancel. Keep that distinction unless the reader of the result specifically needs a different convention.
Run the complete example on a Mac
Use an installed Swift toolchain. Swift.org’s macOS installation guide documents toolchain setup and the commands for creating and running an executable package. Once the toolchain is ready, open Terminal and create a small local project:
mkdir SyntheticTotals
cd SyntheticTotals
swift package init --name SyntheticTotals --type executable
Open the generated executable source file under Sources and replace its contents with the complete program below. The generated file may be inside a target-named subfolder. Keep the package manifest as generated; this program needs no added dependencies or imports because its types and calculation methods belong to Swift’s standard library.
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)")
}
From the package directory, run the executable using the command documented in Swift.org’s macOS guide:
swift run SyntheticTotals
The expected program output, apart from build messages, is:
circle: 10
square: 8
triangle: 0
Overflow check: rejected circle
The program includes checks for the worked totals, empty input, cancellation to zero and an intentional overflow. These are expected results derived from the sample and documented APIs. The code was inspected but was not executed during preparation because the available environment had no Swift compiler.
How does grouping followed by reduction work?
The first function deliberately separates the task into visible stages. Dictionary(grouping: values, by: { $0.key }) creates a dictionary whose values are arrays of matching elements. Apple documents that each resulting group is nonempty and preserves the elements’ order from the source sequence. See init(grouping:by:).
For the sample, the intermediate type is [String: [SyntheticValue]]. The circle group contains quantities 4 and 6; the square group contains 3 and 5; the triangle group contains 0. This intermediate representation is useful when another part of a local calculation also needs to inspect the members of each group.
Next, mapValues keeps the dictionary’s keys and transforms each group array into a single integer. That preservation of keys is the documented behavior of mapValues(_:). It also explains why the triangle entry survives even though its total is zero.
Inside that transformation, reduce(0) begins with zero and passes each successive element to the combining closure. The closure returns the next running total. Apple documents sequential reduction and specifies that reducing an empty sequence returns the initial value. See reduce(::).
There are two different empty cases here. An empty quantity array reduced from zero would yield zero. However, grouping an empty input creates no groups, so the complete function returns an empty dictionary rather than inventing a label with a zero total.
When should you accumulate totals directly?
Use totalDirectly when the final totals are the only required result. Its accumulator starts as an empty [String: Int] dictionary. For each input element, it looks up that label’s previous total, adds the quantity and stores the updated total.
The defaulted lookup supplies zero when a key is missing. Merely reading a default does not create an entry; the assignment in the next line stores the calculated total. Apple’s default-value subscript documentation explains both fallback lookup and in-place updates for values with value semantics.
reduce(into:) gives its closure mutable access to the accumulator. Apple recommends this form over ordinary reduce when building a result with copy-on-write behavior, such as a dictionary. See reduce(into:_:).
| What the next step needs | Choose | Reason |
|---|---|---|
| Group members and their totals | Group, then reduce | The intermediate arrays are available for inspection or reuse. |
| Only one integer per label | Direct accumulation | It avoids constructing the intermediate group arrays. |
| Repeatable text output | Either, then sort keys | Output ordering is a separate decision from aggregation. |
The intermediate-storage distinction follows from the code: grouping retains all input elements in group arrays, while direct accumulation retains one running total per distinct key. This is an implementation comparison, not a measured performance claim. The sample provides no timing benchmark.
How do you handle overflow and stable output?
checkedAdd calls addingReportingOverflow, which returns a partial result and an overflow flag. Apple specifies that the partial value contains the complete sum only when that flag is false. When overflow occurs, the returned partial value is truncated. The program therefore throws before storing it. See addingReportingOverflow(_:).
This contract checks every addition in input order within each key. For example, quantities Int.max, 1 and -1 for one label are rejected at the second addition, even though their mathematical final sum fits in Int. Supporting such inputs requires a different arithmetic strategy; accepting the truncated intermediate result would change the contract.
If several groups overflow, the grouping implementation does not promise which label’s error appears first: dictionary iteration order is unpredictable. The direct implementation encounters elements in source order. Both reject an overflowing calculation, but their first reported key can differ.
Sorting the result’s keys makes the displayed totals predictable. Swift dictionaries are unordered, so printing a dictionary directly should not be used to establish a presentation order. Apple describes this ordering boundary in its Dictionary documentation. The example sorts keys only after aggregation, leaving the calculation unchanged.
Do Foundation or Swift concurrency change this calculation?
Foundation is unnecessary for this value contract. Apple documents NSDictionary as an alternative when reference semantics are required. Here, a typed Swift dictionary already represents the required mapping from labels to integer totals. See Foundation’s NSDictionary documentation.
The calculation is synchronous and keeps its mutable accumulator inside the function. The sample’s SyntheticValue explicitly conforms to Sendable; this expresses a concurrency-related value contract, not a request to run the reduction concurrently. Swift’s Concurrency guide explains sendable values and the requirements for structures whose stored properties are sendable.
For this small desktop calculation, call either function directly and inspect its returned dictionary. Choose grouping when group members remain useful, or direct accumulation when totals are sufficient. Preserve the empty-input and zero-total rules, reject arithmetic overflow explicitly, and sort only when presenting the result.
Sources
- 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.