Skip to Content
Course content

180: OrderedSet and OrderedDictionary (Swift Collections Package)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a feature recently for a search history page. The requirement was simple: keep a list of the user's last ten searches. If they search for something they've already looked up, that term should move to the top of the list, and we definitely shouldn't have the same word appearing twice.

The struggle with standard Arrays

My first instinct was to just use a standard Array. It's the default for a reason—it maintains order. Let's see how that looks in practice:

var searchHistory: [String] = []

func addSearch(_ term: String) {
    if let index = searchHistory.firstIndex(of: term) {
        searchHistory.remove(at: index)
    }
    searchHistory.insert(term, at: 0)
}

addSearch("SwiftUI")
addSearch("Combine")
addSearch("SwiftUI") // Should move to top
print(searchHistory) // ["SwiftUI", "Combine"]

It works, but there's a performance smell here. Every time I call firstIndex(of:), Swift has to scan the array linearly. If this list grew to a few hundred items, I'd be doing an O(n) operation just to check if a term exists. Not ideal.

Trying a Set for speed

To fix the lookup speed, a Set is the obvious choice. Lookups are O(1), which is lightning fast. I'll swap the array for a set and see what happens:

var searchHistory: Set<String> = []
searchHistory.insert("SwiftUI")
searchHistory.insert("Combine")
searchHistory.insert("Generics")

print(searchHistory) // ["Generics", "SwiftUI", "Combine"] (Order is random!)

And there's the wall. The moment I switched to a Set, I lost the order of insertion. My UI would essentially be shuffling the user's history every time the screen refreshed. I'm stuck between an Array (ordered but slow lookups) and a Set (fast lookups but unordered).

Bringing in the Collections Package

This is exactly why Apple created the swift-collections package. It provides data structures that fill these specific gaps in the standard library. Specifically, we want an OrderedSet. It's essentially a hybrid: it maintains a list for order and a hash table for fast membership checks.

Once you've added the package to your project, the implementation becomes much cleaner:

import Collections

var searchHistory: OrderedSet<String> = []

searchHistory.append("SwiftUI")
searchHistory.append("Combine")
searchHistory.append("Generics")

// Moving an existing element to the end (or start)
searchHistory.append("SwiftUI") 
// Wait, append doesn't move it; it just sees it's already there.
// Let's fix that.
searchHistory.remove("SwiftUI")
searchHistory.append("SwiftUI")

print(searchHistory) // ["Combine", "Generics", "SwiftUI"]

Now I have the best of both worlds. I can check if a term exists instantly, and I can iterate through them in the exact order they were added. It's basically an array that refuses to allow duplicates.

When keys need a sequence: OrderedDictionary

But what if I need more than just a list of strings? Suppose I want to store the search term as the key, but I also want to store a timestamp of when the search happened. A standard Dictionary is useless here because, like Set, it's unordered.

Let's try the OrderedDictionary. It behaves like a dictionary for access, but like an array for ordering.

import Collections

var searchMetadata: OrderedDictionary<String, Date> = [:]

searchMetadata["SwiftUI"] = Date()
searchMetadata["Combine"] = Date().addingTimeInterval(10)
searchMetadata["Collections"] = Date().addingTimeInterval(20)

// I can access by key like a normal dictionary
let date = searchMetadata["SwiftUI"]

// But I can also access by index!
let firstSearch = searchMetadata.elements[0] 
print(firstSearch.key) // "SwiftUI"

The .elements property is the secret sauce here. It gives me a view of the dictionary as a collection of key-value pairs that I can subscript or slice just like an array. If you're building a list that needs to be sorted by a specific logic but still needs fast key-based lookup, this is your tool.




📋 Practical Task

Exercise: Building a Priority Task Queue

You are building a task manager where tasks are added in order of priority, but each task must have a unique ID. If a task ID is re-added, it should be treated as a "priority bump" and moved to the end of the queue.

Your Goal: Use OrderedSet from the Swift Collections package to implement a TaskQueue class with the following requirements:

  • A method addTask(id: String) that adds a task to the end of the queue. If the ID already exists, remove it first and then add it to the end.
  • A method popNextTask() -> String? that removes and returns the task at the front of the queue (the oldest/highest priority).
  • A method containsTask(id: String) -> Bool that checks if a task is currently in the queue.

Test your implementation by adding "Task_1", "Task_2", and "Task_3", then bumping "Task_1" to the end, and finally popping the first two tasks to ensure the order is correct.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.