diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Collections.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Collections.xcscheme
index a3e92863b..dad84604b 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Collections.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Collections.xcscheme
@@ -49,6 +49,16 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
+
+
+
+ .target(
+ name: "MultiSets",
+ swiftSettings: settings),
+ .testTarget(
+ name: "MultiSetsTests",
+ dependencies: ["MultiSets", "_CollectionsTestSupport"],
+ swiftSettings: settings),
+
// OrderedSet, OrderedDictionary
.target(
name: "OrderedCollections",
diff --git a/Sources/Collections/Collections.swift b/Sources/Collections/Collections.swift
index beacfc238..370925640 100644
--- a/Sources/Collections/Collections.swift
+++ b/Sources/Collections/Collections.swift
@@ -10,5 +10,6 @@
//===----------------------------------------------------------------------===//
@_exported import DequeModule
+@_exported import MultiSets
@_exported import OrderedCollections
@_exported import PriorityQueueModule
diff --git a/Sources/MultiSets/CountedSet/CountedSet+Collection.swift b/Sources/MultiSets/CountedSet/CountedSet+Collection.swift
new file mode 100644
index 000000000..388a7aff5
--- /dev/null
+++ b/Sources/MultiSets/CountedSet/CountedSet+Collection.swift
@@ -0,0 +1,105 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+extension CountedSet: Collection {
+ /// The position of an element in a counted set.
+ @frozen
+ public struct Index: Comparable {
+ /// An index in the underlying dictionary storage of a counted set.
+ ///
+ /// This is used to distinguish between indices that point to elements with
+ /// different values.
+ @usableFromInline
+ let storageIndex: Dictionary.Index
+
+ /// The relative position of the element.
+ ///
+ /// This doesn't actually correspond to a distinct part of memory. Instead,
+ /// it is used to distinguish between indices that point to elements of the
+ /// same value.
+ ///
+ /// For example, the first index pointing to a value has an index of 0, the
+ /// second index pointing to that value will have an index of 1, and so on.
+ ///
+ /// When a counted set is subscripted with an index, the stored multiplicity
+ /// is retrieved using the `storageIndex` and compared with the `position`
+ /// to determine the index's validity.
+ @usableFromInline
+ let position: UInt
+
+ @inlinable
+ public static func < (
+ lhs: CountedSet.Index,
+ rhs: CountedSet.Index
+ ) -> Bool {
+ guard lhs.storageIndex != rhs.storageIndex else {
+ return lhs.storageIndex < rhs.storageIndex
+ }
+ return lhs.position < rhs.position
+ }
+
+ @usableFromInline
+ init(storageIndex: Dictionary.Index, position: UInt) {
+ self.storageIndex = storageIndex
+ self.position = position
+ }
+ }
+
+ @inlinable
+ public func index(after i: Index) -> Index {
+ guard i.position + 1 < rawValue[i.storageIndex].value else {
+ return Index(
+ storageIndex: rawValue.index(after: i.storageIndex),
+ position: 0
+ )
+ }
+
+ return Index(storageIndex: i.storageIndex, position: i.position + 1)
+ }
+
+ @inlinable
+ public subscript(position: Index) -> Element {
+ let keyPair = rawValue[position.storageIndex]
+ precondition(
+ position.position < keyPair.value,
+ "Attempting to access CountedSet elements using an invalid index"
+ )
+ return keyPair.key
+ }
+
+ /// The number of elements in the set.
+ ///
+ /// - Complexity: O(*k*), where *k* is the number of unique elements in the
+ /// set.
+ @inlinable
+ public var count: Int {
+ Int(rawValue.values.reduce(.zero, +))
+ }
+
+ @inlinable
+ public var startIndex: Index {
+ Index(storageIndex: rawValue.startIndex, position: 0)
+ }
+
+ @inlinable
+ public var endIndex: Index {
+ Index(storageIndex: rawValue.endIndex, position: 0)
+ }
+
+ /// A value equal to the number of unique elements in the set.
+ ///
+ /// - Complexity: O(*k*), where *k* is the number of unique elements in the
+ /// set.
+ @inlinable
+ public var underestimatedCount: Int {
+ rawValue.count
+ }
+}
diff --git a/Sources/MultiSets/CountedSet/CountedSet+ExpressibleByDictionaryLiteral.swift b/Sources/MultiSets/CountedSet/CountedSet+ExpressibleByDictionaryLiteral.swift
new file mode 100644
index 000000000..27efd32c0
--- /dev/null
+++ b/Sources/MultiSets/CountedSet/CountedSet+ExpressibleByDictionaryLiteral.swift
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+extension CountedSet: ExpressibleByDictionaryLiteral {
+ /// Creates a counted set initialized with a dictionary literal.
+ ///
+ /// Do not call this initializer directly. It is called by the compiler to
+ /// handle dictionary literals. To use a dictionary literal as the initial
+ /// value of a counted set, enclose a comma-separated list of key-value pairs
+ /// in square brackets.
+ ///
+ /// For example, the code sample below creates a counted set with string keys.
+ ///
+ /// let countriesOfOrigin = ["BR": 2, "GH": 1, "JP": 5]
+ /// print(countriesOfOrigin)
+ /// // Prints "["BR", "BR", "JP", "JP", "JP", "JP", "JP", "GH"]"
+ ///
+ /// - Parameter elements: The element-multiplicity pairs that will make up the
+ /// new counted set.
+ /// - Precondition: Each element must be unique.
+ @inlinable
+ public init(dictionaryLiteral elements: (Element, UInt)...) {
+ _storage = RawValue(
+ uniqueKeysWithValues: elements.lazy.filter { $0.1 > .zero }
+ )
+ }
+}
+
diff --git a/Sources/MultiSets/CountedSet/CountedSet+Sequence.swift b/Sources/MultiSets/CountedSet/CountedSet+Sequence.swift
new file mode 100644
index 000000000..48eac7018
--- /dev/null
+++ b/Sources/MultiSets/CountedSet/CountedSet+Sequence.swift
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+extension CountedSet: Sequence {
+ /// Returns an iterator over the elements of this collection.
+ ///
+ /// - Complexity: O(1)
+ /// - Remark: A type-erased wrapper is used instead of an opaque type purely
+ /// to preserve compatibility with older versions of Swift.
+ @inlinable
+ public func makeIterator() -> AnyIterator {
+ AnyIterator(
+ _storage
+ .lazy
+ .map { (key, value) -> [Repeated] in
+ let repetitions = value.quotientAndRemainder(
+ dividingBy: UInt(Int.max)
+ )
+
+ var result = [Repeated](
+ repeating: repeatElement(key, count: .max),
+ count: Int(repetitions.quotient)
+ )
+ result.append(repeatElement(key, count: Int(repetitions.remainder)))
+
+ return result
+ }
+ .joined()
+ .joined()
+ .makeIterator()
+ )
+ }
+}
diff --git a/Sources/MultiSets/CountedSet/CountedSet+SetAlgebra.swift b/Sources/MultiSets/CountedSet/CountedSet+SetAlgebra.swift
new file mode 100644
index 000000000..e9ae8fda1
--- /dev/null
+++ b/Sources/MultiSets/CountedSet/CountedSet+SetAlgebra.swift
@@ -0,0 +1,187 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+extension CountedSet: SetAlgebra {
+ @inlinable
+ public init() {
+ _storage = RawValue()
+ }
+
+ /// Returns a new set with the greater number of elements of both this and the
+ /// given set.
+ /// - Parameter other: A set of the same type as the current set.
+ /// - Returns: A new set with the elements of this set and `other`, preserving
+ /// the higher multiplicity.
+ /// - Note: This function does **not** add the multiplicities of each set
+ /// together. Rather, it discards the lower multiplicity for each element.
+ @inlinable
+ public __consuming func union(_ other: __owned CountedSet)
+ -> CountedSet {
+ var result = self
+ result.formUnion(other)
+ return result
+ }
+
+ /// Returns a new set with the lesser number of elements of both this and the
+ /// given set.
+ /// - Parameter other: A set of the same type as the current set.
+ /// - Returns: A new set with the elements of this set and `other`, preserving
+ /// the lower multiplicity.
+ /// - Complexity: O(*k*), where *k* is the number of unique elements in the
+ /// current set.
+ @inlinable
+ public __consuming func intersection(_ other: CountedSet)
+ -> CountedSet {
+ var result = self
+ result.formIntersection(other)
+ return result
+ }
+
+
+ /// Returns a new set with the difference between the number of elements of
+ /// both this and the given set.
+ /// - Parameter other: A set of the same type as the current set.
+ /// - Returns: A new set with the elements of this set and `other`, taking the
+ /// difference between multiplicities.
+ @inlinable
+ public __consuming func symmetricDifference(
+ _ other: __owned CountedSet
+ ) -> CountedSet {
+ var result = self
+ result.formSymmetricDifference(other)
+ return result
+ }
+
+ /// Inserts the given element in the set.
+ ///
+ /// If an element equal to `newMember` is already contained in the set, its
+ /// multiplicity is incremented by one. In this example, a new element is
+ /// inserted into `classDays`, a set of days of the week.
+ ///
+ /// enum DayOfTheWeek: Int {
+ /// case sunday, monday, tuesday, wednesday, thursday,
+ /// friday, saturday
+ /// }
+ ///
+ /// var classDays: CountedSet = [.wednesday, .friday]
+ /// print(classDays.insert(.monday))
+ /// // Prints "(true, .monday)"
+ /// print(classDays)
+ /// // Prints "[.friday, .wednesday, .monday]"
+ ///
+ /// print(classDays.insert(.friday))
+ /// // Prints "(true, .friday)"
+ /// print(classDays)
+ /// // Prints "[.friday, .friday, .wednesday, .monday]"
+ ///
+ /// - Parameter newMember: An element to insert into the set.
+ /// - Complexity: Amortized O(1)
+ /// - Note: Insertion is always performed, in contrast to an uncounted set.
+ /// This means that the result's `inserted` value is always `true`.
+ @inlinable
+ @discardableResult
+ public mutating func insert(_ newMember: __owned Element)
+ -> (inserted: Bool, memberAfterInsert: Element) {
+ _storage[newMember, default: 0] += 1
+ return (inserted: true, memberAfterInsert: newMember)
+ }
+
+ /// Removes the given element.
+ ///
+ /// If an element equal to `member` is contained in the set, its
+ /// multiplicity is decremented by one.
+ /// - Parameter member: An element to remove from the set.
+ /// - Complexity: O(*k*), where *k* is the number of unique elements in the
+ /// set, if the multiplicity of the given element was one. Otherwise, O(1).
+ /// - Note: This method is *not* idempotent, in contrast to an uncounted set,
+ /// as multiple instances of the given element may be present.
+ @inlinable
+ @discardableResult
+ public mutating func remove(_ member: Element) -> Element? {
+ guard let oldMultiplicity = rawValue[member] else {
+ return nil
+ }
+
+ if oldMultiplicity > 1 {
+ _storage[member] = oldMultiplicity &- 1
+ } else {
+ _storage.removeValue(forKey: member)
+ }
+ return member
+ }
+
+ /// Inserts the given element in the set and replaces equal elements that are
+ /// already present.
+ ///
+ /// If an element equal to `newMember` is contained in the set, its
+ /// multiplicity is transferred to `newMember` and incremented by one.
+ /// - Parameter newMember: An element to insert into the set.
+ /// - Returns: The element equal to `newMember` that was contained in the set,
+ /// if any.
+ /// - Complexity: O(*k*), where *k* is the number of unique elements in the
+ /// set.
+ @inlinable
+ @discardableResult
+ public mutating func update(with newMember: __owned Element) -> Element? {
+ guard let oldMemberIndex = rawValue.index(forKey: newMember) else {
+ insert(newMember)
+ return nil
+ }
+
+ let (oldMember, oldValue) = _storage.remove(at: oldMemberIndex)
+ _storage[newMember] = oldValue + 1
+ return oldMember
+ }
+
+ /// Combines the given set into the current set, keeping the higher number of
+ /// elements.
+ /// - Parameter other: A set of the same type as the current set.
+ /// - Returns: A new set with the elements of this set and `other`, preserving
+ /// the higher multiplicity.
+ /// - Note: This function does **not** add the multiplicities of each set
+ /// together. Rather, it discards the lower multiplicity for each element.
+ @inlinable
+ public mutating func formUnion(_ other: __owned CountedSet) {
+ _storage.merge(other.rawValue, uniquingKeysWith: Swift.max)
+ }
+
+ /// Combines the given set into the current set, keeping the lower number of
+ /// elements.
+ /// - Parameter other: A set of the same type as the current set.
+ /// - Returns: A new set with the elements of this set and `other`, preserving
+ /// the lower multiplicity.
+ /// - Complexity: O(*k*), where *k* is the number of unique elements in the
+ /// current set.
+ @inlinable
+ public mutating func formIntersection(_ other: CountedSet) {
+ _storage = RawValue(
+ uniqueKeysWithValues: rawValue.lazy.compactMap { key, value in
+ other.rawValue[key].map { (key, Swift.min($0, value)) }
+ }
+ )
+ }
+
+ /// Combines the given set into the current set, keeping the difference
+ /// between the number of elements.
+ /// - Parameter other: A set of the same type as the current set.
+ /// - Returns: A new set with the elements of this set and `other`, taking the
+ /// difference between multiplicities.
+ @inlinable
+ public mutating func formSymmetricDifference(
+ _ other: __owned CountedSet
+ ) {
+ _storage = rawValue.merging(other.rawValue) {
+ $0 >= $1 ? $0 &- $1 : $1 &- $0
+ }.filter {
+ $0.value != .zero
+ }
+ }
+}
diff --git a/Sources/MultiSets/CountedSet/CountedSet+Sum.swift b/Sources/MultiSets/CountedSet/CountedSet+Sum.swift
new file mode 100644
index 000000000..fe2d241a6
--- /dev/null
+++ b/Sources/MultiSets/CountedSet/CountedSet+Sum.swift
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+extension CountedSet {
+ /// Combines the elements two sets, adding multiplicities together.
+ @inlinable
+ public static func + (lhs: Self, rhs: __owned Self) -> Self {
+ var result = lhs
+ result += rhs
+ return result
+ }
+
+ /// Adds the elements of a set to another set, adding multiplicities
+ /// together.
+ @inlinable
+ public static func += (lhs: inout Self, rhs: __owned Self) {
+ lhs._storage.merge(rhs.rawValue, uniquingKeysWith: +)
+ }
+}
diff --git a/Sources/MultiSets/CountedSet/CountedSet.swift b/Sources/MultiSets/CountedSet/CountedSet.swift
new file mode 100644
index 000000000..cd9d719a0
--- /dev/null
+++ b/Sources/MultiSets/CountedSet/CountedSet.swift
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+/// An unordered, counted multiset.
+@frozen
+public struct CountedSet: RawRepresentable {
+ // Allows internal setter to be referenced from inlined code
+ @usableFromInline
+ internal var _storage = RawValue()
+
+ @inlinable @inline(__always)
+ public var rawValue: [Element: UInt] { _storage }
+
+ @inlinable
+ public var isEmpty: Bool { rawValue.isEmpty }
+
+ /// Creates an empty counted set with preallocated space for at least the
+ /// specified number of unique elements.
+ ///
+ /// - Parameter minimumCapacity: The minimum number of elements that the
+ /// newly created counted set should be able to store without reallocating
+ /// its storage buffer.
+ @inlinable
+ public init(minimumCapacity: Int) {
+ self._storage = .init(minimumCapacity: minimumCapacity)
+ }
+
+ @inlinable
+ public init?(rawValue: [Element: UInt]) {
+ guard rawValue.values.allSatisfy({ $0 > .zero }) else { return nil }
+ _storage = rawValue
+ }
+}
diff --git a/Tests/MultiSetsTests/CountedSet/CountedSet Tests.swift b/Tests/MultiSetsTests/CountedSet/CountedSet Tests.swift
new file mode 100644
index 000000000..4e4565521
--- /dev/null
+++ b/Tests/MultiSetsTests/CountedSet/CountedSet Tests.swift
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+import MultiSets
+
+import _CollectionsTestSupport
+
+class CountedSetTests: CollectionTestCase {
+ func test_empty() {
+ let s = CountedSet()
+ expectEqualElements(s, [])
+ expectEqual(s.count, 0)
+ }
+
+ func test_init_minimumCapacity() {
+ let s = CountedSet(minimumCapacity: 1000)
+ expectGreaterThanOrEqual(s.rawValue.capacity, 1000)
+ }
+}
diff --git a/Tests/MultiSetsTests/CountedSet/CountedSet+SetAlgebra Tests.swift b/Tests/MultiSetsTests/CountedSet/CountedSet+SetAlgebra Tests.swift
new file mode 100644
index 000000000..7c3beb96c
--- /dev/null
+++ b/Tests/MultiSetsTests/CountedSet/CountedSet+SetAlgebra Tests.swift
@@ -0,0 +1,159 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+import MultiSets
+
+import _CollectionsTestSupport
+
+
+private let x: CountedSet = ["a": 1, "b": 2, "c": 3, "d": 4]
+private let y: CountedSet = ["e", "f", "a", "f"]
+
+class CountedSetSetAlgebraTests: CollectionTestCase {
+ /// `S() == []`
+ func testEmptyArrayLiteralInitialization() {
+ XCTAssertEqual(CountedSet(), [])
+ }
+
+ /// `x.intersection(x) == x`
+ func testIdempotentIntersection() {
+ XCTAssertEqual(x.intersection(x), x)
+ }
+
+ /// `x.intersection([]) == []`
+ func testEmptyIntersection() {
+ XCTAssertEqual(x.intersection([]), [])
+ }
+
+ /// `x.union(x) == x`
+ func testIdempotentUnion() {
+ XCTAssertEqual(x.union(x), x)
+ }
+
+ /// `x.union([]) == x`
+ func testEmptyUnion() {
+ XCTAssertEqual(x.union([]), x)
+ }
+
+ /// `x.contains(e)` implies `x.union(y).contains(e)`
+ func testUnionContainsElementsOfCurrentSet() {
+ let union = x.union(y)
+ x.rawValue.keys.forEach { e in XCTAssert(union.contains(e)) }
+ }
+
+ /// `x.union(y).contains(e)` implies `x.contains(e) || y.contains(e)`
+ func testUnionContainsOnlyElementsOfCurrentOrGivenSets() {
+ x.union(y).rawValue.keys.forEach { e in
+ XCTAssert(x.contains(e) || y.contains(e))
+ }
+ }
+
+ /// `x.contains(e) && y.contains(e)` if and only if
+ /// `x.intersection(y).contains(e)`
+ func testIntersectionContainsOnlyAllElementsOfCurrentOrGivenSets() {
+ XCTAssertEqual(
+ x.rawValue.keys.filter { (e: Character) in y.contains(e) },
+ Array(x.intersection(y).rawValue.keys)
+ )
+ }
+
+ /// `x.isSubset(of: y)` implies `x.union(y) == y`
+ func testSubsetDomination() {
+ let y: CountedSet = ["a": 1, "b": 2, "c": 3, "d": 4, "n": 9]
+ assert(x.isSubset(of: y), "Antecedent not satisfied")
+ XCTAssertEqual(x.union(y), y)
+ }
+
+ /// `x.isSuperset(of: y)` implies `x.union(y) == x`
+ func testSupersetAbsorption() {
+ let y: CountedSet = ["b", "b"]
+ assert(x.isSuperset(of: y), "Antecedent not satisfied")
+ XCTAssertEqual(x.union(y), x)
+ }
+
+ /// `x.isSubset(of: y)` if and only if `y.isSuperset(of: x)`
+ func testSubsetOfSuperset() {
+ XCTAssertEqual(x.isSubset(of: y), y.isSuperset(of: x))
+ let y: CountedSet = ["b", "b"]
+ XCTAssertEqual(x.isSubset(of: y), y.isSuperset(of: x))
+ }
+
+ /// `x.isStrictSuperset(of: y)` if and only if `x.isSuperset(of: y) && x != y`
+ func testStrictSuperset() {
+ var y = x
+ XCTAssertEqual(x.isStrictSuperset(of: y), x.isSuperset(of: y) && x != y)
+ y.remove("a")
+ XCTAssertEqual(x.isStrictSuperset(of: y), x.isSuperset(of: y) && x != y)
+ }
+
+ /// `x.isStrictSubset(of: y)` if and only if `x.isSubset(of: y) && x != y`
+ func testStrictSubset() {
+ var y = x
+ XCTAssertEqual(x.isStrictSubset(of: y), x.isSubset(of: y) && x != y)
+ y.insert("n")
+ XCTAssertEqual(x.isStrictSubset(of: y), x.isSubset(of: y) && x != y)
+ }
+
+ func testSymmetricDifference() {
+ XCTAssertEqual(
+ x.symmetricDifference(y),
+ ["b": 2, "c": 3, "d": 4, "e": 1, "f": 2]
+ )
+ }
+
+ func testSymmetricDifferenceWithLargerOperand() {
+ XCTAssertEqual(
+ x.symmetricDifference(["b": 5]),
+ ["a": 1, "b": 3, "c": 3, "d": 4]
+ )
+ }
+
+ func testUpdateExisting() {
+ var referenceStrings: CountedSet = [
+ "testing",
+ "testing",
+ "one",
+ "two",
+ "three",
+ ]
+
+ let newTestingString: NSMutableString = "testing"
+ assert(
+ referenceStrings.first { $0 == "testing" }! !== newTestingString,
+ "The new string is identical to what it is meant to replace"
+ )
+ referenceStrings.update(with: newTestingString)
+ XCTAssertIdentical(
+ referenceStrings.first { $0 == "testing" }!,
+ newTestingString
+ )
+ XCTAssertEqual(referenceStrings.rawValue["testing"], 3)
+ }
+
+ func testUpdateNew() {
+ var s: CountedSet = ["dog"]
+ s.update(with: "cow")
+ XCTAssertEqual(s, ["dog", "cow"])
+ }
+
+ func testRemoveEmpty() {
+ var s = CountedSet()
+ XCTAssertNil(s.remove(42))
+ XCTAssert(s.isEmpty)
+ }
+
+ func testRemoveExisting() {
+ var s: CountedSet = ["testing", "testing"]
+ XCTAssertEqual(s.remove("testing"), "testing")
+ XCTAssertEqual(s, ["testing"])
+ }
+}
diff --git a/Tests/MultiSetsTests/CountedSet/CountedSet+Sum Tests.swift b/Tests/MultiSetsTests/CountedSet/CountedSet+Sum Tests.swift
new file mode 100644
index 000000000..260756899
--- /dev/null
+++ b/Tests/MultiSetsTests/CountedSet/CountedSet+Sum Tests.swift
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Collections open source project
+//
+// Copyright (c) 2021 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+import MultiSets
+
+import _CollectionsTestSupport
+
+
+private let x: CountedSet = ["a": 1, "b": 2, "c": 3, "d": 4]
+private let y: CountedSet = ["e", "f", "a", "f"]
+
+class CountedSetSumTests: CollectionTestCase {
+ func testSum() {
+ XCTAssertEqual(
+ x + y,
+ ["a": 2, "b": 2, "c": 3, "d": 4, "e": 1, "f": 2]
+ )
+ }
+}