In Objective-C there were three basic types of Data Structures, NSArray, NSDictionary, and NSSet. In Objective-C, the immutable and mutable forms were separate, so you also had NSMutableArray, NSMutableDictionary, and NSMutableSet. Especially since it is often an interview question, I should point out that a Data Structure is simply “a particular way of organizing data in a computer so that it can be used efficiently.” You can read that sentence and more at the Wikipedia article about Data Structures.
In Swift we were greeted with Arrays and Dictionaries, but there was no Set to be found! You had to go back to Objective-C’s NSSet to work with them. You can read about the other two Data Structures back in the previous posts Arrays and their Methods in Swift and Swift Dictionary Quick Reference. Well, thanks to Swift 1.2, Set has come to the land of Swift.
What is a Set?
What is a Set you might ask? A set is an unordered collection of distinct objects.
How is that different from an array? Well, an array is a ordered collection, firstly. Each member of an array is at a specific index that you access through it’s subscript (the [#] next to the array, where you replace the # sign with the index you’re looking for). Secondly, the objects at each index need not be distinct. You can have the same object in at all of hte indexes if you really want to for some reason.
How is it different from a dictionary? A dictionary is an unordered collection of values referenced by specific keys. If you have a dictionary of people that are keyed by a String, you would call one with the subscript syntax as well, like ‘ Person[“Johnny”] ‘. So while it is unordered, like the Set is, each value in it is addressed by a specific key.
So if it isn’t indexed, or even addressable by a key, what would you possibly use a set for?
Does order matter with every collection of Objects? Let’s say you were having a potluck dinner, and each person told the host/hostess what they were bringing. You want people to see what foods are available, and the quantity doesn’t matter.
Some people have responded that they’ll bring “Salad”, “Chips”, and “Sandwiches”. Somebody else comes along and says they also want to bring a “Salad”. Salad’s good for you right? Everybody should eat Salad more often!
So now, you have had people respond that there will be “Salad”, “Chips”, “Sandwiches”, and “Salad”. If somebody is asking what will be available to eat at this dinner, do you really need to say “Salad” twice? And does order matter at all? Not really. So a Set of what is available at this dinner is “Salad”, “Chips”, and “Sandwiches” still, even if there is more salad.
But an array is easy, and to make sure we don’t put in the same thing more than once we could just iterate through the array, see if that Object is there, and just not enter it right? Well, checking for if that object is there usually means iterating over the rest of the array before finding the index that object is in, if it is in there at all. That is part of where the Set shines. The Set is a lot faster than an array. The Set is most similar to the keys of a Dictionary type. The keys of a dictionary must be unique, and you find that key-value pair (under the hood) by generating a hash of the key, and check for something at that hash. That is effectively what the Set type does.
If you try to insert something that is already in the Set, it should just accept it, but not actually do anything. So if we added “Salad” to our food set after the first three were added, we would still result in a set of 3 objects. So you don’t need to check if the object is there (yourself) before inserting it. You can just try anyway.
However, if you DID want to check if something is in there, say to limit people from bringing the same thing to this dinner, Set is particularly fast at testing whether an object is in the Set itself. Since it doesn’t have to iterate over itself like the Array, it just checks whether there is something at that value’s hash. If there is, it exists, if there is not, then it doesn’t. This is similar to a Dictionary, where it will return nil if you try to get something from the Dictionary with a key that is not present in that Dictionary.
Anyway, enough chit chat, how do you use Sets in Swift?
Swift Sets and their Methods
These are most of the methods and properties available for the Set type:
Title | Example | Description |
Initialize Set | Set<String>() | This initializes a completely empty Set. |
Initialize Set from Array Literal | let abcSet = Set([“A”, “B”, “C”, “D”]) | This creates a Set that contains the Strings of the first 4 letters of the alphabet. |
Check Set Membership | foodSet.contains(“Sandwiches”) | This returns a Bool of whether foodSet already contains the Swift String “Sandwiches”. |
Insert Value | foodSet.insert(“Salad”) | This will insert the Swift String “Salad” into the Set. |
Remove Value | foodSet.remove(“Chips”) | This will remove a value from the array. This will return an Optional of the Set’s type, with the value if it was there, or nil if it wasn’t. |
Remove All Values | foodSet.removeAll() | This will completely empty the Set of all members. |
Number of Elements | foodSet.count | Returns an Int of how many elements are in the Set. |
Check if Empty | foodSet.isEmpty | Returns a Bool of true if the foodSet has no members. It returns false if there are any members in the Set. |
Check if Subset | entreeSet.isSubset(of: foodSet) | Returns true if entree set is a subset of foodSet. That is, if all of entreeSet’s members are in foodSet. |
Check if Strict Subset | entreeSet.isStrictSubset(of: foodSet) | Returns true if entree set is a subset of foodSet, but not an exact copy. |
Check if Superset | foodSet.isSuperset(of: entreeSet) | Returns true if foodSet is a superset of entreeSet. That is, if foodSet contains all members of entreeSet. |
Check if Strict Superset | sameFoodSet.isStrictSuperset(of: foodSet) | Returns true only if sameFoodSet is a superset of foodSet, but not an exact copy. |
Check if no members same | foodSet.isDisjoint(with otherFoods) | Returns true if the two Sequences have no members in common. |
Combine Sets | foodSet.union(otherFoods) | Returns a new Set containing the members of both foodSet and otherFoods. |
Combine Sets In Place | otherFoods.formUnion(foodSet) | Mutates the otherFoods Set to add the members of foodSet to it. |
Subtract One Set From Another | otherFoods.subtracting(entreeSet) | Returns a new Set with the values of entreeSet removed from otherFoods, if they were present. |
Subtract One Set From Another In Place | otherFoods.subtract(entreeSet) | Mutates the otherFoods Set to subtract an values that were specified in the entreeSet (like above). |
Create Set of Common Members | moreFoods.intersection(entreeSet) | Returns a new Set with the values that were in common between moreFoods and entreeSet. |
Create Set of Common Members In Place | moreFoods.formIntersection(entreeSet) | Mutates the moreFoods Set to perform the intersect method above. |
Create Set of Uncommon Members | moreFoods.symmetricDifference(dessertsSet) | Returns a new Set containing the values that were in either moreFoods or dessertsSet, but NOT both. |
Create Set of Uncommon Members In Place | moreFoods.formSymmetricDifference(dessertsSet) | Mutates the moreFoods Set with the result of the exclusiveOr method above. |
If you look inside the Swift Set type in Swift 1.2 (and Swift 2.1 now), you will see that it actually does refer to Indexes in there. It has to be addressable by the system somehow, but you probably should not mess with them. I mean you technically can, but since the thing is unordered, the actual order they appear in will be… unpredictable is probably the wrong word, but not something I would rely on.
One thing to note, all of the methods from isSubsetOf down actually take a SequenceType value. That is, a type that adopts the SequenceType protocol. So actually, these should take even an Array, since Arrays m adopt the SequenceType protocol. When making a union with an array, it adds the values stored at each index to the Swift Set. A Dictionary technically is a SequenceType as well, but I think the type fails the Generic’s where clause, since it is composed of the key and value types. However, if you can create a union between a set and either the keys or values of the array using the appropriate property of that dictionary for either the keys or values.
Initializing a Swift Set
As shown above, there are a few ways to initialize a Swift Set. There isn’t technically a “Set Literal”, at least not that I’ve seen yet, but you can create a Swift Set with an Array Literal. Each of these ways to create a Swift Set are valid:
var someSet = Set<String>() let abcSet: Set = ["A", "B", "C", "D"] var foodSet = Set(["Salad", "Chips", "Sandwiches"])
The first one makes a completely empty mutable Swift Set. The next makes a constant Set by explicitly typing that it will be a Set and then setting it with an array literal. The final one makes a mutable Set using an initializer that takes a Swift Array Literal. Notice how the last two don’t necessarily need the type of the elements of the Set stated explicitly. You can if you wish, but doing this worked correctly. I don’t know which way is best practice yet, but these are the ones currently available. Maybe we’ll get a true Set literal in a future beta, but for now we can do it well enough with arrays.
Adding and Removing Elements from a Swift Set
The simplest way to add or remove items from a Set is with the insert or remove commands:
foodSet.remove("Chips") //Now contains: {"Salad", "Sandwiches"} foodSet.insert("Soup") //Now contains: {"Salad", "Sandwiches", "Soup"} foodSet.removeAll() //Now Contains 0 members
As mentioned in the table, the remove method actually returns an Optional of the type stored in the Set. It will return nil if the value you want to remove isn’t there in the first place, or it will return the value itself. If you wanted to just remove everything of course, you can just run the method removeAll().
Checking for Element Membership in Sets
The count and isEmpty properties are pretty self explanatory, so we won’t really go over them in any more depth. The rest of the methods however, are rather interesting. The first inquires if a specific element is in the Swift Set, with a Bool return value. The rest in this batch return Bools depending on what the relationship is between the Swift Set and another Sequence type (which is usually a Set, but as mentioned earlier, they could work with Arrays).
//foodSet is {"Salad", "Chips", "Sandwiches"} //entreeSet is {"Salad", "Sandwiches"} //sameFoodSet is {"Salad", "Chips", "Sandwiches"} //otherFoods is {"Quiche", "Donuts"} foodSet.contains("Chips") //returns true entreeSet.isSubset(of: foodSet) //returns true sameFoodSet.isStrictSubset(of: foodSet) //returns false foodSet.isSuperset(of: entreeSet) //returns true foodSet.isStrictSuperset(of: sameFoodSet) //returns false foodSet.isStrictSuperset(of: entreeSet) //returns true foodSet.isDisjoint(with: entreeSet) //returns false foodSet.isDisjoint(with: otherFoods) //returns true
The contains method returns true, since “Chips” is indeed a member of the foodSet.
Since entreeSet contains “Salad” and “Sandwiches”, it is a subset of the entire foodSet, so that returns true. However, while sameFoodSet is a subset of foodSet by containing all of the same items, it is not a Strict subset, which basically means it must be PART of the set, not the whole thing, so it returns false.
The foodSet is a superset of the of the entreeSet, by containing at least everything in the entreeSet. Like before, foodSet is not a Strict superset of sameFoodSet, since they are entirely the same. However, foodSet does contain everything in entreeSet, and something that entree set does not (the “Chips), so the isStrictSuperSetOf returns true for that inquiry.
Finally, we can check if they are disjoint, basically saying if they have nothing in common. The foodSet and entreeSet contain a few members in common, so isDisjointWith evaluates to false. However, there is nothing in common between foodSet and otherFoods, so that one evaluates to true.
Compare Sets
The final batch create new Sets based on how the Swift Set and SequenceTypes being compared actually compare.
//foodSet is {"Salad", "Chips", "Sandwiches"} //otherFoods is {"Quiche", "Donuts"} //entreeSet is {"Salad", "Sandwiches"} //dessertsSet is {"Chips", "Ice Cream", "Donuts"} let moreFoods = foodSet.union(otherFoods) //moreFoods now contains {"Sandwiches", "Salad", "Chips", "Quiche", "Donuts"} otherFoods.formUnion(foodSet) //otherFoods now contains {"Sandwiches", "Quiche", "Salad", "Chips", "Donuts"} otherFoods.subtract(entreeSet) //otherFoods Set containing {"Quiche", "Chips", "Donuts"} otherFoods.intersection(dessertsSet) //Returns Set containing {"Chips", "Donuts"} foodSet.symmetricDifference(dessertsSet) //Returns Set containing {"Sandwiches", "Ice Cream", "Salad", "Donuts"}
Each of them comes with a form<noun> style, which replaces the Swift Set it is called from with the result of whichever <noun> method is called. Of course the Set this is called from must be a variable to use those versions. We didn’t go over each form<noun> style of method, but they all work the same way. You call them like shown above with formUnion.
Union does what it sounds like. It combines the two sets. If there were any in common, they would only be shown once in the resulting Swift Set. So basically, we had “Salad”, “Chips”, “Sandwiches” from foodSet, and combined those with “Quiche” and “Donuts” from otherFoods, to get back a new set containing all of them: {“Donuts”, “Sandwiches”, “Salad”, “Quiche”, “Chips”}.
Next, we outright replaced otherFoods with the union of itself with foodSet. After that, we requested a new Set with the result of subtracting the members of entreeSet (“Salad”, “Sandwiches”) from the newly modified otherFoods (“Chips”, “Sandwiches”, “Quiche”, “Salad”, “Donuts”) to result in a returned Set of (“Soup”, “Quiche”, “Donuts”).
Then we requested a set that was the intersection of otherFoods (“Chips”, “Sandwiches”, “Quiche”, “Salad”, “Donuts”) and dessertsSet (“Chips”, “Ice Cream”, “Donuts”). The only things in common between those two were “Chips” and “Donuts”, so those were the members of the returned Set.
SymmetricDifference then, creates a new Swift Set containing the members that are in either Set, but NOT both. So when taking the symmetricDifference of foodSet (“Salad”, “Chips”, “Sandwiches”) and dessertsSet (“Chips”, “Ice Cream”, “Donuts”), they have “Chips” in common, so that element is left out. This then results in a Set that is the combination of the remaining members (“Sandwiches”, “Ice Cream”, “Salad”, “Donuts”). This is basically an Exclusive Or operation.
Conclusion
A Swift Set provides fast lookup for whether a value is contained therein, especially compared with an Array. When you need to know that an object is in a collection, need it to be unique, but don’t care about the order, the Swift Set may be just the Data Structure for you. I have not used their Objective-C equivalent that much yet, but after learning all of this about the Swift version, I will probably be using them quite a bit more often.
This wasn’t explicitly necessary in the main part of the post, but I thought I should point out a bit more about Data Structures. It sounds like a very highfalutin (huh, never actually written that word) word that you’ll need a degree in Computer Science to understand, but it really just means that, Structures to store Data in. So that means it includes the simple container types in many programming languages like Arrays, Dictionaries, and Sets. It also includes Queues, Stacks, Trees (like a Family Tree), and many more. You can read a lot about a lot more of them at Wikipedia’s List of Data Structures page.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!