Set in Swift: The Overlooked one !
Pic Credits: reddit.com
Every Swift and iOS Developer , in their everyday coding, work with collections one way or the other . We work with Arrays and Dictionaries a lot but when was the last time we used Set in our code.
Honestly, if you ask me, i have never used Set in my code before because i never felt the need of using it. Plus, most of the code samples, solutions, or articles that i read online never used it either.
If you know the basics of Set, you must be aware of the fact that they operate pretty much the same as an array on the outside (other than the fact, that, Set does not guarantee any ordering of elements inside it and only stores unique values). Yup, that’s the basic definition of Set.
Set can be thought of as dictionaries that only store keys and not values. Duplicates never get inserted in Set.
Now, you might be wondering, if a Set behaves essentially the same as an Array , then why would we even use Set in the first place. Before jumping to any conclusion, consider this “ We have var which we use to store data into and is even mutable, then why do we use let ?”
Pic Credits : lovequotesmessages.com
Okay, now you’re confused ! Let’s start fresh .
As per Apple Documentation for Set :
An unordered collection of unique elements.
You can create a set with any element type that conforms to the Hashable protocol
That’s the basic definition and requirement for Set.
Set is extremely useful as well as optimal in cases where our prime motive is to search a collection for a particular item where all values would be unique. Since we are just searching whether the element exists or not (also called as membership tests), it really does not matter if the elements are ordered (since we just want the output to be true or false).
Consider the following cases where Set will be useful
- We want to store all the cells of the tableView that the user selected, as all the values here will be unique (because of different IndexPath for each cell)
- We want to record the user touches on the screen (we can use CGPoint, but since CGPoint does not conform to Hashable out-of-the-box , you need to explicitly provide conformance to Hashable)
Now , what makes Set so unique is the fact that it does not search element based on the value but rather search according to the hashValue which provide faster lookup for items.
The complexity of searching inside a Set, provided you have given a good Hash Function which minimises the hash collisions, is O(1) . What it means is that, using contains() on a Set takes the same amount of time if you have one item as it does if you have one thousand items.
Oh, one more thing, in case you’re wondering, you cannot access the elements of set using subscript notation like you can in case of arrays (i.e. myArray). The reason for this is that Set does not store elements according to index but rather using hashValues which results in random ordering of elements inside Set. So you cannot guarantee that the element that you added first in Set will be there at 0 index position.
Pic Credits : mememaker.net
Now, we’re done with the basics . Let’s move on to the cool parts of Set
Set is closely related to the mathematical concept of a set ; It supports all common set operations that we learned in our math class.
A list of all the operations that can be performed on sets can be found at Apple Developer website
These operations are generally called as Set Algebra.
OptionSet are quite similar to Enum in swift but they differ in the fact that OptionSet are designed as a set, so you can use more than one at a time. Hang on, we’ll get there .
Since they are designed as a Set , adopting this protocol in our custom types lets us perform set-related operations such as membership tests, unions, and intersections on those types.
As per Apple Docs —
For your type to automatically receive default implementations for set-related operations, the rawValue property must be of a type that conforms to the FixedWidthInteger protocol, such as Int or UInt8
What it means, is that, If you have a custom type that conforms to OptionSet , then that custom type should have a property named rawValue which should be of type Int for your custom type to access SetAlgebra.
If you have ever worked with basic animations in your iOS App, then you have been working with OptionSet all along.
This method has a parameter options, in which we generally pass either a single value or an array of animation types like .autoreverse, .curveEaseInOut,which is of type UIView.AnimationOptions , which conforms to OptionSet protocol.
Pic Credits: meme-arsenal.com
Now, how to create your own customOptionSet .
Create a struct that conforms to OptionSet. Create unique options (or cases if you think of it as enum) as static properties of your custom type using unique powers of two (1, 2, 4, 8, 16, and so forth, we’ll get back to why we need to use this approach ,soon) which can be constructed using the left bitshift (<<) operation with incrementing right-hand side values.
Now , coming back to why we need to use bitshift operation to provide unique values. If we assign consecutive integers (1, 2, 3, 4 …) to the option’s raw value, though they are unique, it’d be impossible to distinguish between .delete (which would have the value 3) and [.status, .insert] (which would be 1 + 2).
Advantages of OptionSet over Enum :
- OptionSet let us perform Set related operations on those types
- While working with Enum, if we want a property/variable to have either one case of enum or multiple cases, we have to provide different type for it accordingly
Let me demonstrate an example for the same —
OptionSet conformance doesn’t imply conformance to the Sequence or Collection protocols, so you can’t use .count to determine how many bits are set or iterate over the selected options in a for loop.
If you look inside the Apple Documentation, you will find out that there are only 2 Set inside Standard Library that conform to SetAlgebra , those two have been explained above. But the protocol is also adopted by two interesting types in Foundation: IndexSet and CharacterSet
IndexSet represents a Set of positive integer values. Now, you might be wondering , we can do the same with Set<Int> . Yes, we can , but using IndexSet is more storage efficient as it uses a list of ranges internally.
Consider an example where we have a UITableView with 1000 cells and we want to store the indices of all the cells that user has selected. A Set<Int> needs to store up to 1000 indices, depending on how many rows are selected. An IndexSet, on the other hand, stores continuous ranges, so a selection of the first 500 rows in the tableView only takes two integers to store (the selection’s lower and upper bounds).
We can add a ranges to an IndexSet and then map over the indices as if they were individual members.
The thing about IndexSet is that, no matter in which order you insert elements inside IndexSet, it will always be in ascending order when you try to access it.
CharacterSet a.k.a UnicodeScalarSet
The first thought that comes to everyone’s mind after reading the name CharacterSet is “ Okay, a set that will contain unique characters”. Well ,no ! This particular structure should actually be called UnicodeScalarSet because that’s what it really is , a set of Unicode-compliant characters . Since they are unordered and don’t contain duplicates, CharacterSet are typically used in searching operations .
Also, the “set” part of CharacterSet refers not to Set from the Swift standard library, but instead to the SetAlgebra protocol.
CharacterSet can be initialized as an empty set or from a set of characters present within a string, bytes, or the contents of a file. We can also use some of the standard CharacterSet provided out-of-the-box such as character sets of alphanumerics, decimal digits etc. You can find a collection of them (no pun intended) on Apple Developer website
CharacterSet are extremely useful in case where we want to validate a string against some set of characters (no pun intended, again!).
That’s all from my side ! I personally believe that Set should be used more often as a collection choice, than it is being used right now. Whenever you want to store some non key-value pair data (key-value pair can be stored in Dictionary )into a collection, first ask yourself , “Can I use a Set here instead of an Array” , keeping in mind the unordered nature of Set.
Again, if the sole purpose of storing the data inside the collection is to search the collection later for some element, then consider going with Set instead of an Array !