An Array and a Set seem to be quite the same in the beginning. They’re both collection types and have quite a lot of similarities. Still, we’re often tempted to use Arrays instead of Sets. While this does not have to be a problem it could definitely be better to sometimes go for a Set instead.
The fundamental differences
Before we dive into actual reasons to go for an Array or a Set we go over a few fundamental differences in working with both types.
Initialization
When we compare the initialization of Arrays and Sets we can really see that they’re quite the same.
let arrayOfBlogCategories: [String] = ["Swift", "Debugging", "Xcode", "Workflow", "Optimization"]
let setOfBlogCategories: Set<String> = ["Swift", "Debugging", "Xcode", "Workflow", "Optimization"]
Both types support the same ExpressibleByArrayLiteral
protocol although it’s good to know that the Array Literal type defaults to an Array. Using the following code will end up in an Array of Strings:
let blogCategories = ["Swift", "Debugging", "Xcode", "Workflow", "Optimization"] // Defaults to Array<String>
At first, you might think that both collections are exactly the same after initialization. However, this is not the case. Take a look at the following quote from the documentation:
A set stores distinct values of the same type in a collection with no defined ordering. You can use a set instead of an array when the order of items is not important, or when you need to ensure that an item only appears once.
In other words, a Set is unordered while an Array is keeping its order. Printing out the collections after initialization confirms this:
print(arrayOfBlogCategories) // ["Swift", "Debugging", "Xcode", "Workflow", "Optimization", "WWDC"]
print(setOfBlogCategories) // ["Xcode", "WWDC", "Swift", "Workflow", "Debugging", "Optimization"]
This is important to take into account when picking your choice.
Adding objects
Both Arrays and Sets are value types. Therefore, if you define them as constants using let
you’ll run into the following error when you try to add an object:
As you can see in the above image we have our first difference here. We have to use append(_:)
for an Array and insert(_:)
for a Set. Both methods add the WWDC category to the collection but only in the Array we know for sure it’s been added to the end of the list as the array is ordered.
Another difference here is the return type of the insert(_:)
method. It returns both a inserted
boolean and a memberAfterInsert
property which either contains the already-existing object or the just inserted object. This can be valuable if you want to give feedback to the user if an object already exists:
let (inserted, memberAfterInsert) = setOfBlogCategories.insert("Swift")
if !inserted {
print("\(memberAfterInsert) already exists")
}
// Prints: "Swift already exists"
Uniqueness of elements
A big difference between Sets and Arrays is the uniqueness of elements. An Array can contain the same value twice while a Set will never contain duplicates. This is also the reason that the above insert(_:)
method is returning a boolean to indicate whether the insert actually succeeded.
This is an important difference and can be the reason to go for a Set and to prevent yourself from using a filter on an Array to filter out the duplicates.
The order of elements
One of the biggest differences between an Array and a Set is the order of elements. The documentation describes this as well:
- Array: “An ordered, random-access collection.”
- Set: “An unordered collection of unique elements.”
Therefore, if the order of elements is important to your use-case, you should go for an Array.
But how about NSOrderedSet?
Another available type is NSOrderedSet. However, this is not a Swift type and comes with a cost. As it’s a non-generic type we have to work with Any
objects and cast everywhere we use it.
It is, however, a great ordered alternative to Arrays and should be used when the performance of testing for membership of an element is important. Testing whether an object is contained in a set is faster than testing for membership of an array.
As it is a static collection type it will not be possible to add or remove objects after initialization. This might be an important reason to go for an Array instead.
Performance differences
Another reason to go for a Set is when performance is important or when a large amount of data is expected. Elements in a Set need to conform to the Hashable
protocol which makes Set optimized for performance. It takes the same amount of time to lookup an element in a small collection vs a large collection.
It’s good practice, in general, to create immutable collections if possible for performance. This applies to both Arrays and Sets and allows the Swift compiler to optimize the performance of the collections you create. If you’d like to learn more about performance and collections, check out Performance, functional programming, and collections in Swift.
Core Data, NSSet and converting to Swift types
It’s quite tempting to go for a typed Set when working with collections in Core Data. However, this comes with a cost which is not always clear.
If your Core Data collection is a constant and will not (often) change it’s more than fine to bridge to a Set as the copy(with:)
method, which is used upon bridging, is returning the same set in constant time. The copying performance of mutable NSSet types is unspecified and should be avoided. You can read more about this topic in the documentation.
Conclusion
It turns out that a Set is quite different compared to an Array. Therefore, a simple cheat sheet to end this blog post!
Go for an Array if:
- Order is important
- Duplicate elements should be possible
- Performance is not important
Go for a Set if:
- Order is not important
- Unique elements is a requirement
- Performance is important