In Swift there are three primary collection types – arrays, dictionaries and sets. We’ve already covered arrays and dictionaries in previous posts and in this post we’re going to complete the set (no pun intended) by looking at sets.
Table of Contents
What is a Set?
In Swift, sets are unordered collections of unique values. This means that like a dictionary, the values held in a Swift set aren’t held in any particular order and like the keys in a dictionary, there can’t be any duplicate values.
Like the other collection types in Swift, sets in Swift are also strongly typed. This means that only values of one specified type can be held within a set and although this may seem restrictive, as we’ve seen in previous articles, this actually helps us to avoid bugs by ensuring that we don’t insert values of the incorrect type.
Hash Values for Set Types
On top of only being able to hold a single type, the Swift language, also imposes one more requirement on the types of values that can be held in a set – the types of values that can be held must conform to Swift’s hashable protocol.
This may sound all fancy but all this really means is that values of the specified type must have an Int
property called hashValue
that can be used as a representation of the value.
In addition, because the Hashable
protocol in Swift itself conforms to the Equatable
protocol, it means that the type must also supply an implementation of the equality operator (==
) as well. The Swift language then uses the hashValue
and the equality operator to compare different values within the set (via their hashValue
) to ensure that they are unique.
So you may be wondering which types of values conform to this Hashable
protocol. The good news here is that pretty much all of the basic types in Swift are hashable by default. This includes the String
, Int
, Double
and Bool
types as well as any enumeration types that don’t have associated values.
Ok, so that’s what a set is (a strongly typed, unordered collection of unique values) but what about actually creating one? Before we look at how to do this, let’s first look at how the mutability of sets is controlled.
Declaring and Initialising a Set
Mutable versus Immutable Sets
As with other collection types in Swift, the mutability of a set is governed by whether we declare that set using the var
or let
keywords.
As we’ve seen with other collections, if declared with the var
keyword, the contents of the set will be mutable – meaning that it can be mutated or changed by adding, removing or changing items within the set.
If the set is declared with the let
keyword however the set is said to be immutable or unchangeable. This means that both the sets size and its contents cannot be changed.
Creating an Empty Set
In Swift, there are a number of ways that you can create a set. The first of these is using initialiser syntax.
Initialisation syntax makes use of an initialisation function:
var firstSet = Set<String>()
Let’s walk through this slowly to start with.
Here I’ve started off our declaration as we would when defining any variable – using the var
keyword. As we’ve just seen, this means that the set I’m creating here is going to be mutable (i.e. we’re going to be able to modify it). We then have the name of the variable into which we’re going to store the set.
In this example I’m also taking advantage of Swift’s type inference mechanism as well. This allows me to infer the type of our firstSet
variable, without having to explicitly state it. We’ll come to exactly what type this set is in a moment.
After declaring our mutable set variable we then have the assignment operator and then the Set
type name. The Set
type (in similar fashion to the Array
type we saw in previous posts) is a generic type in Swift. This means that when we declare a set using initialiser syntax we not only supply the Set
type but also have to supply an additional type between angled brackets (< >
) to define the type of values that the set will hold. In this case I’ve declared the set to hold values of type String
so the full type for the set is Set<String>
and Swift infers it as such.
The final piece to the puzzle comes after the sets type. These are a set of parentheses that represent the call to the initialisation function which creates the set. The Set
type in Swift has a number of these initialisation functions, some with parameters and one without, and it is this form, the one without an argument, that creates an empty set of the specified type.
Creating an Empty Set with an Array Literal
So that’s how to create an empty set using initialisation syntax but we actually have a couple of other ways of creating empty sets in Swift as well, both of which use array literals.
As I mentioned, the Set
type in Swift has a number of initialisation functions and one of these functions takes an array literal as a parameter. This particular initialisation function then uses that array parameter to populate the contents of the set it creates.
Our first alternative for creating an empty set then, is to make use of this initialisation function and provide it with an empty array literal:
let secondSet = Set<Double>([])
The syntax is almost exactly the same as we’ve just seen and the result is essentially the same. This time I’ve declared secondSet
using the let
keyword resulting in an immutable set (one where we won’t be able to change the contents of the set) and have declared the set to hold values of type Double
instead of String
.
I’ve also supplied the empty array literal ([]
) as a parameter to the initialisation function. The effect is that it uses this empty array literal to create an empty set of type Set<Double>
.
Our final way of creating an an empty set in Swift is similar to this last one, but instead uses assignment.
In Swift, we can actually split the declaration of our set from it’s initialisation and as long as the sets type can be inferred from the context (which in the following example it can because of the explicit type declaration), we can assign an empty array literal to perform initialisation.
The result is exactly the same as our previous example:
let thirdSet : Set<Double>
thirdSet = []
Now, empty sets are all very well but they’re not that interesting. What about creating a set that actually contains something useful?
Creating Sets That Actually Contain Some Values
In Swift, there are a couple of ways to declare and initialise a set that contains some values and the first technique is a simple extension to what we’ve already seen.
Creating a Set with an Array Literal
We’ve already seen how an empty set can be created by supplying an empty array literal as an initialisation parameter, but (as you can probably guess) we can also create a set that contains an initial set of values by using an array literal that itself contains some values.
Both the initialisation syntax (calling an initialisation function on the Set
type) or the assignment approach we just looked at both work:
let fourthSet = Set<Int>([10, 30]) // Intialisation Syntax
let fifthSet : Set<Double>
fifthSet = [10.1, 37.2, 33.5] // Assignment
In this example, we create two new immutable Set
s. The first, (fourthSet
) contains values of type Int
and is inferred to be of type Set<Int>
. The second, (fifthSet
) is declared to store values of type Double
and is inferred to be of type Set<Double>
. In both cases, we also initialise the sets with values of the relevant types.
Now, you might think we’re done on the initialisation front, but there is one more, lesser known, initialisation option that I wanted to look at before we move on and that is initialising a set using a sequence.
Creating a Set with a Sequence
We’ve already touched on sequences in previous posts and took a cursory look at them when we talked about the open and closed range operators. The final initialisation function at our disposal makes use of sequences as it takes a sequence as a parameter and then uses the sequence to populate the elements within the set (one element in the set for each element in the sequence):
let sixthSet = Set<Int>(1...5)
The result of the example above is an immutable set that stores values of type Int
and contains the values 1
, 2
, 3
, 4
and 5
.
So we now know how to creating both an empty set and a set that actually contains some values. But what about working how many elements are already in a set? Let’s look at that next.
Checking if a Set is Empty
There are two ways in Swift that we can use to check how many elements are in set, both of which are accessible as properties of a given set.
This first of these properties is the isEmpty
property. As the name suggests, it can be used to test whether the set is empty and returns a Bool
value to indicate whether the set is empty or not:
firstSet.isEmpty
// true
Counting the Number of Elements in a Set
In addition to being able to test whether a set is empty or not, we can also determine the exact number of elements that are in a set.
Again, this is done via a property of the Set
type. This time the property is called count
and returns an Int
value representing the number of elements contained in the set:
firstSet.count
// 0
fifthSet.count
// 3
Adding an Element to a Set
Now that we know how to create a set that contains some values and count the number of elements in a set, the next thing to look at is how to add new elements to a set. For sets, this is done using the insert(_:)
function.
The insert(_:)
function accepts a single parameter of the same type as the elements stored within the set. (You’ll get an error if you try to supply a value of an incompatible type).
Let’s have look at an example.
Note: For this example and later ones I’ll use in this post I’m going to revisit an example I’ve used in a previous post, one using the characters from the latest Star Wars films.
Initially we’re going to create an empty forceAwakens
set to hold some of the characters from the movie The Force Awakens, and we’re initially going to add a single element to it:
var forceAwakens = Set<String>()
forceAwakens.count // 0
forceAwakens.isEmpty // true
forceAwakens.insert("Rey") // 'forceAwakens' now contains one item "Rey"
forceAwakens.count // 1
In this example, we declare a new set called forceAwakens
. We’ll use this to hold the names of the characters in the film.
Initially, the set is empty (and therefore has a count
of 0
) but we then use the insert(_:)
function to insert the first of our characters (Rey
). The result is that the set contains one element and the count
reflects this.
To add multiple elements to a set, all we do is use the insert(_:)
function multiple times within a loop:
for character in ["Finn", "BB-8", "Yoda", "C-3PO", "Han Solo", "Princes Leia"] {
forceAwakens.insert(character)
}
// {"Rey", "Han Solo", "Princess Leia", "BB-8", "Yoda", "C-3PO", "Chewbacca", "Finn"}
// Note: The elements in your set may be in a different order.
As we’ll see later, there is also another way to add elements to a set using set operations (the unionInPlace(_:)
function to be precise). Before we look at this though, we’ve got a couple of other things to look at. The first of these is how to access elements within a set.
Finding Elements in a Set
Let’s start this section off gently. We’ve now created our forceAwakens
set, and have learnt how to create it with some initial characters and also how to add additional characters to it, but what about checking on whether a value is already in the set?
Finding If an Element is in a Set
To find out whether an item is already in a set or not, we can use the contains(_:)
function.
The contains(_:)
function takes a single element of the same type as the elements held within the set and returns a boolean value to indicate whether the element is present or not:
forceAwakens.contains("Yoda")
// true
But we can go beyond simply checking whether an element is already in a set or not. We can also get the index of that element.
Finding the Index of an Element in a Set
As I mentioned at the start of this article, the elements in a set are not held in a defined order, but once inserted, elements in the set are located at particular indexes. As with other collections, the indexes of elements in a set start at 0
and then increment up to one less than the number of elements in the set.
For example, what if we wanted to get hold of the index of Yoda
within the set. We could write:
forceAwakens.indexOf("Yoda")
Note: If you run this yourself, you will likely get a different answer due to the unordered nature of the Set
type.
The indexOf(_:)
function returns a value of type SetIndex<T>
where T
is the type of value held in the set. We’ll see this again later in this article.
Iterating Over a Set
In addition to being able to identify the index of an element in a set we can also iterate over the sets elements.
We’ve seen this in previous articles with some of the other Swift collection types. The only thing to watch out for here is that the elements in the set are not held in any particular order so there is no guarantee about the order in which they will appear:
for character in forceAwakens {
print(character)
}
// Rey, Han Solo, Princess Leia, BB-8, Yoda, C-3PO, Chewbacca, Finn
Note: If you do want the elements in a set in a given order, you can also use the sort()
function to sort the values in the set before iterating over the array that results:
for character in forceAwakens.sort() {
print(character)
}
// BB-8, C-3PO, Chewbacca, Finn, Han Solo, Princess Leia, Rey, Yoda
Now that we know how to access elements in a set, there’s one final mechanism that I wanted to look at. I’ve left this one until last because not only does it access the element in the set, but it also modifies the set itself. This is the popFirst()
function.
Removing Elements from a Set
Removing the First Element in a Set
If you’ve done any sort of programming before, you might be used to the idea of pushing things onto a stack and popping them off. I always think of it as a pile of plates. You push a plate onto the top of the stack and pop it back off.
The popFirst()
function embodies this idea and is used to pop, the (notionally) first element of the set, returning the element and removing it from the set in the process:
// forceAwakens contains: {"Rey", "Han Solo", "Princess Leia", "BB-8", "Yoda", "C-3PO", "Chewbacca", "Finn"}
let firstVal = forceAwakens.popFirst()
print(firstVal)
// Rey
// forceAwakens now contains: {"Han Solo", "Princess Leia", "BB-8", "Yoda", "C-3PO", "Chewbacca", "Finn"}
The popFirst()
function actually returns an optional type. This accounts for the fact that in a situation where the set is empty, there is no value to return. In this situation, the popFirst()
returns nil
.
Now, we’re not done with removing the first element from a set. In addition to the popFirst()
function, the Set
type also has a removeFirst()
function.
The removeFirst()
function, has exactly the same behaviour as the the popFirst()
function – it returns the (notionally) first element in the set and in doing so, removes that element from the set itself. However, unlike the popFirst()
function, the removeFirst()
function can only be used with a set that contains at least one element. If the set is empty and the removeFirst()
function is called, an error will be thrown. It’s therefore prudent to first check that the set has at least one element before calling the removeFirst()
function:
// forceAwakens contains: {"Han Solo", "Princess Leia", "BB-8", "Yoda", "C-3PO", "Chewbacca", "Finn"}
if forceAwakens.count > 0 {
let nextVal = forceAwakens.removeFirst()
print(nextVal)
}
// Han Solo
// forceAwakens now contains: {"Princess Leia", "BB-8", "Yoda", "C-3PO", "Chewbacca", "Finn"}
So between the popFirst()
and removeFirst()
functions, we now know how to remove the first element from a set. But what if the element we wanted to remove wasn’t the first element? This is where the remove(_:)
and removeAtIndex(_:)
functions come in. Let’s look at the remove(_:)
function first.
Removing an Element from a Set
The remove(_:)
function allows us to remove a single element from the set on which the function is called. The function takes a single parameter of the same type as the elements stored within the set which is used to indicate the element you wish to removed. For example, say we wanted to remove Yoda
from our forceAwakens
set we could write:
// forceAwakens contains: {"Princess Leia", "BB-8", "Yoda", "C-3PO", "Chewbacca", "Finn"}
let item = forceAwakens.remove("Yoda")
print(item)
// Yoda
// forceAwakens now contains: {"Princess Leia", "BB-8", "C-3PO", "Chewbacca", "Finn"}
As with the popFirst()
and removeFirst()
functions, the remove(_:)
function returns the element that was removed from the set or nil
if the element was not present (the return type of the remove(_:)
function is an optional to account for this).
Removing An Element from a Set By Index
In addition to being able to remove an element directly, we can also remove an element from a set using the index of that element.
We saw how to get the index of an element earlier using the indexOf(_:)
function. That returns an optional value representing the index of the specified element. Once we have the index, we can then supply that index to the removeAtIndex(_:)
method which will both return the element at the given index and remove it from the set.
The thing to remember with the removeAtIndex(_:)
function is that because the element must already be in the set (in order to have a valid index), the function returns a non-optional (rather than optional) value of the same type as those stored within the set:
// forceAwakens contains: {"Princess Leia", "BB-8", "C-3PO", "Chewbacca", "Finn"}
if let idx = forceAwakens.indexOf("BB-8") {
var otherItem = forceAwakens.removeAtIndex(idx)
print(otherItem)
}
// BB-8
// forceAwakens now contains: {"Princess Leia", "C-3PO", "Chewbacca", "Finn"}
Removing Multiple Items from a Set
By now, we know how to retrieve elements from a set, and also how to remove individual elements (whether they be the nominally first element of the set or elsewhere). But what if we wanted to remove multiple items from a set?
There are a number of ways to approach this, two of which we’ll touch on now, and a couple that we’ll look at later in the article when we look at set operations. For now, let’s start with the simplistic approaches.
Given what we’ve learnt just now, the easiest way to remove multiple items from a set is to call one of the functions we just looked at multiple times. For example, we could call the remove(_:)
function multiple times to remove a number of elements form the set:
// forceAwakens contains: {"Princess Leia", "C-3PO", "Chewbacca", "Finn"}
for character in ["Finn", "C-3PO"] {
let character = forceAwakens.remove(character)
print(character)
}
// Finn
// C-3PO
// forceAwakens now contains: {"Princess Leia", "Chewbacca"}
There is however an alternative if we want to remove all elements from a set. Built into the Set
type, is the removeAll(keepCapacity:)
function which as you might expect, removes all the elements from a set. Note: The function has a default parameter for the keepCapacity
parameter which most of the time you can ignore unless you explicitly want to keep the capacity of the set for performance reasons:
forceAwakens.removeAll()
// forceAwakens now contains: []
Ok, so far we’ve covered the basics of Swift sets and if you stop reading here, you’d be wondering why you would want to use them. After all, they sound pretty similar to arrays (except for the non-ordered bit) and they don’t seem to be giving you much more benefit – so why us them?
The reality is that sets in Swift are actually extremely powerful but their real power comes from their ability to perform set operations and check for set membership and set equality. It’s these aspect that I want to touch on next. Let’s start by looking at the concepts of set membership and set equality.
Set Membership and Equality
You might remember some set theory from school but before we dive in, let’s have a bit of a refresher. In the diagram below, we have three sets – A, B and C. These are going to represent three sets of Star Wars characters:
The first set, set A, is going to be our set of characters from the film The Return of the Jedi. This set is declared to contain the following characters:
var returnOfTheJedi = Set<String>(["Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"])
The next set, set B, is our set of characters from the newly released movie The Force Awakens. This set is declared as follows:
var forceAwakens2 = Set<String>(["Finn", "BB-8", "Yoda", "Chewbacca", "C-3PO", "R2-D2"])
The characters in this set have some overlap with the characters in The Return of the Jedi, namely characters like Yoda
, Chewbacca
, C-3PO
and R2-D2
.
The overlap of sets A and B represent the characters that are in both set A (The Return of the Jedi) and set B (The Force Awakens). These include Yoda
, Chewbacca
, C-3PO
and R2-D2
.
Our final set is set C. Set C contains a single element and represents the robots from the film The Force Awakens (BB-8
). Set C is declared as follows:
var robots = Set<String>(["BB-8"])
Set C is said to be a subset of set B because all of the elements in Set B are are contained within Set C.
By inference, set B is also said to be a superset of set C because it contains all the elements of set C.
Ok, that lays out the various sets we’re going to use in the following examples. Let’s start off easy by examining the set statements I made above, but this time using Swift code.
Let’s start off by looking at how to determine whether the elements in two sets are equal.
Set Equality
Testing whether two sets are equal is actually really easy in Swift. All we have to do is to use the ‘is equal’ operator (==
) just as you would with any boolean comparison.
For example, let’s first declare another set that contains identical elements to the robots
set we saw just now:
var robots2 = Set<String>(["BB-8"])
In the case of sets, the ‘is equal’ operator will only return true
if the two sets contain exactly the same elements. If either set contains additional elements, the ‘is equal’ operator will return false:
robots2 == robots
// true
robots == forceAwakens2
// false
Note that in this second case, despite the fact that all of the elements in the robots
set are contained within the forceAwakens2
set, the comparison returns false
because the sets do not contain exactly the same elements.
Subset
The next thing we may want to test for is whether one set is a subset of another set. As we saw from our diagram, the robots from the Force Awakens (set C) is a subset of the characters in the film the Force Awakens (set B).
We can actually test for this in Swift using the isSubsetOf(_:)
function. The function takes another set (or sequence) as it’s single parameter and returns a boolean value indicating whether all the elements of the first set are contained within the set supplied as a parameter:
robots.isSubsetOf(forceAwakens2)
// true
robots2.isSubsetOf(robots)
// true
Notice that in the case of the second set, the function also returns a value of true
. This is due to the fact that these sets contain identical elements and the test (whether all elements of the first set are contained within the second set) actually returns true
.
Strict Subset
Now, in addition to the isSubsetOf(_:)
function, sets in Swift also have an additional function, the isStrictSubsetOf(_:)
function. This function behaves in exactly the same way as the isSubsetOf(_:)
function but returns false
if the two sets are equal. For example:
robots.isStrictSubsetOf(forceAwakens2)
// true
robots2.isStrictSubsetOf(robots)
// false
Notice this time that the comparison of the robots2
set to the robots
set, returns false
. This is because the sets are equal actually equal.
Ok, that’s subsets, but let’s flip this around and look at it from the other perspective, whether one set is a superset of another or not.
Superset
As you might have guessed, sets in Swift also have a function called isSupersetOf(_:)
. This function allows us to test whether one is a superset of another. Again this function returns a boolean value:
forceAwakens2.isSupersetOf(robots)
// true
And in similar fashion to the isSubset(_:)
function we just looked at, the isSupersetOf(_:)
function also returns true
for two sets if they have equal elements (as all the elements of the second set are contained within the first):
robots2.isSupersetOf(robots)
// true
Strict Superset
Finally, we also have the isStrictSupersetOf(_:)
function. As you might expect, this behaves in the same manner as the isSupersetOf(_:)
function but returns false
if the two sets are identical:
forceAwakens2.isStrictSupersetOf(robots)
// true
robots2.isStrictSupersetOf(robots)
// false
That about wraps up the set membership functions for Swift sets, but there is one final function that we need to look at before we move on. That is the isDisjointWith(_:)
function.
Disjoint
So far we’ve looked at whether one set is a superset or subset of another set, but the isDisjointWith(_:)
function allows us to test whether there is no overlap between two sets (or in fact, no overlap between a given set and any other value that compiles with Swift’s sequenceType
protocol such as another set or an array).
For example, in our three Star Wars sets, there is no overlap between the characters in the Return of the Jedi movie (set A) and the robots in the movie the Force Awakens (set C). We can test for this as follows:
returnOfTheJedi.isDisjointWith(robots)
// true
But there is an overlap between the forceAwakens2
set and the robots
set and the isDisjoint(_:)
function therefore returns false
as the two sets overlap:
forceAwakens2.isDisjointWith(robots)
// false
Set Functions
Ok, so we’ve covered the basics of sets and can now test whether sets are either supersets or subsets of each other and whether there is no overlap between the two sets. If we follow this course of thinking, the next logical thing to look at is how to test whether there is a partial overlap between two sets. Let’s do that next.
Intersect / Intersect In Place
If we go back to the diagram of our three Star Wars sets, we can see that there is a partial overlap between the returnOfTheJedi
set and the forceAwakens2
set:
In isolation, the set can be visualised as follows:
The Set
type in Swift, has two functions that allow us to test for this overlap. The first is the intersect(_:)
function.
The intersect(_:)
function takes a single parameter. The parameter is anything that compiles with Swift’s SequenceType
protocol and generates elements of the same type as those stored within the Set
. In reality this usually means another Set
or Array
but it’s obviously not restricted to these types.
When called, the intersect(_:)
function returns a Set
containing those elements that are members of both the set on which the function is called and the set that is supplied as a parameter. For example:
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
// forceAwakens2 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
let commonCharacters = returnOfTheJedi.intersect(forceAwakens2)
// commonCharacters contains {"Yoda", "Chewbacca", "C-3PO", "R2-D2"}
But as I mentioned, we don’t have to supply another set as a parameter, we could supply an array:
// forceAwakens2 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
let intersetSet = forceAwakens2.interset(["Yoda", "C-3PO", "Han Solo"])
// intersectSet contains {"R2-D2", "Yoda"}
You get the idea.
Now, although the interset(_:)
function returns a new set as a result we can also modify the original set, by using the intersectInPlace(_:)
function.
Where the interset(_:)
function returns a new set, the intersectInPlace(_:)
function modifies the set upon which the intersect(_:)
function is called. The result is that we can only call this function on mutable sets (i.e. those declared with the var
keyword).
Note: So that we don’t change our original set, I’ll make a copy of the forceAwakens2
set for use in this example:
var forceAwakens3 = forceAwakens2
// forceAwakens3 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
forceAwakens3.intersectInPlace(returnOfTheJedi)
// forceAwakens3 now contains {"Yoda", "Chewbacca", "C-3PO", "R2-D2"}
As you can see, the contents of the forceAwakens3
set is modified to only contain the values that are common between the two sets.
Union / Union in Place
The next pair of functions available on the Set
type in Swift are the union(_:)
and unionInPlace(_:)
functions. Diagrammatically, they can be represented as follows:
The union(_:)
function, returns a new function containing the elements from both the set on which the function is called and the functions parameter (again, any type that compiles with Swift’s sequenceType
protocol):
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
// forceAwakens2 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
let combinedSet = forceAwakens2.union(returnOfTheJedi)
// combinedSet contains {"R2-D2", "BB-8", "Yoda", "Chewbacca", "C-3PO", "Darth Vader", "Finn"}
In similar fashion to the interset(_:)
function, the union(_:)
also has a counterpart, the unionInPlace(_:)
function, which does the same thing, but instead of returning a new set, modifies the set on which the function is called:
var forceAwakens4 = forceAwakens2
// forceAwakens4 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
forceAwakens4.unionInPlace(returnOfTheJedi)
// forceAwakens4 contains {"R2-D2", "BB-8", "Yoda", "Chewbacca", "C-3PO", "Darth Vader", "Finn"}
Now, remember way back at the start of this article when we were looking at how to add elements to an existing set? You might have guessed that We can use the unionInPlace(_:)
function to add multiple new elements to a set without having to all the insert(_:)
function multiple times:
forceAwakens4.unionInPlace(["Kylo Ren", "Poe Dameron"])
// forceAwakens4 contains {"R2-D2", "Poe Dameron", "Kylo Ren", "BB-8", "Yoda", "Chewbacca", "C-3PO", "Darth Vader", "Finn"}
Handy!
Exclusive OR / Exclusive OR in Place
Next up are the exclusiveOr(_:)
and exclusiveOrInPlace(_:)
functions. These functions identify those elements that are in either the set upon which the function is called, or the set / sequence supplied as a parameter but are not present in both. This can be visualised as follows:
In terms of our Star Wars sets, this essentially means identifying those characters that are either in the forceAwakens2
set or the returnOfTheJedi
set but do not appear in both films:
// forceAwakens2 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
let uniqueCharacters = forceAwakens2.exclusiveOr(returnOfTheJedi)
// uniqueCharacters = {"Finn", "Darth Vader", "BB-8"}
In similar fashion, we also have the exclusiveOrInPlace(_:)
function that does the same thing but modifies the original set:
var forceAwakens5 = forceAwakens2
// forceAwakens5 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
forceAwakens5.exclusiveOrInPlace(returnOfTheJedi)
// forceAwakens5 contains {"Finn", "Darth Vader", "BB-8"}
Subtract / Subtract In Place
Ok, we’re nearly there. Time for our last pair of functions, these are the subtract(_:)
and subtractInPlace(_:)
functions. These functions allow us to subtract the elements of one set or sequence from the elements of another. This can be illustrated as follows:
First, we have the subtract(_:)
function. As with all the other set functions we’ve looked at, it takes a single parameter of any type that complies with Swift’s sequenceType
protocol and removes the elements of that sequence from the set upon which the function is called:
// forceAwakens2 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
let uniqueToForceAwakens = forceAwakens2.subtract(returnOfTheJedi)
// uniqueToForceAwakens contains {"Finn", "BB-8"}
Finally, we have the subtractInPlace(_:)
function. You can probably guess what this does by now:
var forceAwakens6 = forceAwakens2
// forceAwakens6 contains {"Finn", "R2-D2", "Yoda", "Chewbacca", "C-3PO", "BB-8"}
// returnOfTheJedi contains {"Chewbacca", "C-3PO", "R2-D2", "Yoda", "Darth Vader"}
forceAwakens6.subtractInPlace(returnOfTheJedi)
// forceAwakens6 contains {"Finn", "BB-8"}
Ok, I think we’ve done Swift’s Set
type to death by now so we’ll wrap thing up there.
As ever, if you do have any questions (or if I’ve got anything wrong) please get in touch and if you do find this useful, I’d really appreciate it if you share it with your friends.