Removing a value
s.remove(x) takes x out of the set s. If x was there, remove returns it. If it wasn't, nothing happens, there is no error, and remove returns nil. The return type is an optional for that reason.
var colors = Set(["red", "blue", "green"])
print(colors.remove("blue") as Any)
print(colors.remove("pink") as Any)
print(colors.sorted())
This prints:
Optional("blue")
nil
["green", "red"]
You can ignore the returned value, and the compiler doesn't warn about it. The set must be declared with var, since removing changes it. The sorted() call in the last line is there only to print in a stable order.
Looping over a set
for x in s visits each value of the set once. The order isn't fixed, and Swift may even change it from one run to the next. So loop over a set when the order doesn't matter: a total, a count or a yes-or-no check all give the same answer in any order.
for color in colors {
print(color)
}
That prints red and green, one per line, in either order.
Taking out several values at once
When the values to remove follow a rule, filter is another way to get there. On a set it returns a new set holding only the values that pass the test. For example, keep.filter { $0 == "b" } on a set of "a", "b", "c" and "d" gives a set with just "b". The original set is not changed.
You can also call remove inside a loop over the same set. The loop keeps walking through the values the set held when it began, and the removals change the set itself. For a set of "a", "b", "c" and "d", removing "a" and "c" inside the loop leaves ["b", "d"]. Still, a loop that changes what it loops over is easy to misread, so removing by name, as in the exercise, is usually clearer.
An empty set
Removing the last value leaves an empty set, not an error. Its isEmpty is true and its count is 0, and a loop over it simply runs zero times. That makes a total over a set safe when nothing is left: the score stays at its starting value of 0.
The lesson's exercise
A word game keeps the words a player found in a set:
let words = ["cat", "tree", "sun", "qqq", "moon"]
var found = Set(words)
The words "qqq" and "zzz" aren't allowed, so remove both. The player never found "zzz", and removing it simply does nothing. After the two removals, the set holds 4 words:
found.remove("qqq")
found.remove("zzz")
print("\(found.count) words")
A word scores one point per letter, so the second part adds up the lengths. Start score at 0, loop over found with the loop variable word, and add word.count each time:
var score = 0
for word in found {
score += word.count
}
print("\(score) points")
The words left are cat, tree, sun and moon, so the score is 3 + 4 + 3 + 4 = 14. The program prints:
4 words
14 points
The total doesn't depend on the order the loop visits the words, which is what makes a set loop safe here.
Common mistakes
- Expecting an error for a missing value. Removing something that isn't there is allowed. The optional result is
nil. - Removing from a
letset. This is a compile error. Usevar. - Using the order. A loop that keeps "the first word" or prints the words and expects a fixed sequence will behave differently between runs.
- Using
word.length. A Swift string hascount, which counts characters.
Key takeaways
s.remove(x)removesxand returns it, or returnsnilif it wasn't there.for x in svisits every value once, in no fixed order.- Totals, counts and checks are safe answers from a set loop.
