Data Structures and Algorithms in Swift - Implementing Practical Data Structures With Swift 4 (EnglishOnlineClub - Com)
Data Structures and Algorithms in Swift - Implementing Practical Data Structures With Swift 4 (EnglishOnlineClub - Com)
com
Data
Structures
& Algorithms
in Swift
By Kelvin Lau & Vincent Ngo
2
Download from finelybook 7450911@qq.com
Notice of Rights
All rights reserved. No part of this book or corresponding materials
(such as text, images, or source code) may be reproduced or
distributed by any means without prior written permission of the
copyright owner.
Notice of Liability
This book and all corresponding materials (such as source code) are
provided on an “as is” basis, without warranty of any kind, express of
implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and
noninfringement. In no event shall the authors or copyright holders
be liable for any claim, damages or other liability, whether in action of
contract, tort or otherwise, arising from, out of or in connection with
the software or the use of other dealing in the software.
Trademarks
All trademarks and registered trademarks appearing in this book are
the property of their own respective owners.
3
Download from finelybook 7450911@qq.com
4
Download from finelybook 7450911@qq.com
Chris Belanger is the editor of this book. Chris is the Editor in Chief at
raywenderlich.com. He was a developer for nearly 20 years in various
fields from e-health to aerial surveillance to industrial controls. If
there are words to wrangle or a paragraph to ponder, he’s on the case.
When he kicks back, you can usually find Chris with guitar in hand,
looking for the nearest beach. Twitter: @crispytwit.
5
Download from finelybook 7450911@qq.com
Ray Fix is the final pass editor of this book. A passionate Swift
educator, enthusiast and advocate, he is actively using Swift to create
Revolve: a next generation iPad controlled research microscope at
Discover Echo Inc. Ray is mostly-fluent in spoken and written
Japanese and stays healthy by walking, jogging, and playing ultimate
Frisbee. When he is not doing one of those things, he is writing and
dreaming of code in Swift. You can find him on Twitter: @rayfix.
6
Download from finelybook 7450911@qq.com
7
Download from finelybook 7450911@qq.com
Book license
By purchasing Data Structures & Algorithms in Swift, you have the
following license:
You are allowed to use and/or modify the source code in Data
Structures & Algorithms in Swift in as many apps as you want,
with no attribution required.
You are allowed to use and/or modify all art, images and designs
that are included in Data Structures & Algorithms in Swift in as
many apps as you want, but must include this attribution line
somewhere inside your app: “Artwork/images/designs: from Data
Structures & Algorithms in Swift, available at
www.raywenderlich.com”.
This book is for your personal use only. You are NOT allowed to
sell this book without prior authorization, or distribute it to
friends, coworkers or students; they would need to purchase their
own copies.
All materials provided with this book are provided on an “as is” basis,
without warranty of any kind, express or implied, including but not
limited to the warranties of merchantability, fitness for a particular
purpose and noninfringement. In no event shall the authors or
copyright holders be liable for any claim, damages or other liability,
whether in an action of contract, tort or otherwise, arising from, out
of or in connection with the software or the use or other dealings in
the software.
9
Download from finelybook 7450911@qq.com
We hope you enjoy the preview of this book, and that you’ll come
back to help us celebrate the full launch of Data Structures &
Algorithms in Swift later in 2018!
The best way to get update notifications is to sign up for our monthly
newsletter. This includes a list of the tutorials that came out on
raywenderlich.com that month, any important news like book updates
or new books, and a list of our favorite development links for that
month. You can sign up here:
www.raywenderlich.com/newsletter
10
Download from finelybook 7450911@qq.com
https://store.raywenderlich.com/products/swift-apprentice
https://store.raywenderlich.com/products/ios-apprentice
11
Download from finelybook 7450911@qq.com
12
Download from finelybook 7450911@qq.com
www.raywenderlich.com/store/data-structures-algorithms-
swift/source-code
There, you’ll find all the code from the chapters for your use.
13
Download from finelybook 7450911@qq.com
Little is known about the giant squid, due to its preference for cold,
deep-water habitats. It’s much like the data structures and algorithms
that lurk deep within software; although you may not yet understand
how data structures and algorithms form the basis of scalable, high-
performance solutions, this book will be your own personal Nautilus
that will transport you 20,000 leagues under a sea of code!
14
Download from finelybook 7450911@qq.com
Preface
The study of data structures is one about efficiency. Given a particular
amount of data, what is the best way to store it to achieve a particular
goal?
Data structures are a well-studied area, and the concepts are language
agnostic; a data structure from C is functionally and conceptually
identical to the same data structure in any other language, such as
Swift. At the same time, the high-level expressiveness of Swift make it
an ideal choice for learning these core concepts without sacrificing
too much performance.
Interviews
When you interview for a software engineering position, chances are
you’ll be tested on data structures and algorithms. Having a strong
foundation in data structures and algorithms is the “bar” for many
companies with software engineering positions.
Work
Data structures are most relevant when working with large amounts
15
Download from finelybook 7450911@qq.com
Data structures are most relevant when working with large amounts
of data. If you’re dealing with a non-trivial amount of data, using the
appropriate data structure will play a big role in the performance and
scalability of your software.
Self-improvement
Knowing about the strategies used by algorithms to solve tricky
problems gives you ideas for improvements you can make to your own
code. Swift’s Standard Library has small set of general purpose
collection types; they definitely don’t cover every case.
And yet, as you will see, these primitives can be used as a great
starting point for building more complex and special purpose
constructs. Knowing more data structures than just the standard array
and dictionary gives you a bigger collection of tools you can use to
build your own apps.
16
Download from finelybook 7450911@qq.com
In this chapter you’ll focus on two data structures that the standard
library provides right out of the box: Array and Dictionary.
Arrays
An array is a general-purpose, generic container for storing a
collection of elements, and is used commonly in all sorts of Swift
programs. You can create an array by using an array literal, which is a
comma-separated list of values surrounded with square brackets. For
example:
This is a generic type in that it abstracts across any element type. The
element type has no formal requirements; it can be anything. In the
above example, the compiler type infers the elements to be String
types.
17
Download from finelybook 7450911@qq.com
Order
Elements in an array are explicitly ordered. Using the above people
array as an example, "Brian" comes before "Stanley".
You can retrieve the value of an element in the array by writing the
following:
people[0] // "Brian"
people[1] // "Stanley"
people[2] // "Ringo"
Order is defined by the array data structure and should not be taken
for granted. Some data structures, such as Dictionary, have a weaker
concept of order.
Random-access
Random-access is a trait that data structures can claim if they can
handle element retrieval in constant O(1) time. For example, getting
"Ringo" from the people array takes constant time. Again, this
performance should not be taken for granted. Other data structures
such as linked lists and trees do not have constant time access.
Array performance
18
Download from finelybook 7450911@qq.com
Insertion location
The first factor is where you choose to insert the new element inside
the array. The most efficient scenario for adding an element to an
array is to append it at the end of the array:
people.append("Charles")
print(people) // prints ["Brian", "Stanley", "Ringo", "Charles"]
Inserting "Charles" using the append method will place the string at
the end of the array. This is a constant-time operation, and is the
most efficient way to add elements into an array. However, there may
come a time that you need to insert an element in a particular
location, such as in the very middle of the array. In such a scenario,
this is an O(n) operation.
To help illustrate why that is the case, consider the following analogy.
You’re standing in line for the theater. Someone new comes along, to
join the lineup. What’s the easiest place to add people to the lineup?
At the end of course!
If the newcomer tried to insert themselves into the middle of the line,
they would have to convince half the lineup to shuffle back to make
room.
And if they were terribly rude, they’d try to insert themselves at the
head of the line. This is the worst-case scenario, because every single
person in the lineup would need to shuffle back to make room for this
new person in front!
This is exactly how the array works. Inserting new elements from
19
Download from finelybook 7450911@qq.com
This is exactly how the array works. Inserting new elements from
anywhere aside from the end of the array will force elements to
shuffle backwards to make room for the new element:
people.insert("Andy", at: 0)
// ["Andy", "Brian", "Stanley", "Ringo", "Charles"]
The second factor that determines the speed of insertion is the array’s
capacity. If the space that the array pre-allocated (the capacity) is
exceeded it must reallocate storage and copy over the current
elements. This means that any given insertion, even at the end, could
take n steps to complete if a copy is made. However, the standard
library employs a subtle trick to prevent appends from being O(n)
time. Each time it runs out of storage and needs to copy, it doubles
the capacity. Doing this allows arrays to have an amortized cost that
it is still constant time O(1).
Dictionary
A dictionary is another generic collection that holds key-value pairs.
For example, here’s a dictionary containing a user’s name and their
score:
Dictionaries don’t have any guarantees of order, nor can you insert at
20
Download from finelybook 7450911@qq.com
Dictionaries don’t have any guarantees of order, nor can you insert at
a specific index. They also put a requirement on the Key type that it
be Hashable. Fortunately almost all of the standard types are already
Hashable and in the most recent versions Swift, adopting the
Hashable protocol is now trivial.
You can add a new entry to the dictionary with the following syntax:
scores["Andrew"] = 0
21
Download from finelybook 7450911@qq.com
22
Download from finelybook 7450911@qq.com
Linked list
A linked list is a collection of values arranged in a linear
unidirectional sequence. A linked list has several theoretical
advantages over contiguous storage options such as the Swift Array:
Constant time insertion and removal from the front of the list.
1. Hold a value.
2. Hold a reference to the next node. A nil value represents the end
of the list.
Open up the starter playground for this chapter so you can dive right
into the code.
Node
Create a new Swift file in the Sources directory and name it
Node.swift. Add the following to the file:
23
Download from finelybook 7450911@qq.com
node1.next = node2
node2.next = node3
print(node1)
}
24
Download from finelybook 7450911@qq.com
LinkedList
In the Sources directory, create a new file and name it
LinkedList.swift. Add the following to the file:
public init() {}
25
Download from finelybook 7450911@qq.com
}
}
A linked list has the concept of a head and tail, which refers to the
first and last nodes of the list respectively:
You’ll implement each of these in the next section and analyze their
performance characteristics.
push
Adding a value at the front of the list is known as a push operation.
This is also known as head-first insertion. The code for it is
deliciously simple.
26
Download from finelybook 7450911@qq.com
In the case where you’re pushing into an empty list, the new node is
both the head and tail of the list.
example(of: "push") {
var list = LinkedList<Int>()
list.push(3)
list.push(2)
list.push(1)
print(list)
}
---Example of push---
1 -> 2 -> 3
append
The next operation you’ll look at is append. This is meant to add a
value at the end of the list, and is known as tail-end insertion.
Head back into LinkedList.swift and add the following code just below
push:
// 1
guard !isEmpty else {
push(value)
return
27
Download from finelybook 7450911@qq.com
// 2
tail!.next = Node(value: value)
// 3
tail = tail!.next
}
1. Like before, if the list is empty, you’ll need to update both head
and tail to the new node. Since append on an empty list is
functionally identical to push, you simply invoke push to do the
work for you.
2. In all other cases, you simply create a new node after the tail
node. Force unwrapping is guaranteed to succeed since you push
in the isEmpty case with the above guard statement.
3. Since this is tail-end insertion, your new node is also the tail of
the list.
Leap back into the playground and write the following at the bottom:
example(of: "append") {
var list = LinkedList<Int>()
list.append(1)
list.append(2)
list.append(3)
print(list)
}
---Example of append---
1 -> 2 -> 3
28
Download from finelybook 7450911@qq.com
insert(after:)
The third and final operation for adding values is insert(after:).
This operation inserts a value at a particular place in the list, and
requires two steps:
First, you’ll implement the code to find the node where you want to
insert your value.
// 2
while currentNode != nil && currentIndex < index {
currentNode = currentNode!.next
currentIndex += 1
}
return currentNode
}
node(at:) will try to retrieve a node in the list based on the given
index. Since you can only access the nodes of the list from the head
node, you’ll have to make iterative traversals. Here’s the play-by-
play:
1. You create a new reference to head and keep track of the current
number of traversals.
2. Using a while loop, you move the reference down the list until
you’ve reached the desired index. Empty lists or out-of-bounds
29
Download from finelybook 7450911@qq.com
// 1
@discardableResult
public mutating func insert(_ value: Value,
after node: Node<Value>)
-> Node<Value> {
// 2
guard tail !== node else {
append(value)
return tail!
}
// 3
node.next = Node(value: value, next: node.next)
return node.next!
}
2. In the case where this method is called with the tail node, you’ll
call the functionally equivalent append method. This will take
care of updating tail.
3. Otherwise, you simply link up the new node with the rest of the
list and return the new node.
Hop back to the playground page to test this out. Add the following to
the bottom of the playground:
30
Download from finelybook 7450911@qq.com
list.push(3)
list.push(2)
list.push(1)
Performance analysis
Whew! You’ve made good progress so far. To recap, you’ve
implemented the three operations that add values to a linked list and
a method to find a node at a particular index.
31
Download from finelybook 7450911@qq.com
pop
Removing a value at the front of the list is often referred to as pop.
This operation is almost as simple as push, so let’s dive right in.
@discardableResult
public mutating func pop() -> Value? {
defer {
head = head?.next
if isEmpty {
tail = nil
}
}
return head?.value
}
pop returns the value that was removed from the list. This value is
optional, since it’s possible that the list is empty.
By moving the head down a node, you’ve effectively removed the first
node of the list. ARC will remove the old node from memory once the
method finishes, since there will be no more references attached to it.
In the event that the list becomes empty, you set tail to nil.
32
Download from finelybook 7450911@qq.com
Head back inside the playground page and test it out by adding the
following code at the bottom:
example(of: "pop") {
var list = LinkedList<Int>()
list.push(3)
list.push(2)
list.push(1)
---Example of pop---
Before popping list: 1 -> 2 -> 3
After popping list: 2 -> 3
Popped value: Optional(1)
removeLast
Removing the last node of the list is somewhat inconvenient.
Although you have a reference to the tail node, you can’t chop it off
without having a reference to the node before it. Thus, you’ll have to
do an arduous traversal. Add the following code just below pop:
@discardableResult
public mutating func removeLast() -> Value? {
// 1
guard let head = head else {
return nil
}
// 2
guard head.next != nil else {
return pop()
}
33
Download from finelybook 7450911@qq.com
// 3
var prev = head
var current = head
4. Since current is the last node, you simply disconnect it using the
prev.next reference. You also make sure to update the tail
reference.
Head back to the playground page and add the following to the
bottom:
34
Download from finelybook 7450911@qq.com
removeLast requires you to traverse all the way down the list. This
makes for an O(n) operation, which is relatively expensive.
remove(after:)
The final remove operation is removing a particular node at a
particular point in the list. This is achieved much like
insert(after:); You’ll first find the node immediately before the
node you wish to remove, and then unlink it.
@discardableResult
public mutating func remove(after node: Node<Value>) -> Value
defer {
35
Download from finelybook 7450911@qq.com
The unlinking of the nodes occurs in the defer block. Special care
needs to be taken if the removed node is the tail node, since the tail
reference will need to be updated.
Head back to the playground to try it out. You know the drill:
Try adding more elements and play around with the value of index.
Similar to insert(at:), the time complexity of this operation is O(1),
36
Download from finelybook 7450911@qq.com
Performance analysis
You’ve hit another checkpoint! To recap, you’ve implemented the
three operations that remove values from a linked list:
At this point, you’ve defined an interface for a linked list that most
programmers around the world can relate to. However, there’s work
to be done to adorn the Swift semantics. In the next half of the
chapter, you’ll focus on making the interface as Swifty as possible.
There’s more to be said for each of these. You’ll learn more about
each of them when you need to conform to them.
A linked list can earn two qualifications from the Swift collection
protocols. First, since a linked list is a chain of nodes, adopting the
Sequence protocol makes sense. Second, since the chain of nodes is a
finite sequence, it makes sense to adopt the Collection protocol.
38
Download from finelybook 7450911@qq.com
array[5]
39
Download from finelybook 7450911@qq.com
// 1
public var startIndex: Index {
return Index(node: head)
}
// 2
public var endIndex: Index {
return Index(node: tail?.next)
}
// 3
public func index(after i: Index) -> Index {
return Index(node: i.node?.next)
}
// 4
public subscript(position: Index) -> Value {
return position.node!.value
}
2. Collection defines the endIndex as the index right after the last
accessible value, so you give it tail?.next.
40
Download from finelybook 7450911@qq.com
print("List: \(list)")
print("First element: \(list[list.startIndex])")
print("Array containing first 3 elements: \(Array(list.prefix
print("Array containing last 3 elements: \(Array(list.suffix(
41
Download from finelybook 7450911@qq.com
print("array1: \(array1)")
print("array2: \(array2)")
Let’s check whether or not your linked list has value semantics. Write
the following at the bottom of the playground page:
42
Download from finelybook 7450911@qq.com
print("List1: \(list1)")
print("List2: \(list2)")
Unfortunately, your linked list does not have value semantics! This is
because your underlying storage uses a reference type (Node). This is a
serious problem, as LinkedList is a struct and therefore should use
value semantics. Implementing COW will fix this problem.
43
Download from finelybook 7450911@qq.com
oldNode = nextOldNode
}
tail = newNode
}
This method will replace the existing nodes of your linked list with
newly allocated ones with the same value.
Now find all other methods in LinkedList marked with the mutating
keyword and call copyNodes at the top of every method.
push
append
insert(after:)
pop
removeLast
remove(after:)
After you’ve completed the retrofits, the last example function call
should yield the following output:
44
Download from finelybook 7450911@qq.com
Optimizing COW
The O(n) overhead on every mutating call is unacceptable.
There are two avenues that help alleviate this problem. The first is to
avoid copying when the nodes only have one owner.
isKnownUniquelyReferenced
In the Swift Standard Library lives a function named
isKnownUniquelyReferenced. This function can be used to determine
whether or not an object has exactly one reference to it. Let’s test this
out in the linked list COW example.
In the last example function call, find the line where you wrote var
list2 = list and update that to the following:
45
Download from finelybook 7450911@qq.com
With this change, your linked list performance will reclaim its
previous performance with the benefits of COW.
Sharing nodes
The second optimization is a partial sharing of nodes. As it turns out,
there are certain scenarios where you can avoid a copy. A
comprehensive evaluation of all the scenarios is beyond the scope of
this book, but you’ll try to get an understanding of how this works.
Take a look at the following example (no need to write this down):
46
Download from finelybook 7450911@qq.com
list2.push(0)
list1.push(100)
47
Download from finelybook 7450911@qq.com
If you were to print the two lists now, you’ll get the following output:
The unidirectional nature of the linked list means that head first
insertions can ignore the “COW tax”!
48
Download from finelybook 7450911@qq.com
Stacks
Stacks are everywhere. Here are some common examples of things
you would stack:
pancakes
books
paper
cash
Stack
Stacks are useful, and also exceedingly simple. The main goal of
building a stack is to enforce how you access your data. If you had a
tough time with the linked list concepts, you'll bad glad to know that
stacks are comparatively trivial.
This means you can only add or remove elements from one side of the
data structure. In computer science, a stack is known as the LIFO (last
in first out) data structure. Elements that are pushed in last are the
first ones to be popped out.
49
Download from finelybook 7450911@qq.com
iOS uses the navigation stack to push and pop view controllers
into and out of view.
Implementation
Open up the starter playground for this chapter. In the Sources folder
of your playground, create a file named Stack.swift. Inside the file,
write the following:
public init() { }
}
50
Download from finelybook 7450911@qq.com
Choosing the right storage type for your stack is important. The array
is an obvious choice, since it offers constant time insertions and
deletions at one end via append and popLast. Usage of these two
operations will facilitate the LIFO nature of stacks.
@discardableResult
public mutating func pop() -> Element? {
return storage.popLast()
}
print(stack)
51
Download from finelybook 7450911@qq.com
Non-essential operations
There are a couple of nice-to-have operations that make a stack easier
to use. Head back into Stack.swift and add the following to Stack:
Less is more
You may have wondered if you could adopt the Swift collection
protocols for the stack. A stack's purpose is to limit the number of
ways to access your data, and adopting protocols such as Collection
would go against this goal by exposing all the elements via iterators
and the subscript. In this case, less is more!
52
Download from finelybook 7450911@qq.com
loop through the array elements and push each element. However,
since you can write an initializer that just sets the underlying private
storage. Add the following to your stack implementation:
This code creates a stack of strings and pops the top element "D".
Notice that the Swift compiler can type infer the element type from
the array so you can use Stack instead of the more verbose
Stack<String>.
You can go a step further and make your stack initializable from an
array literal. Add this to your stack implementation:
53
Download from finelybook 7450911@qq.com
This creates a stack of Doubles and pops the top value 4.0. Again type
inference saves you from having to type the more verbose
Stack<Double>.
54
Download from finelybook 7450911@qq.com
Queues
Lines are everywhere, whether you are lining up to buy tickets to your
favorite movie, or waiting for a printer machine to print out your
documents. These real-life scenarios mimic the queue data structure.
Common operations
Let’s establish a protocol for queues:
55
Download from finelybook 7450911@qq.com
Notice that the queue only cares about removal from the front, and
insertion at the back. You don’t really need to know what the
contents are in between. If you did, you would probably just use an
array.
Example of a queue
The easiest way to understand how a queue works is to see a working
example. Imagine a group of people waiting in line for a movie ticket.
The queue currently holds Ray, Brian, Sam, and Mic. Once Ray has
received his ticket, he moves out of the line. By calling dequeue(), Ray
is removed from the front of the queue.
Calling peek will return Brian since he is now at the front of the line.
Now comes Vicki, who just joined the line to buy a ticket. By calling
enqueue("Vicki"), Vicki gets added to the back of the queue.
Using an array
56
Download from finelybook 7450911@qq.com
Array-based implementation
The Swift Standard Library comes with core set highly-optimized,
primitive data structures you can use to build higher level
abstractions with. One of them is Array, a data structure that stores a
contiguous, ordered list of elements. In this section, you will use an
array to create a queue.
Here you’ve defined a generic QueueArray struct that adopts the Queue
protocol. Note that the associated type Element is inferred by the type
parameter T.
Leveraging arrays
Add the following code to QueueArray:
57
Download from finelybook 7450911@qq.com
Using the features of Array, you get the following for free:
Enqueue
Adding an element to the back of the queue is easy. Just append an
element to the array. Add the following:
In the example above, notice once you add Mic, the array has two
58
Download from finelybook 7450911@qq.com
In the example above, notice once you add Mic, the array has two
empty spaces.
Dequeue
Removing an item from the front requires a bit more work. Add the
following:
59
Download from finelybook 7450911@qq.com
Time to try out the queue you just implemented! Add the following to
the bottom of the page:
This code puts Ray, Brian and Eric in the queue, then removes Ray
and peeks at Brian but doesn’t remove him.
60
Download from finelybook 7450911@qq.com
61
Download from finelybook 7450911@qq.com
Enqueue
To add an element to the back of the queue simply add the following:
Behind the scenes, the doubly linked list will update its tail node’s
previous and next references to the new node. This is an O(1)
operation.
Dequeue
To remove an element from the queue, add the following:
62
Download from finelybook 7450911@qq.com
This code checks to see if the list is not empty and the first element of
the queue exists. If it doesn’t, it returns nil. Otherwise, it removes
and returns the element at the front of the queue.
63
Download from finelybook 7450911@qq.com
64
Download from finelybook 7450911@qq.com
65
Download from finelybook 7450911@qq.com
You first create a ring buffer, that has a fixed size of 4. The ring buffer
has two pointers that keep track of two things:
2. The write pointer keeps track of the next available slot so you can
override existing elements that have already been read.
Each time you add an item to the queue, the write pointer increments
by one. Let’s add a few more elements:
66
Download from finelybook 7450911@qq.com
Notice that the write pointer moved two more spots and is ahead of
the read pointer. This means that the queue is not empty.
Since the write pointer reached the end, it simply wraps around to the
starting index again.
67
Download from finelybook 7450911@qq.com
As final observation, notice that whenever the read and write pointers
are at the same index, that means the queue is empty.
Now that you have a better understanding of how ring buffers make a
queue, let’s implement one!
68
Download from finelybook 7450911@qq.com
Enqueue
Next add the method below:
Since the queue has a fixed size, you must now return true or false
to indicate whether the element has been successfully added.
enqueue(_:) is still an O(1) operation.
Dequeue
To remove an item from the front of the queue, add the following:
69
Download from finelybook 7450911@qq.com
This code checks if the queue is empty and if so, returns nil. If not, it
returns an item from the front of the buffer. Behind the scenes, the
ring buffer increments the read pointer by one.
That’s all there is to it! Test your ring buffer-based queue by adding
the following at the bottom of the page:
This test code works just like the previous examples dequeuing Ray
and peeking at Brian.
The ring buffer-based queue has the same time complexity for
enqueue and dequeue as the linked list implementation. The only
difference is the space complexity. The ring buffer has a fixed size
which means that enqueue can fail.
The idea behind using two stacks is simple. Whenever you enqueue an
element, it goes in the right stack. When you need to dequeue an
71
Download from finelybook 7450911@qq.com
element, you reverse the right stack and place it in the left stack so
you can retrieve the elements using FIFO order.
Leveraging arrays
Implement the common features of a queue, starting with the
following:
To check if the queue is empty, simply check that both the left and
right stack are empty. This means there are no elements left to
dequeue and no new elements have been enqueued.
You know that peeking looks at the top element. If the left stack is not
empty, the element on top of this stack is at the front of the queue. If
the left stack is empty, the right stack will be reversed and placed in
the left stack. In this case, the element at the bottom of the right
stack is next in the queue.
72
Download from finelybook 7450911@qq.com
Note that the two properties isEmpty and peek are still O(1)
operations.
Enqueue
Next add the method below:
Recall that the right stack is used to enqueue elements. You simply
push to the stack by appending to the array.
Dequeue
Removing an item from a two stack based implementation of a queue
is tricky. Add the following method:
73
Download from finelybook 7450911@qq.com
leftStack = rightStack.reversed() // 2
rightStack.removeAll() // 3
}
return leftStack.popLast() // 4
}
2. If the left stack is empty, set it as the reverse of the right stack.
Remember, you only transfer the elements in the right stack when the
left stack is empty!
74
Download from finelybook 7450911@qq.com
Here you simply combine the left stack with the reverse of the right
stack, and you print all the elements.
Just like all of the examples before, this code enqueues Ray, Brian and
Eric, dequeues Ray and then peeks at Brian.
75
Download from finelybook 7450911@qq.com
76
Download from finelybook 7450911@qq.com
77
Download from finelybook 7450911@qq.com
Trees
There are many types of trees, and they come in various shapes and
sizes. In this chapter, you will learn the basics of using and
implementing a tree.
Terminology
There are many terms associated with trees, so you will get
acquainted with a couple right off the bat.
Node
78
Download from finelybook 7450911@qq.com
Each node encapsulates some data and keeps track of its children.
Every node (except for the topmost one) is connected to exactly one
node above it. That node is called a parent node. The nodes directly
below and connected to it are called its child nodes. In a tree, every
child has exactly one parent. That’s what makes a tree, a tree.
Root
The topmost node in the tree is called the root of the tree. It is the
only node that has no parent:
79
Download from finelybook 7450911@qq.com
Leaf
A node is a leaf if it has no children:
You will run into more terms later on, but this should be enough to
get into the coding of trees.
Implementation
Open up the starter playground for this chapter to get started. A tree
is made up of nodes, so your first task is to create a TreeNode class.
80
Download from finelybook 7450911@qq.com
Each node is responsible for a value and holds references to all its
children using an array.
Time to give it a whirl. Head back to the playground page and write
the following:
beverages.add(hot)
beverages.add(cold)
}
81
Download from finelybook 7450911@qq.com
Traversal algorithms
Iterating through linear collections such as arrays or linked lists is
straightforward. Linear collections have a clear start and end:
82
Download from finelybook 7450911@qq.com
Should nodes on the left have precedence? How should the depth of a
node relate to its precedence? Your traversal strategy depends on the
problem you’re trying to solve. There are multiple strategies for
different trees and different problems.
Depth-first traversal
Write the following at the bottom of TreeNode.swift:
extension TreeNode {
public func forEachDepthFirst(visit: (TreeNode) -> Void) {
visit(self)
children.forEach {
$0.forEachDepthFirst(visit: visit)
}
}
}
83
Download from finelybook 7450911@qq.com
This simple code uses recursion process the next node. (You could use
your own stack if you didn't want your implementation to be
recurrsive.) Time to test it out.
tree.add(hot)
tree.add(cold)
hot.add(tea)
hot.add(coffee)
hot.add(chocolate)
cold.add(soda)
cold.add(milk)
tea.add(blackTea)
tea.add(greenTea)
tea.add(chaiTea)
soda.add(gingerAle)
soda.add(bitterLemon)
84
Download from finelybook 7450911@qq.com
return tree
}
85
Download from finelybook 7450911@qq.com
ginger ale
bitter lemon
milk
Level-order traversal
Write the following at the bottom of TreeNode.swift:
extension TreeNode {
public func forEachLevelOrder(visit: (TreeNode) -> Void) {
visit(self)
var queue = Queue<TreeNode>()
children.forEach { queue.enqueue($0) }
while let node = queue.dequeue() {
visit(node)
node.children.forEach { queue.enqueue($0) }
}
}
}
86
Download from finelybook 7450911@qq.com
Note how you used a queue (not a stack) to make sure the nodes are
visited in the right level-order. A simple recursion (which implicitly
uses a stack) would not have worked!
Search
You already have a method that iterates through all the nodes, so
building a search algorithm shouldn’t take long. Write the following
at the bottom of TreeNode.swift:
87
Download from finelybook 7450911@qq.com
result = node
}
}
return result
}
}
Head back to the playground page to test your code. To save some
time, simply copy the previous example and modify it to test the
search method:
Here you used your level-order traversal algorithm. Since it visits all
of the nodes, if there are multiple matches, the last match will win.
This means that you will get different objects back depending on what
traversal you use.
88
Download from finelybook 7450911@qq.com
89
Download from finelybook 7450911@qq.com
Binary Trees
In the previous chapter, you looked at a basic tree where each node
can have many children. A binary tree is a tree where each node has at
most two children, often referred to as the left and right children:
Binary trees serve as the basis for many tree structures and
algorithms. In this chapter, you’ll build a binary tree and learn about
the three most important tree traversal algorithms.
Implementation
Open the starter project for this chapter. Create a new file and name it
BinaryNode.swift. Add the following inside this file:
90
Download from finelybook 7450911@qq.com
seven.leftChild = one
one.leftChild = zero
one.rightChild = five
seven.rightChild = nine
nine.leftChild = eight
return seven
}
Building a diagram
Building a mental model of a data structure can be quite helpful in
learning how it works. To that end, you’ll implement a reusable
algorithm that helps visualize a binary tree in the console.
91
Download from finelybook 7450911@qq.com
92
Download from finelybook 7450911@qq.com
You’ll be using this diagram for other binary trees in this book.
Traversal algorithms
Previously, you looked at a level-order traversal of a tree. With a few
tweaks, you can make this algorithm work for binary trees as well.
However, instead of re-implementing level-order traversal, you’ll look
at three traversal algorithms for binary trees: in-order, pre-order, and
post-order traversals.
In-order traversal
In-order traversal visits the nodes of a binary tree in the following
order, starting from the root node:
If the current node has a left child, recursively visit this child
first.
If the current node has a right child, recursively visit this child.
Here’s what an in-order traversal looks like for your example tree:
93
Download from finelybook 7450911@qq.com
You may have noticed that this prints the example tree in ascending
order. If the tree nodes are structured in a certain way, in-order
traversal visits them in ascending order! You’ll learn more about
binary search trees in the next chapter.
extension BinaryNode {
Following the rules laid out above, you first traverse to the leftmost
node before visiting the value. You then traverse to the rightmost
node.
Head back to the playground page to test this out. Add the following
at the bottom of the page:
94
Download from finelybook 7450911@qq.com
Pre-order traversal
Pre-order traversal always visits the current node first, then
recursively visits the left and right child:
95
Download from finelybook 7450911@qq.com
Post-order traversal
Post-order traversal only visits the current node after the left and
right child have been visited recursively.
In other words, given any node, you’ll visit its children before visiting
itself. An interesting consequence of this is that the root node is
96
Download from finelybook 7450911@qq.com
97
Download from finelybook 7450911@qq.com
The value of a left child must be less than the value of its parent.
Picking a side forfeits all the possibilities of the other side. Binary
search trees use this property to save you from performing
unnecessary checking.
98
Download from finelybook 7450911@qq.com
Lookup
There’s only one way to do element lookups for an unsorted array.
You need to check every element in the array from the start:
99
Download from finelybook 7450911@qq.com
Every time the search algorithm visits a node in the BST, it can safely
make these two assumptions:
If the search value is less than the current value, it must be in the
left subtree.
If the search value value is greater than the current value, it must
be in the right subtree.
By leveraging the rules of the BST, you can avoid unnecessary checks
and cut the search space in half every time you make a decision.
That’s why element lookup in a BST is an O(log n) operation.
Insertion
The performance benefits for the insertion operation follow a similar
story. Assume you want to insert 0 into a collection:
100
Download from finelybook 7450911@qq.com
In the above example, zero is inserted in front of the array, causing all
other elements to shift backwards by one position. Inserting into an
array has a time complexity of O(n).
By leveraging the rules for the BST, you only needed to make three
traversals to find the location for the insertion, and you didn’t have to
shuffle all the elements around! Inserting elements in a BST is again
an O(log n) operation.
Removal
101
Download from finelybook 7450911@qq.com
This behavior also plays nicely with the lineup analogy. If you leave
the middle of the line, everyone behind you needs to shuffle forward
to take up the empty space.
Nice and easy! There are complications to manage when the node
you’re removing has children, but you’ll look into that later. Even
102
Download from finelybook 7450911@qq.com
Binary search trees drastically reduce the number of steps for add,
remove, and lookup operations. Now that you have a grasp of the
benefits of using a binary search tree, you can move on to the actual
implementation.
Implementation
Open up the starter project for this chapter. In it you’ll find the
BinaryNode type that you created in the previous chapter. Create a
new file named BinarySearchTree.swift and add the following inside
the file:
public init() {}
}
By definition, binary search trees can only hold values that are
Comparable.
Inserting elements
In accordance with the rules of the BST, nodes of the left child must
103
Download from finelybook 7450911@qq.com
In accordance with the rules of the BST, nodes of the left child must
contain values less than the current node. Nodes of the right child
must contain values greater than or equal to the current node. You’ll
implement the insert method while respecting these rules.
extension BinarySearchTree {
The first insert method is exposed to users, while the second one will
be used as a private helper method:
1. This if statement controls which way the next insert call should
traverse. If the new value is less than the current value, you call
104
Download from finelybook 7450911@qq.com
insert on the left child. If the new value is greater than or equal
to the current value, you’ll call insert on the right child.
Head back to the playground page and add the following at the
bottom:
That tree looks a bit unbalanced, but it does follow the rules.
However, this tree layout has undesirable consequences. When
working with trees, you always want to achieve a balanced format:
105
Download from finelybook 7450911@qq.com
106
Download from finelybook 7450911@qq.com
Much nicer!
Finding elements
Finding an element in a BST requires you to traverse through its
nodes. It’s possible to come up with a relatively simple
implementation by using the existing traversal mechanisms you
learned about in the previous chapter.
extension BinarySearchTree {
107
Download from finelybook 7450911@qq.com
Optimizing contains
You can rely on the rules of the BST to avoid needless comparisons.
Back in BinarySearchTree.swift, update the contains method to the
108
Download from finelybook 7450911@qq.com
following:
Removing elements
Removing elements is a little more tricky, as there are a few different
scenarios you need to handle.
109
Download from finelybook 7450911@qq.com
110
Download from finelybook 7450911@qq.com
111
Download from finelybook 7450911@qq.com
You have two child nodes (12 and 37) to reconnect, but the parent
node only has space for one child. To solve this problem, you’ll
implement a clever workaround by performing a swap.
When removing a node with two children, replace the node you
removed with smallest node in its right subtree. Based on the rules of
the BST, this is the leftmost node of the right subtree:
112
Download from finelybook 7450911@qq.com
It’s important to note that this produces a valid binary search tree.
Because the new node was the smallest node in the right subtree, all
nodes in the right subtree will still be greater than or equal to the new
node. And because the new node came from the right subtree, all
nodes in the left subtree will be less than the new node.
After performing the swap, you can simply remove the value you
copied, which is just a leaf node.
113
Download from finelybook 7450911@qq.com
Implementation
Open up BinarySearchTree.swift to implementing remove. Add the
following code at the bottom of the file:
extension BinarySearchTree {
114
Download from finelybook 7450911@qq.com
-> BinaryNode<Element>? {
guard let node = node else {
return nil
}
if value == node.value {
// more to come
} else if value < node.value {
node.leftChild = remove(node: node.leftChild, value: value)
} else {
node.rightChild = remove(node: node.rightChild, value: value)
}
return node
}
}
This should look familiar to you. You’re using the same recursive
setup with a private helper method as you did for insert. You’ve also
added a recursive min property to BinaryNode to find the minimum
node in a subtree.
// 1
if node.leftChild == nil && node.rightChild == nil {
return nil
}
// 2
if node.leftChild == nil {
return node.rightChild
}
// 3
if node.rightChild == nil {
return node.leftChild
}
// 4
node.value = node.rightChild!.min.value
node.rightChild = remove(node: node.rightChild, value: node.value)
1. In the case where the node is a leaf node, you simply return nil,
115
Download from finelybook 7450911@qq.com
1. In the case where the node is a leaf node, you simply return nil,
thereby removing the current node.
4. This is the case where the node to be removed has both a left and
right child. You replace the node’s value with the smallest value
from the right subtree. You then call remove on the right child to
remove this swapped value.
Head back to the playground page and test remove by writing the
following:
116
Download from finelybook 7450911@qq.com
┌──5
4
│ ┌──2
└──1
└──0
117
Download from finelybook 7450911@qq.com
AVL Trees
In the previous chapter, you learned about the O(log n) performance
characteristics of the binary search tree. However, you also learned
that unbalanced trees can deteriorate the performance of the tree, all
the way down to O(n). In 1962, Georgy Adelson-Velsky and Evgenii
Landis came up with the first self-balancing binary search tree: the
AVL Tree.
Understanding balance
A balanced tree is the key to optimizing the performance of the binary
search tree. In this section, you’ll learn about the three main states of
balance.
Perfect balance
The ideal form of a binary search tree is the perfectly balanced state.
In technical terms, this means every level of the tree is filled with
nodes, from top to bottom.
Not only is the tree perfectly symmetrical, the nodes at the bottom
level are completely filled. This is the requirement for being perfectly
balanced.
"Good-enough" balance
118
Download from finelybook 7450911@qq.com
The definition of a balanced tree is that every level of the tree must be
filled, except for the bottom level. In most cases of binary trees, this is
the best you can do.
Unbalanced
Finally, there’s the unbalanced state. Binary search trees in this state
suffer from various levels of performance loss, depending on the
degree of imbalance.
Keeping the tree balanced gives the find, insert and remove
operations an O(log n) time complexity. AVL trees maintain balance
by adjusting the structure of the tree when the tree becomes
119
Download from finelybook 7450911@qq.com
unbalanced. You’ll learn how this works as you progress through the
chapter.
Implementation
Inside the starter project for this chapter is an implementation of the
binary search tree as created in the previous chapter. The only
difference is that all references to the binary search tree have been
renamed to AVL tree.
Binary search trees and AVL trees share much of the same
implementation; In fact, all that you’ll be adding is the balancing
component. Open up the starter project to begin.
Measuring balance
To keep a binary tree balanced, you’ll need a way to measure the
balance of the tree. The AVL tree achieves this with a height property
in each node. In tree-speak, the height of a node is the longest
distance from the current node to a leaf node:
Open the starter playground for this chapter and add the following
120
Download from finelybook 7450911@qq.com
Open the starter playground for this chapter and add the following
property to AVLNode in the compiled sources folder:
The height of the left and right children of each node must differ by at
most 1. This is known as the balance factor.
121
Download from finelybook 7450911@qq.com
This is a balanced tree, where all levels except the bottom one are
filled. The blue numbers represent the height of each node, while the
green numbers represent the balanceFactor.
Inserting 40 into the tree turns it into an unbalanced tree. Notice how
the balanceFactor changes. A balanceFactor of 2 or -2 is an
indication of an unbalanced tree.
122
Download from finelybook 7450911@qq.com
Although more than one node may have a bad balancing factor, you
only need to perform the balancing procedure on the bottom-most
node containing the invalid balance factor: the node containing 25.
Rotations
The procedures used to balance a binary search tree are known as
rotations. There are four rotations in total, for the four different ways
that a tree can become unbalanced. These are known as left rotation,
left-right rotation, right rotation, and right-left rotation.
Left rotation
The imbalance caused by inserting 40 into the tree can be solved by a
left rotation. A generic left rotation of node x looks like this:
Before going into specifics, there are two takeaways from this before
and after comparison:
123
Download from finelybook 7450911@qq.com
1. The right child is chosen as the pivot. This node will replace the
rotated node as the root of the subtree (it will move up a level).
1. The node to be rotated will become the left child of the pivot (it
moves down a level). This means the current left child of the
pivot must be moved elsewhere.
3. You update the heights of the rotated node and the pivot.
4. Finally, you return the pivot so it can replace the rotated node in
the tree.
Here are the before and after effects of the left rotation of 25 from the
124
Download from finelybook 7450911@qq.com
Here are the before and after effects of the left rotation of 25 from the
previous example:
Right rotation
Right rotation is the symmetrical opposite of left rotation. When a
series of left children is causing an imbalance, it’s time for a right
rotation. A generic right rotation of node x looks like this:
125
Download from finelybook 7450911@qq.com
Right-left rotation
You may have noticed that the left and right rotations balance nodes
that are all left children or all right children. Consider the case where
36 is inserted into the original example tree.
126
Download from finelybook 7450911@qq.com
Doing a left rotation in this case won’t result in a balanced tree. The
way to handle cases like this is to perform a right rotation on the right
child before doing the left rotation. Here’s what the procedure looks
like:
2. Now that nodes 25, 36, and 37 are all right children, you can
apply a left rotation to balance the tree.
127
Download from finelybook 7450911@qq.com
Don't worry just yet about when this is called. You'll get to that in a
second. You first need to handle the final case, left-right rotation.
Left-right rotation
Left-right rotation is the symmetrical opposite of the right-left
rotation. Here’s an example:
2. Now that nodes 25, 15, and 10 are all left children, you can apply
a right rotation to balance the tree.
128
Download from finelybook 7450911@qq.com
node.leftChild = leftRotate(leftChild)
return rightRotate(node)
}
That’s it for rotations. Next, you’ll figure out when to apply these
rotations at the correct location.
Balance
The next task is to design a method that uses balanceFactor to decide
whether a node requires balancing or not. Write the following method
below leftRightRotate:
Revisiting insertion
You’ve already done the majority of the work. The remainder is fairly
130
Download from finelybook 7450911@qq.com
You’ve already done the majority of the work. The remainder is fairly
straightforward. Update insert(from:value:) to the following:
Instead of returning the node directly after inserting, you pass it into
balanced. This ensures every node in the call stack is checked for
balancing issues. You also update the node’s height.
That’s all there is to it! Head into the playground page and test it out.
Add the following to the playground:
131
Download from finelybook 7450911@qq.com
│ └──9
│ └──8
7
│ ┌──6
│ ┌──5
│ │ └──4
└──3
│ ┌──2
└──1
└──0
Revisiting remove
Retrofitting the remove operation for self-balancing is just as easy as
fixing insert. In AVLTree, find remove and replace the final return
statement with the following:
Head back to the playground page and add the following code at the
bottom of the file:
132
Download from finelybook 7450911@qq.com
┌──18
16
└──15
Removing 10 caused a left rotation on 15. Feel free to try out a few
more test cases of your own.
133
Download from finelybook 7450911@qq.com
Tries
The trie (pronounced as try) is a tree that specializes in storing data
that can be represented as a collection, such as English words:
Example
You are given a collection of strings. How would you build a
component that handles prefix matching? Here’s one way:
class EnglishDictionary {
134
Download from finelybook 7450911@qq.com
You form a word by tracing the collection of characters from the root
to a node with a special indicator — a terminator — represented by a
black dot. An interesting characteristic of the trie is that multiple
words can share the same characters.
135
Download from finelybook 7450911@qq.com
First you travel to the node containing C. That quickly excludes other
branches of the trie from the search operation:
Next, you need to find the words that have the next letter U. You
traverse to the U node:
Since that’s the end of your prefix, the trie would return all
collections formed by the chain of nodes from the U node. In this case,
the words CUT and CUTE would be returned. Imagine if this trie
136
Download from finelybook 7450911@qq.com
Implementation
As always, open up the starter playground for this chapter.
TrieNode
You’ll begin by creating the node for the trie. In the Sources directory,
create a new file named TrieNode.swift. Add the following to the file:
// 1
public var key: Key?
// 2
public weak var parent: TrieNode?
// 3
public var children: [Key: TrieNode] = [:]
// 4
public var isTerminating = false
137
Download from finelybook 7450911@qq.com
1. key holds the data for the node. This is optional because the root
node of the trie has no key.
3. In binary search trees, nodes have a left and right child. In a trie,
a node needs to hold multiple different elements. You’ve
declared a children dictionary to help with that.
Trie
Next, you’ll create the trie itself, which will manage the nodes. In the
Sources folder, create a new file named Trie.swift. Add the following
to the file:
public init() {}
}
138
Download from finelybook 7450911@qq.com
The Trie class is built for all types that adopt the Collection
protocol, including String. In addition to this requirement, each
element inside the collection must be Hashable. This is required
because you’ll be using the collection’s elements as keys for the
children dictionary in TrieNode.
Next, you’ll implement four operations for the trie: insert, contains,
remove and a prefix match.
Insert
Tries work with any type that conforms to Collection. The trie will
take the collection and represent it as a series of nodes, where each
node maps to an element in the collection.
// 2
for element in collection {
if current.children[element] == nil {
current.children[element] = Node(key: element, parent: current)
}
current = current.children[element]!
}
// 3
current.isTerminating = true
}
The time complexity for this algorithm is O(k), where k is the number
of elements in the collection you’re trying to insert. This is because
you need to traverse through or create each node that represents each
element of the new collection.
Contains
contains is very similar to insert. Add the following method to Trie:
Here you traverse the trie in a way similar to insert. You check every
element of the collection to see if it’s in the tree. When you reach the
last element of the collection, it must be a terminating element. If
not, the collection was not added to the tree and what you’ve found is
merely a subset of a larger collection.
To test out insert and contains, navigate to the playground page and
add the following code:
Remove
Removing a node in the trie is a bit more tricky. You need to be
particularly careful when removing each node, since nodes can be
shared between two different collections. Write the following method
just below contains:
141
Download from finelybook 7450911@qq.com
return
}
// 2
current.isTerminating = false
// 3
while let parent = current.parent,
current.children.isEmpty && !current.isTerminating {
parent.children[current.key!] = nil
current = parent
}
}
Taking it comment-by-comment:
3. This is the tricky part. Since nodes can be shared, you don’t want
to carelessly remove elements that belong to another collection.
If there are no other children in the current node, it means that
other collections do not depend on the current node.
Head back to the playground page and add the following to the
bottom:
142
Download from finelybook 7450911@qq.com
example(of: "remove") {
let trie = Trie<String>()
trie.insert("cut")
trie.insert("cute")
Prefix matching
The most iconic algorithm for the trie is the prefix matching
algorithm. Write the following at the bottom of Trie.swift:
Your prefix matching algorithm will sit inside this extension, where
143
Download from finelybook 7450911@qq.com
Your prefix matching algorithm will sit inside this extension, where
CollectionType is constrained to RangeReplaceableCollection. This
is required because the algorithm will need access to the append
method of RangeReplaceableCollection types.
// 2
return collections(startingWith: prefix, after: current)
}
1. You start by verifying that the trie contains the prefix. If not, you
return an empty array.
2. After you’ve found the node that marks the end of the prefix, you
call a recursive helper method
collections(startingWith:after:) to find all the sequences
after the current node.
if node.isTerminating {
results.append(prefix)
}
144
Download from finelybook 7450911@qq.com
// 2
for child in node.children.values {
var prefix = prefix
prefix.append(child.key!)
results.append(contentsOf: collections(startingWith: prefix
after: child))
}
return results
}
2. Next, you need to check the current node’s children. For every
child node, you recursively call
collections(startingWith:after:) to seek out other
terminating nodes.
Time to take the method for a spin. Navigate back to the playground
page and add the following:
145
Download from finelybook 7450911@qq.com
trie.insert("cared")
trie.insert("cars")
trie.insert("carbs")
trie.insert("carapace")
trie.insert("cargo")
146
Download from finelybook 7450911@qq.com
Binary Search
Binary search is one of the most efficient searching algorithms with a
time complexity of O(log n). This is comparable with searching for an
element inside a balanced binary search tree.
There are two conditions that need to be met before binary search
may be used:
Example
The benefits of binary search are best illustrated by comparing it with
linear search. Swift’s Array type uses linear search to implement its
index(of:) method. This means it traverses through the whole
collection, or until it finds the element:
147
Download from finelybook 7450911@qq.com
Instead of eight steps to find 31, it only takes three. Here’s how it
works:
148
Download from finelybook 7450911@qq.com
In the example where you’re looking for the value 31 (which is greater
than the middle element 22), you apply binary search on the right
subsequence:
You continue these three steps until you can no longer split up the
collection into left and right halves, or until you find the value inside
the collection.
Implementation
Open the starter playground for this chapter. Create a new file in the
Sources folder named BinarySearch.swift. Add the following to the
file:
// 1
public extension RandomAccessCollection where Element: Comparable
// 2
func binarySearch(for value: Element, in range: Range<Index>? =
-> Index? {
// more to come
}
}
149
Download from finelybook 7450911@qq.com
// 1
let range = range ?? startIndex..<endIndex
// 2
guard range.lowerBound < range.upperBound else {
return nil
}
// 3
let size = distance(from: range.lowerBound, to: range.upperBound)
let middle = index(range.lowerBound, offsetBy: size / 2)
// 4
if self[middle] == value {
return middle
// 5
} else if self[middle] > value {
return binarySearch(for: value, in: range.lowerBound..<middle)
} else {
return binarySearch(for: value, in: index(after: middle)..<range.up
}
1. First you check if range was nil. If so, you create a range that
covers the entire collection.
3. Now that you’re sure you have elements in the range, you find
the middle index in the range.
150
Download from finelybook 7450911@qq.com
4. You then compare the value at this index with the value you’re
searching for. If they match, you return the middle index.
5. If not, you recursively search either the left or right half of the
collection.
let array = [1, 5, 15, 17, 19, 22, 24, 31, 105, 150]
index(of:): Optional(7)
binarySearch(for:): Optional(7)
151
Download from finelybook 7450911@qq.com
Have you seen the movie Toy Story, with the claw machine and the
squeaky little green aliens? Imagine that the claw machine is
operating on your heap structure, and will always pick the minimum
or maximum value, depending on the type of heap.
In a min heap, parent nodes must always contain a value that is less
than or equal to the value in its children. The root node will always
contain the lowest value.
153
Download from finelybook 7450911@qq.com
Heap applications
Some useful applications of a heap include:
Heap Sort
Note: You will learn about Priority Queues in Chapter 13, Heap
Sort in Chapter 17 and Dijkstra’s and Prim’s algorithms in
Chapter 22 and 23.
154
Download from finelybook 7450911@qq.com
Note: Don’t confuse these heaps with memory heaps. The term
heap is sometimes confusingly used in computer science to refer
to a pool of memory. Memory heaps are a different concept and
not what you are studying here.
In this chapter, you will focus on creating a heap, where you’ll see
how convenient it is to fetch the minimum and maximum element of
a collection.
This type contains an array to hold the elements in the heap and a
sort function that defines how the heap should be ordered. By passing
an appropriate function in the initializer, this type can be used to
create both min and max heaps.
155
Download from finelybook 7450911@qq.com
indeed binary trees, but they can be represented with a simple array.
This seems like an unusual way to build a tree. But one of the benefits
of this heap implementation is efficient time and space complexity, as
the elements in the heap are all stored together in memory.
You will see later on that swapping elements will play a big part in
heap operations. This is also easier to do with an array than with a
binary tree data structure.
Let’s take a look at how heaps can be represented using an array. Take
the following binary heap:
156
Download from finelybook 7450911@qq.com
As you go up a level, you’ll have twice as many nodes than in the level
before.
It’s now easy to access any node in the heap. You can compare this to
how you’d access elements in an array: Instead of traversing down the
left or right branch, you can simply access the node in your array
using simple formulas.
157
Download from finelybook 7450911@qq.com
You might want to obtain the parent of a node. You can solve for i in
this case. Given a child node at index i, this child’s parent node can
be found at index floor( (i - 1) / 2).
Note: Traversing down an actual tree to get the left and right
child of a node is a O(log n) operation. In an random access data
structure such as an array, that same operation is just O(1).
158
Download from finelybook 7450911@qq.com
Now that you have a good understanding of how you can represent a
heap using an array, you’ll look at some important operations of a
heap.
159
Download from finelybook 7450911@qq.com
A remove operation will remove the maximum value at the root node.
To do so, you must first swap the root node with the last element in
the heap.
Once you’ve swapped the two elements, you can remove the last
element and store its value so you can later return it.
Now you must check the max heap’s integrity. But first, ask yourself,
“Is it still a max heap?”
Remember: The rule for a max heap is that the value of every parent
node must be larger than, or equal to, the values of its children. Since
160
Download from finelybook 7450911@qq.com
the heap no longer follows this rule, you must perform a sift down.
To perform a sift down, you start from the current value 3 and check
its left and right child. If one of the children has a value that is greater
than the current value, you swap it with the parent. If both children
have a greater value, you swap the parent with the child having the
greater value.
Now you have to continue to sift down until the node’s value is not
larger than the values of its children.
161
Download from finelybook 7450911@qq.com
Once you reach the end, you’re done, and the max heap’s property
has been restored!
Implementation of remove
Add the following method to Heap:
4. The heap may not be a max or min heap anymore, so you must
perform a sift down to make sure it conforms to the rules.
Now to see how to sift down nodes. Add the following method after
remove():
162
Download from finelybook 7450911@qq.com
5. If there is a left child, and it has a higher priority than its parent,
make it the candidate.
163
Download from finelybook 7450911@qq.com
Now that you know how to remove from the top of the heap, how do
you add to a heap?
164
Download from finelybook 7450911@qq.com
Now you must check the max heap’s property. Instead of sifting
down, you must now sift up since the node you just inserted might
have a higher priority than its parents. This sifting up works much
like sifting down, by comparing the current node with its parent and
swapping them if needed.
165
Download from finelybook 7450911@qq.com
Implementation of insert
Add the following method to Heap:
166
Download from finelybook 7450911@qq.com
insert appends the element to the array and then performs a sift
up.
siftUp swaps the current node with its parent, as long as that
node has a higher priority than its parent.
You have so far looked at removing the root element from a heap, and
inserting into a heap. But what if you wanted to remove any arbitrary
element from the heap?
167
Download from finelybook 7450911@qq.com
} else {
elements.swapAt(index, elements.count - 1) // 3
defer {
siftDown(from: index) // 5
siftUp(from: index)
}
return elements.removeLast() // 4
}
}
To remove any element from the heap, you need an index. Let’s go
over how this works:
1. Check to see if the index is within the bounds of the array. If not,
return nil.
2. If you’re removing the last element in the heap, you don’t need
to do anything special. Simply remove and return the element.
1. If you’re not removing the last element, first swap the element
with the last element.
But — why do you have to perform both a sift down and a sift up?
168
Download from finelybook 7450911@qq.com
Assume you are trying to remove 5. You swap 5 with the last element,
which is 8. You now need to perform a sift up to satisfy the max heap
property.
Now assume you are trying to remove 7. You swap 7 gets swapped
with the last element, 1. You now need to perform a sift down to
satisfy the max heap property.
169
Download from finelybook 7450911@qq.com
170
Download from finelybook 7450911@qq.com
2. Check to see if the element you are looking for has higher priority
than the current element at index i. If it does, the element you
are looking for cannot possibly be lower in the heap.
4. Recursively search for the element starting from the left child of
i.
5. Recursively search for the element starting from the right child of
i.
Building a heap
You now have all the necessary tools to represent a heap. To wrap up
this chapter, you’ll build a heap from an existing array of elements
and test it out.
if !elements.isEmpty {
for i in stride(from: elements.count / 2 - 1, through: 0
siftDown(from: i)
}
171
Download from finelybook 7450911@qq.com
}
}
Testing
Time to try it out. Add the following to your playground:
172
Download from finelybook 7450911@qq.com
while !heap.isEmpty {
print(heap.remove()!)
}
This creates a max heap (because > is used as the sorting function)
and removes elements one-by-one until it is empty. Notice that the
elements are removed largest to smallest and the following numbers
are printed to the console.
12
8
7
6
4
3
1
1
173
Download from finelybook 7450911@qq.com
The heap data structure is good for maintaining the highest or lowest
value, depending on the type of heap. You also learned how to
validate the heap by sifting elements up and down to satisfy the max
or min heap property.
In the next few chapters, you will see other uses for heaps such as
building priority queues and sorting a collection of objects.
174
Download from finelybook 7450911@qq.com
Priority Queue
Queues are simply lists that maintain the order of elements using
first-in-first-out (FIFO) ordering. A priority queue is another version
of a queue that, instead of using FIFO ordering, dequeues elements in
priority order. For example, a priority queue can either be:
Applications
Some useful applications of a priority queue include:
175
Download from finelybook 7450911@qq.com
These are just some of the use cases, but priority queues have many
more applications as well.
Common operations
In Chapter 5, Queues, you established the following protocol for
queues:
The priority queue will conform to the Queue protocol and implement
the common operations:
176
Download from finelybook 7450911@qq.com
Implementation
You can create a priority queue in the following ways:
Next you will look at how to use a heap to create a priority queue.
To conform to the Queue protocol, add the following right after the
init(sort:elements:) initializer:
178
Download from finelybook 7450911@qq.com
return heap.peek()
}
The heap is a perfect candidate for a priority queue. You simply need
to call various methods of a heap to implement the operations of a
priority queue!
Testing
Add the following to your playground:
179
Download from finelybook 7450911@qq.com
12
8
7
6
4
3
1
1
Not only did you learn how to implement a priority queue, you
learned to apply the heap data structure and conformed to the queue
protocol. Now that’s composition!
180
Download from finelybook 7450911@qq.com
Bubble sort
Selection sort
Insertion sort
Bubble sort
One of the simplest sorts is the bubble sort, which repeatedly
compares adjacent values and swaps them, if needed, to perform the
sort. The larger values in the set will therefore "bubble up" to the end
of the collection.
Example
Consider the following hand of cards:
181
Download from finelybook 7450911@qq.com
Subsequent passes through the collection will do the same for 9 and 4
respectively:
182
Download from finelybook 7450911@qq.com
The sort is only complete when you can perform a full pass over the
collection without having to swap any values. At worst, this will
require n-1 passes, where n is the count of members in the collection.
Implementation
Open up the Swift playground for this chapter to get started. In the
Sources directory of your playground, create a new file named
BubbleSort.swift. Write the following inside the file:
183
Download from finelybook 7450911@qq.com
array.swapAt(current, current + 1)
swapped = true
}
}
// 4
if !swapped {
return
}
}
}
2. A single pass bubble the largest value to the end of the collection.
Every pass needs to compare one less value than in the previous
pass, so you essentially shorten the array by one with each pass.
Try it out! Head back into the main playground page and write the
following:
184
Download from finelybook 7450911@qq.com
Bubble sort has a best time complexity of O(n) if it's already sorted,
and a worst and average time complexity of O(n²), making it one of
the least appealing sorts in the known universe.
Selection sort
Selection sort follows the basic idea of bubble sort, but improves upon
this algorithm by reducing the number of swapAt operations.
Selection sort will only swap at the end of each pass. You'll see how
that works in the following implementation.
Example
Assume you have the following hand of cards:
During each pass, selection sort will find the lowest unsorted value
and swap it into place:
185
Download from finelybook 7450911@qq.com
Implementation
In the Sources directory of your playground, create a new file named
SelectionSort.swift. Write the following inside the file:
186
Download from finelybook 7450911@qq.com
}
}
1. You perform a pass for every element in the collection, except for
the last one. There is no need to include the last element, since if
all other elements are in their correct order, the last one will be
as well.
Try it out! Head back to the main playground page and add the
following:
Just like bubble sort, selection sort has a best, worst, and average time
complexity of O(n²), which is fairly dismal. It's a simple to
understand, though, and it does perform better than bubble sort!
Insertion sort
187
Download from finelybook 7450911@qq.com
Example
The idea of insertion sort is similar to how you'd sort a hand of cards.
Consider the following hand:
Insertion sort will iterate once through the cards, from left to right.
Each card is shifted to the left until it reaches its correct position.
188
Download from finelybook 7450911@qq.com
1. You can ignore the first card, as there are no previous cards to
compare it with.
It's worth pointing out that the best case scenario for insertion sort
occurs when the sequence of values are already in sorted order, and
no left shifting is necessary.
189
Download from finelybook 7450911@qq.com
Implementation
In the Sources directory of your playground, create a new file named
InsertionSort.swift. Write the following inside the file:
1. Insertion sort requires you to iterate from left to right once. This
loop does that.
2. Here, you run backwards from the current index so you can shift
left as needed.
Head back to the main playground page and write the following at the
bottom:
190
Download from finelybook 7450911@qq.com
Generalization
In this section, you'll generalize these sorting algorithms for
collection types other than Array. Exactly which collection types,
though, depends on the algorithm:
Bubble sort and selection sort really only traverse the collection
front to back, so they can handle any Collection.
The algorithm stays the same; you simply update the loop to use the
collection's indices. Head back to the main playground page to verify
that bubble sort still works the way it should.
192
Download from finelybook 7450911@qq.com
lowest = other
}
other = collection.index(after: other)
}
if lowest != current {
collection.swapAt(lowest, current)
}
}
}
193
Download from finelybook 7450911@qq.com
194
Download from finelybook 7450911@qq.com
Merge Sort
Merge sort is one of the most efficient sorting algorithms. With a time
complexity of O(log n), it’s one of the fastest of all general-purpose
sorting algorithms. The idea behind merge sort is divide and conquer;
to break up a big problem into several smaller, easier to solve
problems and then combine those solutions into a final result. The
merge sort mantra is to split first and merge after.
1. First, split the pile in half. You now have two unsorted piles:
1. Now keep splitting the resulting piles until you can’t split
anymore. In the end, you will have one (sorted!) card in each pile:
195
Download from finelybook 7450911@qq.com
196
Download from finelybook 7450911@qq.com
Implementation
Open up the starter playground to get started.
Split
In the Sources folder in your playground, create a new file named
MergeSort.swift. Write the following inside the file:
Here you split the array into halves. Splitting once isn’t enough,
however; you have to keep splitting recursively until you can’t split
any more, which is when each subdivision contains just one element.=
197
Download from finelybook 7450911@qq.com
// 2
let left = mergeSort(Array(array[..<middle]))
let right = mergeSort(Array(array[middle...]))
// ... more to come
}
2. You’re now calling mergeSort on the left and right halves of the
original array. As soon as you’ve split the array in half, you’ll try
to split again.
There’s still more work to do before your code will compile. Now that
you’ve accomplished the splitting part, it’s time to focus on merging.
Merge
Your final step is to merge the left and right arrays together. To
keep things clean, you will create a separate merge function for this.
198
Download from finelybook 7450911@qq.com
// 4
if leftElement < rightElement {
result.append(leftElement)
leftIndex += 1
} else if leftElement > rightElement {
result.append(rightElement)
rightIndex += 1
} else {
result.append(leftElement)
leftIndex += 1
result.append(rightElement)
rightIndex += 1
}
}
// 5
if leftIndex < left.count {
result.append(contentsOf: left[leftIndex...])
}
if rightIndex < right.count {
result.append(contentsOf: right[rightIndex...])
}
return result
}
2. The smaller of the two elements goes into the result array. If the
elements were equal, they can both be added.
199
Download from finelybook 7450911@qq.com
Finishing up
Complete the mergeSort function by calling merge. Because you call
mergeSort recursively, the algorithm will split and sort both halves
before merging them together.
3. The merging function should take two sorted arrays and produce
a single sorted array.
Finally - time to see this in action. Head back to the main playground
page and test your merge sort with the following:
200
Download from finelybook 7450911@qq.com
print("Original: \(array)")
print("Merge sorted: \(mergeSort(array))")
}
This outputs:
Performance
The best, worst and average time complexity of merge sort is O(n log
n), which isn’t too bad. If you’re struggling to understand where n log
n comes from, think about how the recursion works:
As you recurse, you split a single array into two smaller arrays.
This means an array of size 2 will need 1 level of recursion, an
array of size 4 will need 2 levels, an array of size 8 will need 3
levels, and so on. If you had an array of 1024 elements, it would
take 10 levels of recursively splitting in two to get down to 1024
single element arrays. In general, if you have an array of size n,
the number of levels is log2(n).
This brings the total cost to O(log n) × O(n) = O(n log n).
The previous chapter’s sort algorithms were in-place and used swapAt
to move elements around. Merge sort, by contrast, allocates
additional memory to do its work. How much? There are log2(n) levels
of recursion and at each level n elements are used. That makes the
total O(n log n) in space complexity.
201
Download from finelybook 7450911@qq.com
202
Download from finelybook 7450911@qq.com
Radix Sort
In this chapter, you’ll look at a completely different model of sorting.
So far, you’ve been relying on comparisons to determine the sorting
order. Radix sort is a non-comparative algorithm for sorting integers
in linear time.
Example
To show how radix sort works, you’ll sort the following array:
First, the array is divided into buckets based on the value of the least
significant digit: the ones digit.
203
Download from finelybook 7450911@qq.com
The relative order of the elements didn’t change this time, but you’ve
still got more digits to inspect.
For values that have no hundreds position (or any other position
without a value), the digit will be assumed to be zero.
204
Download from finelybook 7450911@qq.com
Reassembling the array from these buckets leads to the final sorted
array:
Implementation
Open up the starter project for this chapter. In the Sources directory,
create a new file named RadixSort.swift. Add the following to the file:
}
}
205
Download from finelybook 7450911@qq.com
var digits = 1
while !done {
}
}
2. You declare two variables to track your progress. Radix sort works
in multiple passes, so done serves as a flag that determines
whether the sort is complete. The digits variable keeps track of
the current digit you’re looking at.
Next, you’ll write the logic that sorts each element into buckets (also
known as Bucket sort).
Bucket Sort
Write the following inside the while loop:
// 1
var buckets: [[Int]] = .init(repeating: [], count: base)
// 2
forEach {
number in
let remainingPart = number / digits
let digit = remainingPart % base
buckets[digit].append(number)
}
// 3
digits *= base
self = buckets.flatMap { $0 }
206
Download from finelybook 7450911@qq.com
3. You update digits to the next digit you wish to inspect and
update the array using the contents of buckets. flatMap will
flatten the two-dimensional array to a one-dimensional array, as
if you’re emptying the buckets into the array.
if remainingPart > 0 {
done = false
}
Since forEach iterates over all the integers, as long as one of the
integers still has unsorted digits, you’ll need to continue sorting.
207
Download from finelybook 7450911@qq.com
Radix sort works best when k is constant, which occurs when all
numbers in the array have the same count of significant digits. Its
time complexity then becomes O(n). Radix sort also incurs a O(n)
space complexity, as you need space to store each bucket.
208
Download from finelybook 7450911@qq.com
Heap Sort
Heapsort is another comparison-based algorithm that sorts an array
in ascending order using a heap. This chapter builds on the heap
concepts presented in Chapter 12, "The Heap Data Structure".
1. In a max heap, all parent nodes are larger than their children.
2. In a min heap, all parent nodes are smaller than their children.
The diagram below shows a heap with parent node values underlined:
Getting started
Open up the starter playground. This playground already contains an
implementation of a max heap. Your goal is to extend Heap so it can
also sort. Before you get started, let's look at a visual example of how
heap sort works.
209
Download from finelybook 7450911@qq.com
Example
For any given unsorted array, to sort from lowest to highest, heap sort
must first convert this array into a max heap.
This conversion is done by sifting down all the parent nodes so they
end up in the right spot. The resulting max heap is:
210
Download from finelybook 7450911@qq.com
Because the largest element in a max heap is always at the root, you
start by swapping the first element at index 0 with the last element at
index n - 1. As a result of this swap, the last element of the array is in
the correct spot, but the heap is now invalidated. The next step is thus
to sift down the new root note 5 until it lands in its correct position.
Note that you exclude the last element of the heap as we no longer
consider it part of the heap, but of the sorted array.
211
Download from finelybook 7450911@qq.com
212
Download from finelybook 7450911@qq.com
Implementation
Next, you’ll implement this sorting algorithm. The actual
implementation is very simple, as the heavy lifting is already done by
the siftDown method:
extension Heap {
func sorted() -> [Element] {
var heap = Heap(sort: sort, elements: elements) // 1
for index in heap.elements.indices.reversed() { // 2
heap.elements.swapAt(0, index) // 3
heap.siftDown(from: 0, upTo: index) // 4
}
return heap.elements
}
}
1. You first make a copy of the heap. After heap sort sorts the
elements array, it is no longer a valid heap. By working on a copy
of the heap, you ensure the heap remains valid.
2. You loop through the array, starting from the last element.
3. You swap the first element and the last element. This moves the
largest unsorted element to its correct spot.
4. Because the heap is now invalid, you must sift down the new root
node. As a result, the next largest element will become the new
root.
let heap = Heap(sort: >, elements: [6, 12, 2, 26, 8, 18, 21,
print(heap.sorted())
Performance
Even though you get the benefit of in-memory sorting, the
performance of heap sort is O(n log n) for its best, worse and average
cases. This is because you have to traverse the whole list once, and
every time you swap elements you must perform a sift down, which is
an O(log n) operation.
Heap sort is also not a stable sort because it depends on how the
elements are laid out and put into the heap. If you were heap sorting a
deck of cards by their rank, for example, you might see their suite
change order with respect to the original deck.
214
Download from finelybook 7450911@qq.com
Quicksort
In the preceding chapters, you’ve learned to sort an array using
comparison-based sorting algorithms, merge sort, and heap sort.
Example
Open up the starter playground. A naïve implementation of Quicksort
is provided in quicksortNaive.swift:
215
Download from finelybook 7450911@qq.com
no need to sort.
3. Using the pivot, split the original array into three partitions.
Elements less than, equal to or greater than the pivot go into
different partitions.
Let’s now visualize the code above. Given the unsorted array below:
216
Download from finelybook 7450911@qq.com
Partitioning strategies
Lomuto’s partitioning
Lomuto’s partitioning algorithm always chooses the last element as
the pivot. Let’s look at how this works in code.
low and high set the range within the array you will partition.
This range will get smaller and smaller with every recursion.
var i = low // 2
for j in low..<high { // 3
if a[j] <= pivot { // 4
a.swapAt(i, j) // 5
i += 1
}
}
a.swapAt(i, high) // 6
218
Download from finelybook 7450911@qq.com
return i // 7
1. Set the pivot. Lomuto always chooses the last element as the
pivot.
2. The variable i indicates how many elements are less than the
pivot. Whenever you encounter an element that is less than the
pivot, you swap it with the element at index i and increase i.
3. Loop through all the elements from low to high, but not
including high since it’s the pivot.
6. Once done with the loop, swap the element at i with the pivot.
The pivot always sits between the less and greater partitions.
While this algorithm loops through the array, it divides the array into
four regions:
[ values <= pivot | values > pivot | not compared yet | pivot ]
low i-1 i j-1 j high-1 high
219
Download from finelybook 7450911@qq.com
Step-by-step
Let’s look at a few steps of the algorithm to get a clear understanding
of how it works.
0 1 2 3 4 5 6 7 8 9 10 11 12
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, | 8 ]
low high
i
j
0 1 2 3 4 5 6 7 8 9 10 11 12
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, | 8 ]
low high
i
j
0 1 2 3 4 5 6 7 8 9 10 11 12
[ 0, 12, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, | 8 ]
low high
i
j
The third element 3 is again smaller than the pivot, so another swap
occurs.
220
Download from finelybook 7450911@qq.com
0 1 2 3 4 5 6 7 8 9 10 11 12
[ 0, 3, 12, 9, 2, 21, 18, 27, 1, 5, 8, -1, | 8 ]
low high
i
j
These steps continue until all but the pivot element have been
compared. The resulting array is:
0 1 2 3 4 5 6 7 8 9 10 11 12
[ 0, 3, 2, 1, 5, 8, -1, 27, 9, 12, 21, 18, | 8 ]
low high
i
0 1 2 3 4 5 6 7 8 9 10 11 12
[ 0, 3, 2, 1, 5, 8, -1 | 8 | 9, 12, 21, 18, | 27 ]
low high
i
221
Download from finelybook 7450911@qq.com
}
}
Here you simply apply Lomuto’s algorithm to partition the array into
two regions, then recursively sort these regions. Recursion ends once
a region has less than two elements.
You can try out Lomuto’s Quicksort by adding the following to your
playground:
Hoare’s partitioning
Hoare’s partitioning algorithm always chooses the first element as the
pivot. Let’s look at how this works in code.
while true {
repeat { j -= 1 } while a[j] > pivot // 3
repeat { i += 1 } while a[i] < pivot // 4
if i < j { // 5
a.swapAt(i, j)
} else {
return j // 6
}
}
}
222
Download from finelybook 7450911@qq.com
Note: The index returned from the partition does not necessarily
have to be the index of the pivot element.
Step-by-step
Given the unsorted array below:
First, 12 is set as the pivot. Then i and j will start running through
the array, looking for elements that are not lesser than (in the case of
i) or greater than (in the case of j) the pivot. i will stop at element 12
and j will stop at element 8.
223
Download from finelybook 7450911@qq.com
There are far fewer swaps here compared to Lomuto’s algorithm. Isn’t
that nice?
224
Download from finelybook 7450911@qq.com
[8, 7, 6, 5, 4, 3, 2, 1]
If you use Lomuto’s algorithm, the pivot will be the last element 1.
This results in the following partitions:
less: [ ]
equal: [1]
225
Download from finelybook 7450911@qq.com
greater: [8, 7, 6, 5, 4, 3, 2]
An ideal pivot would split the elements evenly between the less than
and greater than partitions. Choosing the first or last element of an
already sorted array as a pivot makes Quicksort perform much like
insertion sort, which results in a worst-case performance of O(n²).
One way to address this problem is by using the median of three pivot
selection strategy. Here you find the median of the first, middle and
last element in the array and use that as a pivot. This prevents you
from picking the highest or lowest element in the array.
226
Download from finelybook 7450911@qq.com
a.swapAt(pivotIndex, high)
let pivot = partitionLomuto(&a, low: low, high: high)
quicksortLomuto(&a, low: low, high: pivot - 1)
quicksortLomuto(&a, low: pivot + 1, high: high)
}
}
227
Download from finelybook 7450911@qq.com
228
Download from finelybook 7450911@qq.com
Step-by-step
Let’s go over an example using the unsorted array below:
0 is smaller than the pivot so you swap the elements at equal and
smaller and increase both pointers:
229
Download from finelybook 7450911@qq.com
e
l
And so on.
To understand how and when the algorithm ends, let’s continue from
the second-to-last step:
230
Download from finelybook 7450911@qq.com
e
l
Indices smaller and larger now point to the first and last elements of
the middle partition. By returning them, the function clearly marks
the boundaries of the three partitions.
That’s it!
231
Download from finelybook 7450911@qq.com
and then partitioning. You sort the array by breaking it down into
smaller partitions and partition until you can go no further.
Remember that quicksort can perform its best in O(n log n), but when
an array is nearly sorted, it could perform as bad as O(n²).
232
Download from finelybook 7450911@qq.com
Graphs
What do social networks have in common with booking cheap flights
around the world? You can represent both of these real-world models
as graphs!
In the graph below, the vertices are represented by circles, and the
edges are the lines that connect them.
Weighted graphs
In a weighted graph, every edge has a weight associated with it that
represents the cost of using this edge. This lets you choose the
cheapest or shortest path between two vertices.
233
Download from finelybook 7450911@qq.com
Using this network, you can determine the cheapest flights from San
Francisco to Singapore for all those budget-minded digital nomads
out there!
Directed graphs
As well as assigning a weight to an edge, your graphs can also have
direction. Directed graphs are more restrictive to traverse, as an edge
may only permit traversal in one direction.
234
Download from finelybook 7450911@qq.com
Undirected graphs
You can think of an undirected graph as a directed graph where all
edges are bidirectional.
In an undirected graph:
235
Download from finelybook 7450911@qq.com
Common operations
Let's establish a protocol for graphs.
Open up the starter project for this chapter. Create a new file named
Graph.swift and add the following inside the file:
case directed
case undirected
}
associatedtype Element
236
Download from finelybook 7450911@qq.com
weight: Double?)
func add(_ edge: EdgeType, from source: Vertex<Element>,
to destination: Vertex<Element>,
weight: Double?)
func edges(from source: Vertex<Element>) -> [Edge<Element>]
func weight(from source: Vertex<Element>,
to destination: Vertex<Element>) -> Double?
}
Before you can do that, you must first build types to represent
vertices and edges.
237
Download from finelybook 7450911@qq.com
Defining a vertex
Create a new file named Vertex.swift and add the following inside the
file:
You'll use Vertex as the key type for a dictionary, so you need to
conform to Hashable. Add the following extension to implement the
requirements for Hashable:
238
Download from finelybook 7450911@qq.com
Because vertices have a unique index, you use the index property to
implement hashValue and ==.
Defining an edge
To connect two vertices, there must be an edge between them!
Create a new file named Edge.swift and add the following inside the
file:
239
Download from finelybook 7450911@qq.com
Adjacency list
The first graph implementation you'll learn uses an adjacency list. For
every vertex in the graph, the graph stores a list of outgoing edges.
The adjacency list below describes the network for the network of
flights depicted above:
240
Download from finelybook 7450911@qq.com
Implementation
Create a new file named AdjacencyList.swift, and add the following:
public init() {}
241
Download from finelybook 7450911@qq.com
Creating a vertex
Add the following method to AdjacencyList:
Here you create a new vertex and return it. In the adjacency list, you
store an empty array of edges for this new vertex.
242
Download from finelybook 7450911@qq.com
This method creates a new edge and stores it in the adjacency list.
extension Graph {
244
Download from finelybook 7450911@qq.com
Here you find the first edge from source to destination; if there is
one, you return its weight.
245
Download from finelybook 7450911@qq.com
edgeString.append("\(edge.destination)")
}
}
result.append("\(vertex) ---> [ \(edgeString) ]\n") // 3
}
return result
}
}
2. For every vertex, you loop through all its outgoing edges and add
an appropriate string to the output.
3. Finally, for every vertex you print both the vertex itself and its
outgoing edges.
You have finally completed your first graph! Let's now try it out by
building a network.
Building a network
Let's go back to the flights example, and construct a network of flights
with the prices as weights.
246
Download from finelybook 7450911@qq.com
247
Download from finelybook 7450911@qq.com
print(graph.description)
You have just created a graph using an adjacency list, where you used
a dictionary to store the outgoing edges for every vertex. Let's take a
look at a different approach to how to store vertices and edges.
Adjacency matrix
248
Download from finelybook 7450911@qq.com
The following adjacency matrix describes the network for the flights
depicted above. Edges that don't exist have a weight of 0.
249
Download from finelybook 7450911@qq.com
Using the array of vertices on the left, you can learn a lot from the
matrix. For example:
Note: There is a blue line in the middle of the matrix. When the
row and column are equal, this represents an edge between a
vertex and itself, which is not allowed.
Implementation
Create a new file named AdjacencyMatrix.swift and add the following
to it:
public init() {}
250
Download from finelybook 7450911@qq.com
Creating a Vertex
Add the following method to AdjacencyMatrix:
251
Download from finelybook 7450911@qq.com
1. Add a new row to the matrix. This row holds the outgoing edges
for the new vertex.
Creating edges
Creating edges is as simple as filling in the matrix. Add the following
method:
252
Download from finelybook 7450911@qq.com
To retrieve the outgoing edges for a vertex, you search the row for this
vertex in the matrix for weights that aren not nil. Every non-nil
weight corresponds with an outgoing edge. The destination is the
vertex that corresponds with the column in which the weight was
found.
253
Download from finelybook 7450911@qq.com
return weights[source.index][destination.index]
}
254
Download from finelybook 7450911@qq.com
Building a network.
You will be reusing the same example from AdjacencyList:
with:
0: Singapore
1: Tokyo
2: Hong Kong
3: Detroit
4: San Francisco
5: Washington DC
6: Austin Texas
7: Seattle
255
Download from finelybook 7450911@qq.com
ø 500.0 300.0 ø ø ø ø ø
500.0 ø 250.0 450.0 ø 300.0 ø ø
300.0 250.0 ø ø 600.0 ø ø ø
ø 450.0 ø ø ø ø 50.0 ø
ø ø 600.0 ø ø 337.0 297.0 218.0
ø 300.0 ø ø 337.0 ø 292.0 277.0
ø ø ø 50.0 297.0 292.0 ø ø
ø ø ø ø 218.0 277.0 ø ø
San Francisco Outgoing Flights:
--------------------------------
from: 4: San Francisco to: 2: Hong Kong
from: 4: San Francisco to: 5: Washington DC
from: 4: San Francisco to: 6: Austin Texas
from: 4: San Francisco to: 7: Seattle
Graph analysis
256
Download from finelybook 7450911@qq.com
If your graph has lots of edges, it's considered a dense graph, and an
adjacency matrix would be a better fit as you'd be able to access your
weights and edges far more quickly.
You have only scratched the surface of graphs so far. There are many
more algorithms that can be applied to graph data structures. You will
go through some of these in the upcoming chapters!
258
Download from finelybook 7450911@qq.com
Breadth-First Search
In the previous chapter, you explored how graphs can be used to
capture relationships between objects. Remember that objects are just
vertices, and the relationships between them are represented by
edges.
Example
BFS starts off by selecting any vertex in a graph. The algorithm then
explores all neighbors of this vertex before traversing the neighbors of
259
Download from finelybook 7450911@qq.com
You will use a queue to keep track of which vertices to visit next. The
260
Download from finelybook 7450911@qq.com
You will use a queue to keep track of which vertices to visit next. The
first in first out approach of the queue guarantees that all of a vertex's
neighbors are visited before you traverse one level deeper.
1. To begin, you pick a source vertex to start from. Here you have
chosen A, which is added to the queue.
2. As long as the queue is not empty, you dequeue and visit the next
vertex, in this case A. Next, you add all of A's neighboring vertices
[B, D, C] to the queue.
Note: It's important to note that you only add a vertex to the
queue when it has not yet been visited and is not already in the
queue.
261
Download from finelybook 7450911@qq.com
1. The queue is not empty, so you dequeue and visit the next vertex,
which is B. You then add B's neighbor E to the queue. A is already
visited so it does not get added. The queue now has [D, C, E].
1. Next, you dequeue C and add its neighbors [F, G] to the queue.
The queue now has [E, F, G].
Note that you have now visited all of A's neighbors! BFS now moves
on to the second level of neighbors.
262
Download from finelybook 7450911@qq.com
1. You dequeue E and add H to the queue. The queue now has [F,
G, H]. Note that you don't add B or F to the queue because B is
already visited and F is already in the queue.
1. You dequeue F, and since all its neighbors are already in the
queue or visited, you don't add anything to the queue.
2. Just like the previous step, you dequeue G and don't add anything
to the queue.
263
Download from finelybook 7450911@qq.com
Implementation
Open up the starter playground for this chapter. This playground
contains an implementation of a graph that was built in the previous
chapter. It also includes a stack-based queue implementation which
you will use to implement BFS.
extension Graph {
// more to come
return visited
}
}
264
Download from finelybook 7450911@qq.com
queue.enqueue(source) // 1
enqueued.insert(source)
2. You continue to dequeue a vertex from the queue until the queue
is empty.
3. Every time you dequeue a vertex from the queue, you add it to
the list of visited vertices.
4. Then, you find all edges that start from the current vertex and
iterate over them.
5. For each edge, you check to see if its destination vertex has been
enqueued before, and if not, you add it to the code.
265
Download from finelybook 7450911@qq.com
Notice the order of the explored vertices using breadth first search:
0: A
1: B
2: C
3: D
4: E
5: F
6: G
7: H
One thing to keep in mind with neighboring vertices is that the order
in which you visit them is determined by how you construct your
graph. You could have added an edge between A and C before adding
one between A and B. In this case, the output would list C before B.
Performance
When traversing a graph using breadth-first search, each vertex is
enqueued once. This has a time complexity of O(V) . During this
traversal, you also visit all the the edges. The time it takes to visit all
edges is O(E) . This means the overall time complexity for breadth-
first search is O(V + E).
The space complexity of BFS is O(V) since you have to store the
vertices in three separate structures: queue, enqueued and visited.
266
Download from finelybook 7450911@qq.com
267
Download from finelybook 7450911@qq.com
Depth-First Search
In the previous chapter, you looked at breadth-first search where you
had to explore every neighbor of a vertex before going to the next
level. In this chapter, you will look at depth-first search, another
algorithm for traversing or searching a graph.
Topological sorting
Detecting a cycle
Example
Let's go through a DFS example. The example graph below is exactly
the same as the previous chapter. This is so you can see the difference
between BFS and DFS.
268
Download from finelybook 7450911@qq.com
You will use a stack to keep track of the levels you move through. The
stack's last-in-first-out approach helps with backtracking. Every push
on the stack means you move one level deeper. You can pop to return
to a previous level if you reach a dead end.
269
Download from finelybook 7450911@qq.com
2. As long as the stack is not empty, you visit the top vertex on the
stack and push the first neighboring vertex that has yet to be
visited. In this case you visit A and push B.
Recall from the previous chapter that the order in which you add
edges influences the result of a search. In this case the first edge
added to A was an edge to B, so B is pushed first.
270
Download from finelybook 7450911@qq.com
Note that every time you push on the stack, you advance further down
a branch. Instead of visiting every adjacent vertex, you simply
continue down a path until you reach the end and then backtrack.
271
Download from finelybook 7450911@qq.com
1. The next vertex to visit is C. It has neighbors [A, F, G], but all of
these have been visited. You have reached a dead end, so it's time
to backtrack by popping C off the stack.
2. This brings you back to G. It has neighbors [F, C], but all of these
have been visited. Another dead end, pop G.
272
Download from finelybook 7450911@qq.com
2. This brings you all the way back to A, whose neighbor D still needs
to be visited, so you push D on the stack.
273
Download from finelybook 7450911@qq.com
When exploring the vertices, you can construct a tree like structure,
showing the branches you've visited. You can see how deep DFS went,
compared to BFS.
Implementation
Open up the starter playground for this chapter. This playground
contains an implementation of a graph that as well as a stack which
you'll use to implement DFS.
extension Graph {
stack.push(source)
pushed.insert(source)
visited.append(source)
274
Download from finelybook 7450911@qq.com
return visited
}
}
To start the algorithm, you add the source vertex to all three.
275
Download from finelybook 7450911@qq.com
1. You continue to check the top of the stack for a vertex until the
stack is empty. You have labeled this loop outer so you have a
way to continue to the next vertex, even within nested loops.
2. You find all the neighboring edges for the current vertex.
3. If there are no edges, you pop the vertex off the stack and
continue to the next one.
6. If the current vertex did not have any unvisited neighbors, you
know you've reached a dead end and can pop it off the stack.
Once the stack is empty, the DFS algorithm is complete! All you have
to do is return the visited vertices in the order you visited them.
276
Download from finelybook 7450911@qq.com
0: A
1: B
4: E
5: F
6: G
2: C
7: H
3: D
Performance
Depth-first search will visit every single vertex at least once. This has
a time complexity of O(V).
277
Download from finelybook 7450911@qq.com
https://github.com/raywenderlich/swift-algorithm-
club/tree/master/Depth-First%20Search.
278
Download from finelybook 7450911@qq.com
Dijkstra’s Algorithm
Have you ever used the Google or Apple Maps app to find the shortest
or fastest from one place to another? Dijkstra’s algorithm is
particularly useful in GPS networks to help find the shortest path
between two places.
Example
All the graphs you have looked at thus far have been undirected
graphs. Let’s change it up a little and work with a directed graph!
279
Download from finelybook 7450911@qq.com
First pass
From vertex A, look at all outgoing edges. In this case, you have three
edges:
A to B, has a cost of 8.
A to F, has a cost of 9.
A to G, has a cost of 1.
280
Download from finelybook 7450911@qq.com
As you work through this example, the table on the right of the graph
will represent a history, or record, of Dijkstra’s algorithm at each
stage. Each pass of the algorithm will add a row to the table. The last
row in the table will be the final output of the algorithm.
Second pass
In the next cycle, Dijkstra’s algorithm looks at the lowest cost path
you have thus far. A to G has the smallest cost of 1, and is also the
shortest path to get to G. This is marked with a dark fill in the output
table.
Now from the lowest cost path, vertex G, look at all the outgoing
edges. There is only one edge from G to C, and its total cost is 4. This
is because the cost from A to G to C is 1 + 3 = 4.
Every value in the output table has two parts: the total cost to reach
that vertex, and the last neighbor on the path to that vertex. For
example, the value 4 G in the column for vertex C means that the cost
281
Download from finelybook 7450911@qq.com
Third pass
In the next cycle, you look at the next-lowest cost. According to the
table, the path to C has the smallest cost, so the search will continue
from C. You fill column C because you’ve found the shortest path to
get to C.
282
Download from finelybook 7450911@qq.com
Fourth pass
Now in the next cycle, ask yourself what is the next-lowest cost path?
According to the table, C to E has the smallest total cost of 5, so the
search will continue from E.
You fill column E because you’ve found the shortest path. Vertex E
has the following outgoing edges:
Fifth pass
284
Download from finelybook 7450911@qq.com
Sixth pass
285
Download from finelybook 7450911@qq.com
Seventh pass
F is next up.
286
Download from finelybook 7450911@qq.com
F has one outgoing edge to A with a total cost of 9 + 2 = 11. You can
disregard this edge since A is the starting vertex.
Eighth pass
You have covered every vertex except for H. H has two outgoing edges
to G and F. However, there is no path from A to H. This is why the
whole column for H is nil.
287
Download from finelybook 7450911@qq.com
This completes Dijkstra’s algorithm, since all the vertices have been
visited!
You can now check the final row for the shortest paths and their cost.
For example, the output tells you the cost to get to D is 7. To find the
path, you simply backtrack. Each column records the previous vertex
the current vertex is connected to. You should get from D to E to C to
G and finally back to A.
288
Download from finelybook 7450911@qq.com
Implementation
Open up the starter playground for this chapter. This playground
comes with an adjacency list graph and a priority queue which you
will use to implement Dijkstra’s algorithm.
The priority queue is used to store vertices that have not been visited.
It’s a min-priority queue so that every time you dequeue a vertex, it
gives you vertex with the current tentative shortest path.
Here you defined an enum named Visit. This keeps track of two
states:
2. The vertex has an associated edge that leads to a path back to the
starting vertex.
Now define a class called Dijkstra. Add the following after the code
you added above:
289
Download from finelybook 7450911@qq.com
}
}
Helper methods
Before building Dijkstra, let’s create some helper methods that will
help create the algorithm.
You need a mechanism to keep track of the total weight from the
current vertex back to the start vertex. To do this, you will keep track
of a dictionary named paths that stores a Visit state for every vertex.
290
Download from finelybook 7450911@qq.com
5. Set the current vertex to the edge’s source vertex. This moves
you closer to the start vertex.
6. Once the while loop reaches the start case you have completed
the path and return it.
291
Download from finelybook 7450911@qq.com
Once you have the ability to construct a path from the destination
back to the start vertex, you need a way to calculate the total weight
for that path. Add the following method to class Dijkstra:
2. flapMap removes all the nil weights values from the paths.
Now that you have established your helper methods, let’s implement
Dijkstra’s algorithm.
// 2
var priorityQueue = PriorityQueue<Vertex<T>>(sort: {
self.distance(to: $0, with: paths) <
self.distance(to: $1, with: paths)
})
priorityQueue.enqueue(start) // 3
// to be continued
}
292
Download from finelybook 7450911@qq.com
This method takes in a start vertex and returns a dictionary of all the
paths. Within the method you:
return paths
2. For the current vertex, you go through all its neighboring edges.
3. You make sure the edge has a weight. If not, you move on to the
next edge.
293
Download from finelybook 7450911@qq.com
Once all the vertices have been visited, and the priority queue is
empty, you return the dictionary of shortest paths back to the start
vertex.
Navigate to the main playground, and you will notice the graph above
has been already constructed using an adjacency list. Time to see
Dijkstra’s algorithm in action.
294
Download from finelybook 7450911@qq.com
1. Calculate the shortest paths to all the vertices from the start
vertex A.
This outputs:
A --(1.0)--> G
G --(3.0)--> C
C --(1.0)--> E
E --(2.0)--> D
295
Download from finelybook 7450911@qq.com
Performance
In Dijkstra’s algorithm, you constructed your graph using an
adjacency list. You used a min-priority queue to store vertices and
extract the vertex with the minimum path. This has an overall
performance of O(log V). This is because the heap operations of
extracting the minimum element or inserting an element both take
O(log V).
This time, instead of going down to the next level, you use a min-
priority queue to select a single vertex with the shortest distance to
traverse down. That means it is O(1 + E) or simply O(E).
Whenever you see a network that has weighted edges and you need to
find the shortest path, think of your good friend Dijkstra!
296
Download from finelybook 7450911@qq.com
Prim’s Algorithm
In previous chapters, you’ve looked at depth-first and breadth-first
search algorithms. These algorithms form spanning trees.
From this undirected graph that forms a triangle, you can generate
three different spanning trees, where you require only two edges to
connect all vertices.
297
Download from finelybook 7450911@qq.com
might want to find the cheapest way to lay out a network of water
pipes.
Notice that only the third subgraph forms a minimum spanning tree,
since it has the minimum total cost of 3.
There are six steps to finding a minimum spanning tree with Prim’s
algorithm:
298
Download from finelybook 7450911@qq.com
Example
Imagine the graph below represents a network of airports. The
vertices are the airports, and the edges between them represent the
cost of fuel to fly an airplane from one airport to the next.
299
Download from finelybook 7450911@qq.com
1. Choose any vertex in the graph. I’ll assume you chose vertex 2.
2. This vertex has edges with weights [6, 5, 3]. A greedy algorithm
chooses the smallest weighted edge.
2. Choose the next shortest edge from the explored vertices. The
edges are [6, 5, 6, 6]. You choose the edge with weight 5, which is
connected to vertex 3.
2. The next potential edges are [6, 1, 5, 4, 6]. You choose the edge
with weight 1, which is connected to vertex 1.
2. Choose the next shortest edge from the explored vertices. The
edges are [5, 5, 4, 6]. You choose the edge with weight 4, which is
connected to vertex 6.
301
Download from finelybook 7450911@qq.com
2. Choose the next shortest edge from the explored vertices. The
edges are [5, 5, 2]. You choose the edge with weight 2, which is
connected to vertex 4.
Note: If all edges have the same weight, you can pick any one of
them.
302
Download from finelybook 7450911@qq.com
This is the minimum spanning tree from our example produced from
Prim’s algorithm.
Implementation
Open up the starter playground for this chapter. This playground
comes with an adjacency list graph and a priority queue which you
will use to implement Prim’s algorithm.
The priority queue is used to store the edges of the explored vertices.
It’s a min-priority queue so that every time you dequeue an edge, it
gives you the edge with the smallest weight.
303
Download from finelybook 7450911@qq.com
Helper methods
Before building the algorithm, you’ll create some helper methods to
keep you organized and consolidate duplicate code.
Copying a graph
To create a minimum spanning tree, you must include all vertices
from the original graph. Add the following to class Prim:
Finding edges
Besides copying the graph’s vertices, you also need to find and store
the edges of every vertex you explore. Add the following to class Prim:
304
Download from finelybook 7450911@qq.com
3. If it has not been visited, you add the edge to the priority queue.
305
Download from finelybook 7450911@qq.com
2. cost keeps track of the total weight of the edges in the minimum
spanning tree.
visited.insert(start) // 3
addAvailableEdges(for: start, // 4
in: graph,
check: visited,
to: &priorityQueue)
// to be continued
1. Copy all the vertices from the original graph to the minimum
spanning tree.
306
Download from finelybook 7450911@qq.com
4. Add all potential edges from the start vertex into the priority
queue.
visited.insert(vertex) // 4
cost += smallestEdge.weight ?? 0.0 // 5
mst.add(.undirected, // 6
from: smallestEdge.source,
to: smallestEdge.destination,
weight: smallestEdge.weight)
addAvailableEdges(for: vertex, // 7
in: graph,
check: visited,
to: &priorityQueue)
}
3. If this vertex has been visited, restart the loop and get the next
smallest edge.
307
Download from finelybook 7450911@qq.com
6. Add the smallest edge into the minimum spanning tree you are
constructing.
Navigate to the main playground, and you’ll see the graph above has
been already constructed using an adjacency list.
This constructs a graph from the example section. You’ll see the
following output:
cost: 15.0
mst:
308
Download from finelybook 7450911@qq.com
5 ---> [ 2 ]
6 ---> [ 3, 4 ]
3 ---> [ 2, 1, 6 ]
1 ---> [ 3 ]
2 ---> [ 5, 3 ]
4 ---> [ 6 ]
Performance
In the algorithm above you maintain three data structures:
2. A Set to store all vertices you have visited. Adding a vertex to the
set and checking if the set contains a vertex also have a time
complexity of O(1).
310
Download from finelybook 7450911@qq.com
Conclusion
We hope you learned a lot about data structures and algorithms in
Swift as you read this book — and had some fun in the process!
Knowing when and why to apply data structures and algorithms goes
beyond just acing that whiteboard interview. With the knowledge
you’ve gained here, you can easily and efficiently solve pretty much
any data manipulation or graph analysis issue put in front of you.
Thank you again for purchasing this book. Your continued support is
what makes the tutorials, books, videos, conferences and other things
we do at raywenderlich.com possible, and we truly appreciate it!
311
Download from finelybook 7450911@qq.com
iOS Apprentice
https://store.raywenderlich.com/products/ios-apprentice
Each new app will be a little more advanced than the one before, and
together they cover everything you need to know to make your own
312
Download from finelybook 7450911@qq.com
Swift Apprentice
https://store.raywenderlich.com/products/swift-apprentice
This is a sister book to the iOS Apprentice; the iOS Apprentice focuses
313
Download from finelybook 7450911@qq.com
This is a sister book to the iOS Apprentice; the iOS Apprentice focuses
on making apps, while Swift Apprentice focuses on the Swift 4
language itself.
314
Download from finelybook 7450911@qq.com
over another. This set of basic data structures and algorithms will
serve as an excellent foundation for building more complex and
special-purpose constructs. As well, the high-level expressiveness of
Swift makes it an ideal choice for learning these core concepts
without sacrificing performance.
In this book, you’ll take a deep dive into the Realm Database, learn
315
Download from finelybook 7450911@qq.com
In this book, you’ll take a deep dive into the Realm Database, learn
how to set up your first Realm database, see how to persist and read
data, find out how to perform migrations and more. In the last
chapter of this book, you'll take a look at the synchronization features
of Realm Cloud to perform real-time sync of your data across all
devices.
316
Download from finelybook 7450911@qq.com
Whether you’re looking to create a backend for your iOS app, or want
to create fully-featured web apps, Vapor is the perfect platform for
you.
This book starts with the basics of web development and introduces
the basics of Vapor; it then walks you through creating APIs and web
backends; creating and configuring databases; deploying to Heroku,
AWS, or Docker; testing your creations and more1
iOS 11 by Tutorials
317
Download from finelybook 7450911@qq.com
https://store.raywenderlich.com/products/ios-11-by-tutorials
This book is for intermediate iOS developers who already know the
basics of iOS and Swift development but want to learn the new APIs
introduced in iOS 11.
Discover the new features for developers in iOS 11, such as ARKit,
Core ML, Vision, drag & drop, document browsing, the new changes
in Xcode 9 and Swift 4 — and much, much more.
318
Download from finelybook 7450911@qq.com
After reading this book, you'll have the tools and knowledge to answer
even the most obscure question about your code — or someone else’s.
319
Download from finelybook 7450911@qq.com
This book is for iOS developers who already feel comfortable with iOS
and Swift, and want to dive deep into development with RxSwift.
320
Download from finelybook 7450911@qq.com
This book is for intermediate iOS developers who already know the
basics of iOS and Swift 4 development but want to learn how to use
Core Data to save data in their apps.
Start with with the basics like setting up your own Core Data Stack all
the way to advanced topics like migration, performance,
multithreading, and more!
321
Download from finelybook 7450911@qq.com
This book is for iOS developers who already know the basics of iOS
and Swift 4, and want to dive deep into animations.
Start with basic view animations and move all the way to layer
animations, animating constraints, view controller transitions, and
more!
watchOS by Tutorials
https://store.raywenderlich.com/products/watchos-by-tutorials
322
Download from finelybook 7450911@qq.com
This book is for intermediate iOS developers who already know the
basics of iOS and Swift development but want to learn how to make
Apple Watch apps for watchOS 4.
tvOS Apprentice
https://store.raywenderlich.com/products/tvos-apprentice
323
Download from finelybook 7450911@qq.com
This book teaches you how to make tvOS apps in two different ways:
via the traditional method using UIKit, and via the new Client-Server
method using TVML.
325
Download from finelybook 7450911@qq.com
Android Apprentice
https://store.raywenderlich.com/products/android-apprentice
326
Download from finelybook 7450911@qq.com
The Android Apprentice takes you all the way from building your first
app, to submitting your app for sale. By the end of this book, you’ll be
experienced enough to turn your vague ideas into real apps that you
can release on the Google Play Store.
You’ll build 4 complete apps from scratch — each app is a little more
complicated than the previous one. Together, these apps will teach
you how to work with the most common controls and APIs used by
Android developers around the world.
Kotlin Apprentice
https://store.raywenderlich.com/products/kotlin-apprentice
327
Download from finelybook 7450911@qq.com
328