Sequences
Arrays, ranges, and strings as sequences: length, element selection, membership, slicing, and the processing patterns that work on all of them.
- CS61A
- Sequences · Composing Programs 2.1–2.3
- Swift book
- Collection Types · Strings and Characters
Unit 1 was about functions. Unit 2 is about data: how to put many values together, and how to build new kinds of values of your own. It starts with the most common way to combine values, putting them in order.
A sequence is an ordered collection of values. CS61A describes the sequence abstraction by what you can do with one, not by how it is stored:
- Length: how many elements it has.
- Element selection: get the element at a position.
- Membership: ask whether a value is in it.
- Slicing: take a contiguous part of it.
Arrays, ranges, and strings are all sequences in Swift, and all of them support these operations.
Arrays
An array holds values of one type, in order. [Int] is the type of an
array of integers:
let primes = [2, 3, 5, 7, 11]
print(primes.count) // 5, the length
print(primes[0], primes[4]) // 2 11, element selection
print(primes.contains(9)) // false, membership
print(primes.first!, primes.isEmpty) // 2 false
Positions start at 0, and asking for a position that does not exist stops
the program, as Lesson 1 showed. + joins two arrays into a new one:
print([1, 2] + [3]) // [1, 2, 3]
An array bound with var can grow and change:
var numbers = [3, 1]
numbers.append(4) // [3, 1, 4]
numbers.insert(0, at: 0) // [0, 3, 1, 4]
numbers += [5] // [0, 3, 1, 4, 5]
print(numbers, numbers.firstIndex(of: 4)!) // [0, 3, 1, 4, 5] 3
The exclamation mark after first and firstIndex(of:) is there because
both might have no answer: an empty array has no first element. Lesson 11
is about how Swift represents “no answer”.
Iterating
A for-in loop visits the elements in order. enumerated() gives each
element with its position, and zip walks two sequences side by side:
for (index, name) in ["Ada", "Grace"].enumerated() {
print(index, name)
}
for (number, letter) in zip([1, 2, 3], ["a", "b", "c"]) {
print(number, letter)
}
Slicing
A slice is a contiguous part of a sequence, written with a range:
let values = [10, 20, 30, 40]
print(values[1...2]) // [20, 30]
print(values[..<2]) // [10, 20]
print(values.prefix(2), values.suffix(1), values.dropFirst())
Swift’s slices come with a surprise. A slice of an array is an
ArraySlice, a view of the original array that keeps the original
positions. It does not renumber from zero.
What would Swift print?
let values = [10, 20, 30, 40]
let middle = values[1...2]
print(middle[1], middle.startIndex)Show answer and explanation
Answer20 1
middle holds 20 and 30 at their original positions, 1 and 2. So
middle[1] is 20, not 30, and its first valid index is 1. Wrap it in
Array(middle) to get a fresh array numbered from 0.
Strings
A string is a sequence of Characters. It has a length, membership, and
iteration like any sequence:
let word = "hello"
print(word.count, word.first!, word.contains("e")) // 5 h true
for letter in word {
print(letter, terminator: "-")
}
print()
What a string does not allow is selecting a character by an integer.
What would Swift print?
let word = "hello"
print(word[0])Show answer and explanation
AnswerError
Swift rejects word[0] at compile time: “cannot subscript String with an
Int”. Characters can take different amounts of memory (think of an emoji or
an accented letter), so jumping to position k is not a constant-time
operation, and Swift will not pretend it is. Use word.first, word.prefix(3),
or convert with Array(word) when you need positions.
reversed() gives the characters in reverse order; String(...) turns
them back into a string:
print(String("stressed".reversed())) // desserts
Sequence processing
Lesson 4’s map, filter, and reduce work on every sequence, including
ranges. Together they cover most of what CS61A does with list
comprehensions:
let squares = (1...5).map { $0 * $0 } // [1, 4, 9, 16, 25]
let evens = squares.filter { $0 % 2 == 0 } // [4, 16]
let total = squares.reduce(0, +) // 55
print(squares, evens, total)
print([3, 1, 2].sorted(), Array(1...3))
What would Swift print?
let words = ["swift", "is", "fun"]
print(words.map { $0.count }.reduce(0, +), words.filter { $0.count > 2 })Show answer and explanation
Answer10 ["swift", "fun"]
The lengths are 5, 2, and 3, which add up to 10. The filter keeps the words longer than two characters.
Put the lines in order
Put the lines in order: digits(n) returns the digits of n as an array, and the program prints [2, 0, 2, 6].
return [n]print(digits(2026))}if n < 10 {return digits(n / 10) + [n % 10]}func digits(_ n: Int) -> [Int] {
Show the correct program
func digits(_ n: Int) -> [Int] {
if n < 10 {
return [n]
}
return digits(n / 10) + [n % 10]
}
print(digits(2026))Lab 8: Sequences
Q1: Couple
Write couple(_:_:), which takes two arrays of the same length and returns
an array of two-element arrays, pairing elements at the same position.
func couple(_ first: [Int], _ second: [Int]) -> [[Int]] {
// your code here
}
Show solution
func couple(_ first: [Int], _ second: [Int]) -> [[Int]] {
precondition(first.count == second.count, "arrays must have the same length")
return zip(first, second).map { [$0, $1] }
}
assert(couple([1, 2, 3], [4, 5, 6]) == [[1, 4], [2, 5], [3, 6]])
assert(couple([], []) == [])zip produces pairs; map turns each pair into a two-element array.
Q2: Palindromes
Return true if a string reads the same forward and backward.
func isPalindrome(_ text: String) -> Bool {
// your code here
}
Show solution
func isPalindrome(_ text: String) -> Bool {
let characters = Array(text)
return characters == characters.reversed()
}
assert(isPalindrome("racecar"))
assert(isPalindrome("noon"))
assert(!isPalindrome("swift"))
assert(isPalindrome(""))Two arrays are equal when they have the same elements in the same order. The empty string is a palindrome: reversing nothing gives nothing.
Q3: Running totals
Write runningTotals(_:), which returns an array whose element at position
i is the sum of the input’s elements at positions 0 through i.
func runningTotals(_ values: [Int]) -> [Int] {
// your code here
}
Show solution
func runningTotals(_ values: [Int]) -> [Int] {
var totals: [Int] = []
var sum = 0
for value in values {
sum += value
totals.append(sum)
}
return totals
}
assert(runningTotals([1, 2, 3, 4]) == [1, 3, 6, 10])
assert(runningTotals([5]) == [5])
assert(runningTotals([]) == [])[Int] after var totals: is needed because an empty array literal alone
does not say what type of element it will hold.
What’s next
Next lesson: containers that are not ordered by position, dictionaries and sets, and the tuples that hold a few values together.