Many iOS and macOS applications use Set
for internal logic. A new Swift package called swift-collections introduces OrderedSet
, a set which keeps track of insertion order. This post presents an overview of OrderedSet
and usage examples:
- OrderedSet Examples
a. Insert Element
b. Get Element
c. Remove Element
d. Union
e. Intersection - Set vs OrderedSet
a. OrderedSet Maintains Insert Order
b. OrderedSet Insert Performance Differs - When To Use OrderedSet
a. Time-Series Deduplication
Note: To use OrderedSet
, first add the swift-collections
Swift package to your project. Then, import the OrderedCollections
module:
import OrderedCollections
OrderedSet Examples
Insert Element
var orderedSet = OrderedSet(["id0", "id1"])
orderedSet.append("id2")
// OrderedSet now contains, in order,
// "id0", "id1", "id2"
Get Element
A traditional Set
does not have an interface to "get" an element, only contains(_:) -> Bool
to determine if the Set
includes a specific element. OrderedSet implements contains(_:) -> Bool
as well, and also allows elements to be retrieved by the position of the element in the order:
var orderedSet = OrderedSet(["id0", "id1", “id2”])
// orderedSet contains, in order,
// "id0", "id1", "id2". Getting the element
// at index 1 returns “id1”
// Get the element at index 1
orderedSet[1] // "id1"
Remove Element
There are multiple ways to remove elements from an OrderedSet
. One way is to remove an element explicitly, either specifying the element directly or the index the element is at:
// Remove a specific element
orderedSet.remove("id2")
// Remove an element at a specific index
orderedSet.remove(at: 2)
Another method is to remove elements relative to the front and back of the OrderedSet
:
// Remove elements from the front
orderedSet.removeFirst()
orderedSet.removeFirst(2)
// Remove elements from the back
orderedSet.removeLast()
orderedSet.removeLast(2)
OrderedSet
also includes methods for removing all elements, and removing all elements that meet some filter criteria:
// Remove all elements
orderedSet.removeAll()
// Filter elements
orderedSet.removeAll { (element) -> Bool in
// Filter criteria
}
Union
A union between a Set
and another Set
returns a new Set
with elements of both source sets. Similarly, a union between an OrderedSet
and another OrderedSet
(or sequence) returns a new OrderedSet
with elements of both sources. In an OrderedSet
union, new elements are appended to the back of the OrderedSet
.
let orderedSet = OrderedSet(["id0", "id1"])
var newOrderedSet = orderedSet.union([
"id1", "newId", "id0"
])
// newOrderedSet now contains, in order,
// "id0", "id1", "newId"
Intersection
An intersection between a Set
and another Set
returns a new Set
with only common elements of both source sets. Similarly, an intersection between an OrderedSet
and another OrderedSet
(or sequence) returns a new OrderedSet
with only common elements of both sources. In an OrderedSet
intersection, elements are ordered according to the OrderedSet
on which union(_:)
was called:
let orderedSet = OrderedSet([
"id0", "id1", "id2", "id3", "id4"
])
var newOrderedSet = orderedSet.intersection([
"id3", "id2", "id0"
])
// newOrderedSet now contains, in order,
// "id0", "id2", "id3"
Set vs OrderedSet
Set
is an unordered collection of unique elements, often used to test if an element belongs to a set. Like Set
, OrderedSet
contains unique elements and can be used to test if an element belongs to a set. Unlike Set
:
OrderedSet Maintains Insert Order
As the name indicates, OrderedSet
maintains element order. This means an OrderedSet
can efficiently check if an element is within the OrderedSet
, like a traditional Set
, and also retrieve elements at a specific position, like a traditional Array
.
To maintain element order, OrderedSet
has a different interface than Set
. Instead of implementing add(_:)
to add elements, OrderedSet
implements append(_:)
and insert(_:, at:)
for greater clarity on how the order is modified. OrderedSet.append(_:)
adds an element to the back of an OrderedSet
(if the element is not already in the OrderedSet
), and insert(_:, at:)
adds an element at a specific index in the OrderedSet
.
OrderedSet Insert Performance Differs
An OrderedSet
implementation requires additional logic for insertions and removals to maintain element order. The swift-collections README details the performance impact:
"these operations are expected to have performance characteristics similar to an Array: inserting or removing an element to the end of an ordered set is expected to execute in O(1) operations, while they are expected to take linear time at the front (or in the middle) of the set"
This means that repeated calls to insert(_:, at:)
or remove(at:)
at the front or the middle of an OrderedSet
will take longer than repeated append(_:)
or removeLast()
calls.
When To Use OrderedSet
Time-Series Deduplication
In some cases, for example a time series of data points, deduplication requires order to be perseved. An OrderedSet
can deduplicate ordered elements while preserving the original element order:
var timeSeries = [
["id": "0", "value": "0"],
["id": "1", "value": "1"],
["id": "0", "value": "0"],
["id": "2", "value": "2"],
["id": "1", "value": "1"]
]
var orderedUniqueSeries = OrderedSet(timeSeries)
// orderedUniqueSeries contains, in order,
// ["id": "0", "value": "0"],
// ["id": "1", "value": "1"],
// ["id": "2", "value": "2"]
// Iterating over an OrderdSet always
// yields elements in order
for item in orderedUniqueSeries {
print("\(item["id"]!)")
}
// Expected console output:
// "0"
// "1"
// "2"
Swift OrderedSet
That’s it! By using the swift-collections
package and OrderedSet
, you can take advantage of both Set
properties and element ordering in Swift.