Skip to content

Commit fab5b98

Browse files
authored
Merge pull request kodecocodes#610 from simonwhitaker/master
Improvements to Multiset
2 parents 0f433b6 + e7bce0d commit fab5b98

File tree

3 files changed

+64
-16
lines changed

3 files changed

+64
-16
lines changed

Multiset/Multiset.playground/Contents.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,19 @@ set2.add("Foo")
2424

2525
set2.isSubSet(of: set) // true
2626
set.isSubSet(of: set2) // false
27+
28+
// Convenience constructor: pass a Collection of Hashables to the constructor
29+
var cacti = Multiset<Character>("cacti")
30+
cacti.allItems
31+
var tactical = Multiset<Character>("tactical")
32+
cacti.isSubSet(of: tactical) // true
33+
tactical.isSubSet(of: cacti) // false
34+
35+
// Test ExpressibleByArrayLiteral protocol
36+
let set3: Multiset<String> = ["foo", "bar"]
37+
set3.count(for: "foo")
38+
39+
// Test Equatable protocol
40+
let set4 = Multiset<String>(set3.allItems)
41+
set4 == set3 // true
42+
set4 == set // false

Multiset/Multiset.playground/Sources/Multiset.swift

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@
77

88
import Foundation
99

10-
public struct Multiset<Element: Hashable> {
11-
private var storage: [Element: UInt] = [:]
10+
public struct Multiset<T: Hashable> {
11+
private var storage: [T: UInt] = [:]
1212
public private(set) var count: UInt = 0
1313

1414
public init() {}
1515

16-
public mutating func add (_ elem: Element) {
16+
public init<C: Collection>(_ collection: C) where C.Element == T {
17+
for element in collection {
18+
self.add(element)
19+
}
20+
}
21+
22+
public mutating func add (_ elem: T) {
1723
storage[elem, default: 0] += 1
1824
count += 1
1925
}
2026

21-
public mutating func remove (_ elem: Element) {
27+
public mutating func remove (_ elem: T) {
2228
if let currentCount = storage[elem] {
2329
if currentCount > 1 {
2430
storage[elem] = currentCount - 1
@@ -29,7 +35,7 @@ public struct Multiset<Element: Hashable> {
2935
}
3036
}
3137

32-
public func isSubSet (of superset: Multiset<Element>) -> Bool {
38+
public func isSubSet (of superset: Multiset<T>) -> Bool {
3339
for (key, count) in storage {
3440
let supersetcount = superset.storage[key] ?? 0
3541
if count > supersetcount {
@@ -39,12 +45,12 @@ public struct Multiset<Element: Hashable> {
3945
return true
4046
}
4147

42-
public func count(for key: Element) -> UInt {
48+
public func count(for key: T) -> UInt {
4349
return storage[key] ?? 0
4450
}
4551

46-
public var allItems: [Element] {
47-
var result = [Element]()
52+
public var allItems: [T] {
53+
var result = [T]()
4854
for (key, count) in storage {
4955
for _ in 0 ..< count {
5056
result.append(key)
@@ -56,7 +62,7 @@ public struct Multiset<Element: Hashable> {
5662

5763
// MARK: - Equatable
5864
extension Multiset: Equatable {
59-
public static func == (lhs: Multiset<Element>, rhs: Multiset<Element>) -> Bool {
65+
public static func == (lhs: Multiset<T>, rhs: Multiset<T>) -> Bool {
6066
if lhs.storage.count != rhs.storage.count {
6167
return false
6268
}
@@ -69,3 +75,10 @@ extension Multiset: Equatable {
6975
return true
7076
}
7177
}
78+
79+
// MARK: - ExpressibleByArrayLiteral
80+
extension Multiset: ExpressibleByArrayLiteral {
81+
public init(arrayLiteral elements: T...) {
82+
self.init(elements)
83+
}
84+
}

Multiset/README.markdown

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,30 @@
22

33
A multiset (also known as a bag) is a data structure similar to a regular set, but it can store multiple instances of the same element.
44

5-
The following all represent the same *set*, but do not represent the same *multiset*.
5+
For example, if I added the elements 1, 2, 2 to a regular set, the set would only contain two items, since adding 2 a second time has no effect.
66

77
```
8-
[1, 3, 2]
9-
[1, 3, 2, 2]
8+
var set = Set<Int>()
9+
set.add(1) // set is now [1]
10+
set.add(2) // set is now [1, 2]
11+
set.add(2) // set is still [1, 2]
1012
```
1113

12-
In this multiset implementation, ordering is unimportant. So these two multisets are identical:
14+
By comparison, after adding the elements 1, 2, 2 to a multiset, it would contain three items.
1315

1416
```
15-
[1, 2, 2]
16-
[2, 1, 2]
17+
var set = Multiset<Int>()
18+
set.add(1) // set is now [1]
19+
set.add(2) // set is now [1, 2]
20+
set.add(2) // set is now [1, 2, 2]
1721
```
1822

23+
You might be thinking that this looks an awful lot like an array. So why would you use a multiset? Let's consider the differences between the two…
24+
25+
- Ordering: arrays maintain the order of items added to them, multisets do not
26+
- Testing for membership: testing whether an element is a member of the collection is O(N) for arrays, O(1) for multisets.
27+
- Testing for subset: testing whether collection X is a subset of collection Y is a simple operation for a multiset, but complex for arrays
28+
1929
Typical operations on a multiset are:
2030

2131
- Add an element
@@ -24,6 +34,14 @@ Typical operations on a multiset are:
2434
- Get the count for the whole set (the number of items that have been added)
2535
- Check whether it is a subset of another multiset
2636

37+
One real-world use of multisets is to determine whether one string is a partial anagram of another. For example, the word "cacti" is a partial anagrams of "tactical". (In other words, I can rearrange the letters of "tactical" to make "cacti", with some letters left over.)
38+
39+
``` swift
40+
var cacti = Multiset<Character>("cacti")
41+
var tactical = Multiset<Character>("tactical")
42+
cacti.isSubSet(of: tactical) // true!
43+
```
44+
2745
## Implementation
2846

2947
Under the hood, this implementation of Multiset uses a dictionary to store a mapping of elements to the number of times they've been added.
@@ -32,7 +50,7 @@ Here's the essence of it:
3250

3351
``` swift
3452
public struct Multiset<Element: Hashable> {
35-
public private(set) var storage: [Element: UInt] = [:]
53+
private var storage: [Element: UInt] = [:]
3654

3755
public init() {}
3856
```
@@ -56,6 +74,7 @@ Here's how you'd use this method to add to the set we created earlier:
5674
```swift
5775
set.add("foo")
5876
set.add("foo")
77+
set.allItems // returns ["foo", "foo"]
5978
```
6079

6180
Our set now contains two elements, both the string "foo".

0 commit comments

Comments
 (0)