Skip to content

Commit fb523d6

Browse files
author
Simon Whitaker
committed
Add ctor for instantiating with a collection of Hashables
1 parent 6c7eb0c commit fb523d6

File tree

3 files changed

+47
-25
lines changed

3 files changed

+47
-25
lines changed

Multiset/Multiset.playground/Contents.swift

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

2525
set2.isSubSet(of: set) // true
2626
set.isSubSet(of: set2) // false
27+
28+
var cacti = Multiset<Character>("cacti")
29+
var tactical = Multiset<Character>("tactical")
30+
cacti.isSubSet(of: tactical) // true
31+
tactical.isSubSet(of: cacti) // false

Multiset/Multiset.playground/Sources/Multiset.swift

Lines changed: 15 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
}

Multiset/README.markdown

Lines changed: 27 additions & 16 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,11 +50,7 @@ Here's the essence of it:
3250

3351
``` swift
3452
public struct Multiset<Element: Hashable> {
35-
fileprivate var storage: [Element: UInt]
36-
37-
public init() {
38-
storage = [:]
39-
}
53+
private var storage: [Element: UInt] = [:]
4054
```
4155

4256
And here's how you'd use this class to create a multiset of strings:
@@ -49,11 +63,7 @@ Adding an element is a case of incrementing the counter for that element, or set
4963

5064
``` swift
5165
public mutating func add (_ elem: Element) {
52-
if let currentCount = storage[elem] {
53-
storage[elem] = currentCount + 1;
54-
} else {
55-
storage[elem] = 1
56-
}
66+
storage[elem, default: 0] += 1
5767
}
5868
```
5969

@@ -62,6 +72,7 @@ Here's how you'd use this method to add to the set we created earlier:
6272
```swift
6373
set.add("foo")
6474
set.add("foo")
75+
set.allItems // returns ["foo", "foo"]
6576
```
6677

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

0 commit comments

Comments
 (0)