Skip to content

Commit 49ce108

Browse files
author
Thomas Heinis
committed
fix: dictionary literal with duplicate keys now sums counts, Also added validateCount helper for consistent count validation.
1 parent 078aacb commit 49ce108

2 files changed

Lines changed: 69 additions & 4 deletions

File tree

Sources/DataStructuresKit/Collections/Bag.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ public struct Bag<Element: Hashable> {
130130
}
131131
}
132132

133+
// MARK: - Validation
134+
135+
/// Validates that a count is positive.
136+
@usableFromInline
137+
internal static func validateCount(_ count: Int) -> Bool {
138+
count > 0
139+
}
140+
133141
// MARK: - Initialization
134142

135143
/// Creates an empty bag.
@@ -175,8 +183,9 @@ public struct Bag<Element: Hashable> {
175183
/// let bag = Bag(["apple": 3, "banana": 2])
176184
/// print(bag.totalCount) // 5
177185
/// ```
186+
@inlinable
178187
public init(_ counts: [Element: Int]) {
179-
precondition(counts.values.allSatisfy { $0 > 0 }, "All counts must be positive")
188+
precondition(counts.values.allSatisfy(Self.validateCount), "All counts must be positive")
180189
let total = counts.values.reduce(0, +)
181190
self.storage = Storage(counts: counts, totalCount: total)
182191
}
@@ -315,7 +324,7 @@ public struct Bag<Element: Hashable> {
315324
/// ```
316325
@inlinable
317326
public mutating func insert(_ element: Element, count: Int) {
318-
precondition(count > 0, "Count must be positive")
327+
precondition(Self.validateCount(count), "Count must be positive")
319328
ensureUnique()
320329
storage.counts[element, default: 0] += count
321330
storage.totalCount += count
@@ -363,7 +372,7 @@ public struct Bag<Element: Hashable> {
363372
@inlinable
364373
@discardableResult
365374
public mutating func remove(_ element: Element, count: Int) -> Int {
366-
precondition(count > 0, "Count must be positive")
375+
precondition(Self.validateCount(count), "Count must be positive")
367376
guard let current = storage.counts[element] else { return 0 }
368377

369378
ensureUnique()
@@ -546,7 +555,7 @@ extension Bag: ExpressibleByDictionaryLiteral {
546555
self.storage = Storage()
547556
for (element, count) in elements {
548557
precondition(count > 0, "All counts must be positive")
549-
storage.counts[element] = count
558+
storage.counts[element, default: 0] += count
550559
storage.totalCount += count
551560
}
552561
}

Tests/DataStructuresKitTests/CollectionsTests/BagTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,60 @@ struct BagTests {
312312
#expect(bag.uniqueCount == 9)
313313
#expect(bag.totalCount == 12)
314314
}
315+
316+
@Test("Dictionary literal with duplicate keys sums counts")
317+
func testDictionaryLiteralDuplicateKeys() {
318+
let bag: Bag<String> = ["a": 1, "a": 2, "b": 3]
319+
320+
#expect(bag.count(of: "a") == 3)
321+
#expect(bag.count(of: "b") == 3)
322+
#expect(bag.uniqueCount == 2)
323+
#expect(bag.totalCount == 6)
324+
}
325+
}
326+
327+
@Test("Dictionary literal basic usage")
328+
func testDictionaryLiteralBasic() {
329+
let bag: Bag<String> = ["apple": 3, "banana": 2]
330+
331+
#expect(bag.count(of: "apple") == 3)
332+
#expect(bag.count(of: "banana") == 2)
333+
#expect(bag.uniqueCount == 2)
334+
#expect(bag.totalCount == 5)
335+
}
336+
337+
@Test("Dictionary literal single element")
338+
func testDictionaryLiteralSingleElement() {
339+
let bag: Bag<String> = ["only": 5]
340+
341+
#expect(bag.count(of: "only") == 5)
342+
#expect(bag.uniqueCount == 1)
343+
#expect(bag.totalCount == 5)
344+
}
345+
346+
@Test("Dictionary literal empty")
347+
func testDictionaryLiteralEmpty() {
348+
let bag: Bag<String> = [:]
349+
350+
#expect(bag.isEmpty)
351+
#expect(bag.uniqueCount == 0)
352+
#expect(bag.totalCount == 0)
353+
}
354+
355+
@Test("Dictionary literal with count of 1")
356+
func testDictionaryLiteralCountOfOne() {
357+
let bag: Bag<String> = ["a": 1, "b": 1, "c": 1]
358+
359+
#expect(bag.count(of: "a") == 1)
360+
#expect(bag.count(of: "b") == 1)
361+
#expect(bag.count(of: "c") == 1)
362+
#expect(bag.uniqueCount == 3)
363+
#expect(bag.totalCount == 3)
364+
}
365+
366+
@Test("Count validation rejects zero and negative")
367+
func testCountValidation() {
368+
#expect(Bag<String>.validateCount(1) == true)
369+
#expect(Bag<String>.validateCount(0) == false)
370+
#expect(Bag<String>.validateCount(-5) == false)
315371
}

0 commit comments

Comments
 (0)