Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 Swift

WWDC21 · 31 min · Swift

Meet the Swift Algorithms and Collections packages

Discover two of the latest additions to the list of open-source Swift packages from Apple: Swift Algorithms and Swift Collections. Not only can you use these packages immediately, they also incubate new algorithms and data structures for eventual inclusion in the Swift Standard Library. We’ll show you how you can integrate these packages into your projects and select the right algorithms and data structures to make your code clearer and faster.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 23 snippets

The map algorithm swift · at 1:00 ↗
// Raw loop:
var selectedMessages: [Message] = []
for indexPath in indexPathsForSelectedRows {
    selectedMessages.append(messages[indexPath.row])
}

// Using `map` makes this clearer and faster.
indexPathsForSelectedRows.map { messages[$0.row] }
The compactMap algorithm swift · at 1:36 ↗
// Raw loop:
var attachments: [Attachment] = []
for message in messages {
    if let attachment = message.attachment {
        attachments.append(attachment)
    }
}

// The above is just a `map` and a `filter`.
messages
    .filter { $0.attachment != nil }
    .map { $0.attachment! }

// This pattern is so common we have a special name and algorithm for it.
messages.compactMap { $0.attachment }
The flatMap algorithm swift · at 2:06 ↗
extension Message {
    func makeMessageParts() -> [TranscriptItem]
}

messages // [Message]
    .map { $0.makeMessageParts() } // [[TranscriptItem]]
    .joined() // [TranscriptItem]

// This pattern is so common that we have another special kind of map for it.
messages // [Message]
    .flatMap { $0.makeMessageParts() }  // [TranscriptItem]
Chaining together algorithms swift · at 3:00 ↗
// Raw loop:
var photos: [PhotoItem] = []
for item in transcript.reversed() {
    if let photo = item as? PhotoItem {
        photos.append(photo)
        if photos.count == 6 {
            break
        }
    }
}

// The above can be expressed more concisely by chaining together algorithms.
transcript
    .reversed() // [TranscriptItem]
    .compactMap { $0 as? PhotoItem } // [PhotoItem]
    .prefix(6) // [PhotoItem]

// This gives us more flexibility to express this code more clearly.
transcript
    .compactMap { $0 as? PhotoItem } // [PhotoItem]
    .suffix(6) // [PhotoItem]
    .reversed() // [PhotoItem]
Lazy adapters swift · at 4:19 ↗
extension Message {
    func makeMessageParts() -> [TranscriptItem]
}

messages
    .map { $0.makeMessageParts() } // [[TranscriptItem]]
    .joined() // FlattenSequence<[[TranscriptItem]]>
Lazy algorithm chains swift · at 4:58 ↗
transcript
    .lazy // LazySequence<[TranscriptItem]>
    .compactMap { $0 as? PhotoItem } // LazyCompactMap<[TranscriptItem], PhotoItem>
    .suffix(6) // LazyCompactMap<ArraySlice<TranscriptItem>, PhotoItem>
    .reversed() // ReversedCollection<LazyCompactMap<ArraySlice<TranscriptItem>, PhotoItem>>
Wrapping a lazy algorithm chain in an Array initializer swift · at 5:48 ↗
Array(
    transcript
        .lazy
        .compactMap { $0 as? PhotoItem }
        .suffix(6)
        .reversed()
)
windows(ofCount:) swift · at 7:13 ↗
import Algorithms

let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for window in x.windows(ofCount: 3) {
    print(window)
}

// Prints [0, 1, 2]
// Prints [1, 2, 3]
// Prints [2, 3, 4]
// Prints [3, 4, 5]
adjacentPairs() swift · at 7:30 ↗
import Algorithms

let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for (prev, next) in x.adjacentPairs() {
    print((prev, next))
}

// Prints (0, 1)
// Prints (1, 2)
// Prints (2, 3)
// Prints (3, 4)
chunks(ofCount:) swift · at 7:45 ↗
import Algorithms

let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for chunk in x.chunks(ofCount: 3) {
    print(chunk)
}

// Prints [0, 1, 2]
// Prints [3, 4, 5]
// Prints [6, 7, 8]
// Prints [9]
chunked(on:) swift · at 8:08 ↗
import Algorithms

let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for (isPrime, chunk) in x.chunked(on: \.isPrime) {
    print((isPrime, chunk))
}

// Prints (false, [0, 1])
// Prints (true, [2, 3])
// Prints (false, [4])
// Prints (true, [5])
Recognizing the chunked(on:) pattern swift · at 8:33 ↗
// Raw loop:
var prev: Element?
for element in collection {
    if prev?.value != element.value {
        // do work
    }
    prev = element
}

// The above is just `chunked(on:)`.
for (value, chunk) in collection.chunked(on: \.value) {
    // do work
}
Mapping, chunking, and joining swift · at 8:49 ↗
import Algorithms

extension Message {
    func makeMessageParts() -> [TranscriptItem]
}

transcript = Array(
    messages
        .lazy
        .flatMap { $0.makeMessageParts() }
        .chunked { $1.date.timeIntervalSince($0.date) < 60 * 60 }
        .joined { DateItem(date: $1.first!.date) }
)
Double-ended queues swift · at 14:56 ↗
var queue: Deque = ["A", "B", "C"]

queue.append("D")
queue.append("E")
queue.removeFirst()  // "A"
queue.removeFirst()  // "B"

queue.prepend("F")
queue.prepend("G")
queue.removeLast()   // "E"
queue.removeLast()   // "D"
Deque protocol conformances swift · at 15:46 ↗
var items: Deque = ["D", "E", "f"]
print(items[1])  // "E"
items[2] = "F"
items.insert(contentsOf: ["A", "B", "C"], at: 0)
print(items[1])  // "B"
Accessing elements is still efficient swift · at 17:31 ↗
var items: Deque = ["D", "E", "F"]
print(items[1])  // "E"
items.insert(contentsOf: ["A", "B", "C"], at: 0)
print(items[1])  // "B"
Removing elements at random is twice as fast on average swift · at 18:39 ↗
var items: Deque = ["A", "B", "C", "D", "E", "F"]
items.removeSubrange(1 ..< 3)
Unordered sets swift · at 19:33 ↗
let first: Set = ["A", "B", "C", "D", "E", "F"]
print(first)  // ["B", "E", "C", "F", "D", "A"]
let second: Set = ["A", "B", "C", "D", "E", "F"]
print(second)  // ["A", "D", "E", "F", "C", "B"]
print(first == second)  // true
Ordered sets swift · at 20:26 ↗
let first: OrderedSet = ["A", "B", "C", "D", "E", "F"]
print(first)         // ["A", "B", "C", "D", "E", "F"]

let second: OrderedSet = ["F", "E", "D", "C", "B", "A"]

print(first == second)                      // false
print(first.unordered == second.unordered)  // true
Ordered sets resemble how arrays work swift · at 21:04 ↗
var items: OrderedSet = ["E", "D", "C", "B", "A"]
items[3]  // "B"
items.append("F")         // (inserted: true, index: 5)
items.insert("B", at: 1)  // (inserted: false, index: 3)
items.remove("E")
items.sort()
items.shuffle()
Ordered sets implement high-level set operations swift · at 22:32 ↗
var items: OrderedSet = ["B", "D", "E"]
items.formUnion(["A", "B", "C", "F"])
items.subtract(["A", "B", "G"])

let other: OrderedSet = ["C", "D", "E", "F"]
print(items == other)  // false
print(items.unordered == other.unordered)  // true
Ordered dictionaries swift · at 26:46 ↗
var dict: OrderedDictionary = [2: "two", 1: "one", 0: "zero"]

print(dict[1])  // Optional("one")
print(dict)     // [2: "two", 1: "one", 0: "zero"]

dict[3] = "three"
dict[1] = nil
print(dict)     // [2: "two", 0: "zero", 3: "three"]
Subscripting always means the keying subscript swift · at 27:38 ↗
var dict: OrderedDictionary = [2: "two", 0: "zero", 3: "three"]

print(dict[0])           // Optional("zero")       

print(dict.elements[0])  // (key: 2, value: "two")

Resources