Arrays VS Sets In Swift

October 11th, 2024

⏱ Reading Time: 8 mins

Collections play a vital role in programming in any programming language. In Swift particularly, collections, or more precisely, sequences of elements that can be traversed many times according to the documentation, consist of three types; arrays, sets and dictionaries. Among all, the Array is the most often used collection type, with the other two having lower usage frequency.

In this post dictionaries will be left aside, as the focus is entirely on arrays and sets. Both of these types present several similarities, but also fundamental and radical differences. As a result, and based on the attributes and capabilities they offer, one might be more suitable to utilize over the other, but always depending on the use case. There is no better or worse collection type to use just out of the box. It always depends on the needs the collection must fulfill.

But in order to decide which one to use, it’s required to be aware of what’s different between the two. So, before we get into the details of how we handle them in common operations, let’s get to know what actually changes between arrays and sets.

Main differences between Array and Set

From a practical point of view, there are two key differences that actually dictate whether we need to use the one or the other:

  1. Sets do not accept duplicate values, while arrays do. If maintaining unique values is a must in your task, then Set is probably the best candidate collection to use, unless there are other reasons mandating to use an array.
  2. Elements in a Set do not have a specific order. If you initialize and print the elements of a set multiple times, each time they will appear in a different order. Since order is non-guaranteed, index-based reference of elements is actually meaningless; using an index to refer to a specific item into a Set is risky, as the order of elements can change after an addition or removal. If order of elements is important, then array is the way to go. The same if you plan to perform index-based manipulation of elements. Otherwise, using a Set is likely a better choice due to an efficiency-related reason, which is mentioned right next.

Performance-wise, Set is always a better choice because of its implementation details and the way it stores its elements in memory, which are required to conform to Hashable protocol. Since it has to do with hash values internally, it provides a constant time for operations on elements (O(1)). That comes in contrast to arrays that store and access elements sequentially and have an average time of O(n).

Having said all that, let’s get to know how to handle the most common operations on both arrays and sets.

Initializing arrays and sets

Starting with the basics, both arrays and sets can be initialized in a quite similar fashion, but not exactly the same. We can either be specific and explicit, or let the compiler implicitly conclude the type of the contained elements. For instance:

See that the syntax is a bit different between the array and the set. In the former, we define the data type between brackets. In the latter, the data type is specified between angle brackets (the lower than and greater than symbols).

Arrays and sets can have initial values upon creation:

In this case, it’s not really necessary to include the Int data type between the angle brackets, and simply keep the Set keyword:

Most likely you know that we can omit to explicitly declare the elements’ data type and let the compiler implicitly determine it when there are initial values. To be specific, we can write this:

That’s the short form of initializing an array. For sets it’s not the same; we still need to be clear when initializing a Set. Yet, there is a short form here too. Instead of declaring the data type, we initialize a Set passing as argument the collection of values:

Inserting elements to arrays and sets

Arrays and sets use different methods for adding new elements in the collection. In arrays there is the append(_:) method:

The above adds the new value as the last element to the array. We can also insert a value to a specific index using the insert(_:at:) method:

The new value is added at index 1, meaning the second position in the array.

Let’s go to Set now, where there is the insert(_:) instance method for adding new elements like so:

But, unlike the append(_:) or the insert(_:at:) methods of Array that do not return a value (they are void methods), insert(_:) returns a tuple with a boolean and the element we are trying to insert. More precisely:

  • If the element does not exist in the set, then the first value in the tuple is true and the second is the newly inserted element.
  • If the element already exists in the set, then the first value is false and the second is the existing element.

Note: Remember that sets do not accept duplicate values (more on that in a while)!

For example:

Note that it’s not mandatory to always handle the return value from the insert(_:) method. It’s marked with the @discardableResult attribute (see more here) so the compiler does not throw an error when left unhandled.

In order to be able to add new elements, arrays and sets must be initialized with the var keyword as variables and not with the let as constants. Both of them are value types and cannot be mutated (modified) if they are constants.

The following would make Xcode show an error:

Iterating over elements

Going through the elements of collections is a pretty common task in every programming language. The most popular type of loop, for-in, can be used in both arrays and sets, as demonstrated in the following examples:

Notice that the elements of the array are printed in order, but there’s no specific order in the set. That’s one of the fundamental differences as said in the beginning.

The forEach(_:) higher order function can also be used for iteration:

However, the following will make the compiler show an error:

The index-based iteration works perfectly in arrays, but not in sets, and the nature of the latter is the reason for that. Set is an unordered collection type that stores unique values, with the order of elements not being guaranteed after they’ve been inserted to the set. So, it’s not possible to access a single element using a subscript just like we do in arrays.

Inserting duplicate values

Now that we’ve talked about how to append new elements to both types of collections and how to iterate over their elements, it’s worth focusing a bit on what happens when we attempt to add duplicate values. Let’s start with the array, since it’s the most common use case:

See that the number 3 exists twice in the array, and in the expected indices. Now let’s try to do the same in a set:

Notice here that the number 3 is not inserted to the set, because it already exists in it. The inserted value of the result is false, indicating that insertion did not happen, while the memberAfterInsert has the value 3; the existing value in the set. An additional proof of that is the loop, where numbers are printed in random order once again, however number 3 exists just once.

See that the compiler does not raise any error when we try to add a duplicate value to the set. It simply ignores the new insertion and returns a result tuple similar to the one shown above.

Note that even if we initialize a Set with duplicate values, those will be actually inserted just once. For example:

Getting the index of elements

In arrays there are various methods that return the index of elements. Let’s go through some of them:

In all the above examples, the index returned value is an Array<Int>.Index value and not an Int.

On the other hand, one would expect that similar methods would (or should) not exist in Set, considering that index is meaningless there. The order of the elements is not constant as they are not stored sequentially and it changes when modifications happen to the set. But, it provides some methods that return the index of an element which you should use with caution, if not at all. Always remember that an index returned at any given moment is valid for that moment only; there is no guarantee that the next time you’ll seek the index of the same element will have the same value as before.

So, the provided instance methods to get an index in a Set are:

  • firstIndex(of:)
  • firstIndex(where:)
  • index(_:offsetBy:)
  • index(after:)

All of them return a Set<Int>.Index value in the example demonstrated here. The value in the angle brackets matches the data type of the elements in the Set every time. As said, be careful as using them in two different time instances might not return the same results.

Deleting elements

When it comes to delete items from arrays and sets, arrays provide a wider range of methods to manage that in comparison to sets. That’s because there are methods working with the index, something that’s not actually supported in sets.

Let’s see the most important (almost all) delete methods in arrays first:

Note that all methods except for the last two return the removed item. It’s not mandatory to handle it, unless it’s necessary.

As far as it regards sets, see next that there are much less methods to remove elements:

There is also a remove(at:) method which is similar to the array’s remove(at:). It accepts the index of the element to delete as argument. However, I would suggest against using it, because the index of an element is not certain in Set, and the order changes with every new insertion or deletion. Be cautious if you’re planning on using it.

Conclusion

Getting to the end of this tutorial, the above is pretty much all you need to know in order to wisely choose between Array and Set, as well as the basic and most common operations you can perform in them. By having all that in mind it’s easy to discover other less used features or APIs not presented here. But all the essential information is now considered known, so go ahead and use collections thoughtfully from now on.

Thank you for reading!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.