What a hash set is
A hash set stores unique values. Adding a value that is already there changes nothing, and asking whether a value is present is fast: constant time on average, no matter how many values the set holds. That makes a set the right tool when you need to remove duplicates or answer "have I seen this before?" many times.
In Swift the hash set is the Set type. Its elements must be Hashable, which covers Int, String, Bool, Double and your own types that adopt the protocol. Arrays of hashable elements are hashable too.
Creating a set
An empty set needs its element type spelled out, because Swift can't guess it:
var mySet = Set<String>()
You can also build a set from any sequence, and the duplicates are dropped:
let elements = ["b", "c", "c", "d"]
var mySet = Set(elements)
print(mySet.count)
3
Creating an empty set is O(1). Building one from a sequence of n elements is O(n). A set literal needs a type annotation, because a bare literal in square brackets is an array: let a: Set = [1, 2, 3].
Adding, checking and removing
insert adds a value, contains checks for one, and remove deletes one. All three take O(1) time on average.
var fruits = Set(["apples", "bananas"])
let first = fruits.insert("oranges")
print(first.inserted)
let again = fruits.insert("apples")
print(again.inserted)
print(fruits.contains("apples"))
print(fruits.contains("pears"))
print(fruits.remove("bananas") as Any)
print(fruits.remove("pears") as Any)
true
false
true
false
Optional("bananas")
nil
Both insert and remove return a result, and Swift lets you ignore it. insert returns a tuple whose inserted field says whether the value was new. remove returns the removed element as an optional, or nil when the value wasn't there. Neither raises an error for a duplicate or a missing value, so removing something that isn't in the set is safe.
Iterating, and the order problem
A for-in loop visits every element in O(n) time:
for fruit in fruits {
print(fruit)
}
A set has no meaningful order, and Swift deliberately varies it: the same program can visit the elements in a different order each time it runs. Never rely on the order of iteration. When you need a predictable order, for printing or comparing, call sorted() first, which returns an array:
let ids = Set([3, 1, 2])
print(ids.sorted())
[1, 2, 3]
A set uses O(n) space for n elements. Sets also support the usual set algebra: with a = [1, 2, 3] and b = [3, 4], a.union(b) holds 1, 2, 3 and 4, a.intersection(b) holds only 3, and a.subtracting(b) holds 1 and 2.
The exercise, step by step
- Start with data that has a duplicate.
fruitsArrayis aletarray holding"apples","bananas"and"apples". Swift infers[String]. - Build the set.
var fruits = Set<String>(fruitsArray)keeps one"apples". It is avarbecause the next steps change it. - Insert a new fruit.
fruits.insert("oranges")grows the set to three elements. - Check membership with
if fruits.contains("apples")and printWe have apples. - Remove one with
fruits.remove("bananas"). The returned optional is ignored. - Loop over the set with
for fruit in fruitsand print each one. It shows only apples and oranges: the duplicate collapsed and the banana is gone, in whichever order Swift visits them.
The full program
let fruitsArray = ["apples", "bananas", "apples"]
var fruits = Set<String>(fruitsArray)
fruits.insert("oranges")
if fruits.contains("apples") {
print("We have apples")
}
fruits.remove("bananas")
for fruit in fruits {
print(fruit)
}
Gotchas
- Sets are value types. Assigning a set to another variable copies it, and
insertandremovechange the set, so the variable must bevar. Callinginserton aletset is a compile error. - No indexing. You can't write
fruits[0]. A set has no positions; usecontainsto ask about a value. - Array lookup is slower.
array.contains(x)scans the array inO(n), whileset.contains(x)isO(1)on average.
Key takeaways
Setholds unique, hashable values with no defined order.insert,containsandremoverun in constant time on average.- Building a set from an array removes its duplicates in
O(n). - Use
sorted()whenever the order of the output matters.
