Multiset

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.

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.

var set = Set<Int>()
set.add(1) // set is now [1]
set.add(2) // set is now [1, 2]
set.add(2) // set is still [1, 2]

By comparison, after adding the elements 1, 2, 2 to a multiset, it would contain three items.

var set = Multiset<Int>()
set.add(1) // set is now [1]
set.add(2) // set is now [1, 2]
set.add(2) // set is now [1, 2, 2]

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…

Typical operations on a multiset are:

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.)

var cacti = Multiset<Character>("cacti")
var tactical = Multiset<Character>("tactical")
cacti.isSubSet(of: tactical) // true!

Implementation

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.

Here's the essence of it:

public struct Multiset<Element: Hashable> {
  private var storage: [Element: UInt] = [:]
  
  public init() {}

And here's how you'd use this class to create a multiset of strings:

var set = Multiset<String>()

Adding an element is a case of incrementing the counter for that element, or setting it to 1 if it doesn't already exist:

public mutating func add (_ elem: Element) {
  storage[elem, default: 0] += 1
}

Here's how you'd use this method to add to the set we created earlier:

set.add("foo")
set.add("foo") 
set.allItems // returns ["foo", "foo"]

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

Removing an element works much the same way as adding; decrement the counter for the element, or remove it from the underlying dictionary if its value is 1 before removal.

public mutating func remove (_ elem: Element) {
  if let currentCount = storage[elem] {
    if currentCount > 1 {
      storage[elem] = currentCount - 1
    } else {
      storage.removeValue(forKey: elem)
    }
  }
}

Getting the count for an item is simple: we just return the value for the given item in the internal dictionary.

public func count(for key: Element) -> UInt {
  return storage[key] ?? 0
}

Written for the Swift Algorithm Club by Simon Whitaker