Swift Programming from Scratch
The Swift Sandbox is integrated, making the exercises interactive. Read more about the book here.
Chapter 10: Tuples & Enums
Tuples
A tuple is a group of zero or more values represented as one value.
For example ("John", "Smith")
holds the first and last name of a person. You can access the inner values using the dot(.
) notation followed by the index of the value:
var person = ("John", "Smith")
var firstName = person.0 // John
var lastName = person.1 // Smith
Named elements
You can name the elements from a tuple and use those names to refer to them. An element name is an identifier followed by a colon(:).
var person = (firstName: "John", lastName: "Smith")
var firstName = person.firstName // John
var lastName = person.lastName // Smith
Creating a tuple
You can declare a tuple like any other variable or constant. To initialize it you will need a another tuple or a tuple literal. A tuple literal is a list of values separated by commas between a pair of parentheses. You can use the dot notation to change the values from a tuple if it’s declared as a variable.
var point = (0, 0)
point.0 = 10
point.1 = 15
point // (10, 15)
Note: Tuple are value types. When you initialize a variable tuple with another one it will actually create a copy.
var origin = (x: 0, y: 0)
var point = origin
point.x = 3
point.y = 5
print(origin) // (0, 0)
print(point) // (3, 5)
Types
The type of a tuple is determined by the values it has. So ("tuple", 1, true)
will be of type (String, Int, Bool)
. You can have tuples with any combination of zero or more types.
If the tuple has only one element then the type of that tuple is the type of the element. (Int)
is the same as Int
. This has a strange implication: in swift every variable or constant is a tuple.
var number = 123
print(number) // 123
print(number.0) // 123
print(number.0.0) // 123
print(number.0.0.0) // 123
print(number.0.0.0.0.0.0.0) // 123
Empty tuple
()
is the empty tuple – it has no elements. It also represents the Void
type.
Decomposing Tuples
var person = (firstName: "John", lastName: "Smith")
var (firstName, lastName) = person
var (onlyFirstName, _) = person
var (_, onlyLastName) = person
Note: the _
means “I don’t care about that value”
Multiple assignment
You can use tuples to initialize more than one variable on a single line:
Instead of:
var a = 1
var b = 2
var c = 3
you can write:
var (a, b, c) = (1, 2, 3)
Instead of:
a = 1
b = 2
c = 3
you can write:
(a, b, c) = (1, 2, 3)
And yes! One line swap:
(a, b) = (b, a)
Returning multiple values
You can return multiple values from a function if you set the result type to a tuple. Here is a simple example of a function that return the quotient and the remainder of the division of a
by b
.
func divmod(_ a: Int, _ b:Int) -> (Int, Int) {
return (a / b, a % b)
}
divmod(7, 3) // (2, 1)
divmod(5, 2) // (2, 1)
divmod(12, 4) // (3, 0)
Or the named version:
func divmod(_ a: Int, _ b:Int) -> (quotient: Int, remainder: Int) {
return (a / b, a % b)
}
divmod(7, 3) // (quotient: 2, remainder:1)
divmod(5, 2) // (quotient: 2, remainder:1)
divmod(12, 4) // (quotient: 3, remainder:0)
Enums
An enumeration is a data type consisting of a set of named values, called members.
Defining an enumeration
You can define a new enumeration using the enum
keyword followed by it’s name. The member values are introduced using the case
keyword.
enum iOSDeviceType {
case iPhone
case iPad
case iWatch
}
var myDevice = iOSDeviceType.iPhone
Dot syntax
If the type of an enumeration is known or can be inferred then you can use the dot syntax for members.
// in this case the type is known
var myDevice: iOSDeviceType = .iPhone
// in this case the type can be inferred
if myDevice == .iPhone {
print("I have an iPhone!")
}
Associated Values
Swift enumerations can store associated values of any type, and the value type can be different for each member. For example you might want to store a device model for the iPhone and iPad (like "mini"
for the iPad, or "6 Plus"
for the iPhone).
enum iOSDeviceType {
case iPhone(String)
case iPad(String)
case iWatch
}
You can get the associated values by using a switch statement:
var myDevice = iOSDeviceType.iPhone("6")
switch myDevice {
case .iPhone(let model):
print("iPhone \(model)")
case .iPad(let model):
print("iPad \(model)")
case .iWatch:
print("iWatch")
default:
print("not an iOS device")
}
// iPhone 6
Note: Swift does not provide equality operators automatically for enumerations with associated values. You might be tempted to use nested switch statements in order to test equality. Don’t forget the tuple pattern!
var myDevice = iOSDeviceType.iPhone("6")
var six = iOSDeviceType.iPhone("6")
var sixPlus = iOSDeviceType.iPhone("6 Plus")
// testing equlity with == wont work
// myDevice == six
// myDevice == sixPlus
func sameDevice(_ firstDevice: iOSDeviceType,
secondDevice: iOSDeviceType) -> Bool {
switch (firstDevice, secondDevice) {
case (.iPhone(let a), .iPhone(let b)) where a == b:
return true
case (.iPad(let a), .iPad(let b)) where a == b:
return true
case (.iWatch, .iWatch):
return true
default:
return false
}
}
print(sameDevice(myDevice, six)) // true
print(sameDevice(myDevice, sixPlus)) // false
print(sameDevice(myDevice, .iWatch)) // false
Raw Values
Enums can have a raw value (a primitive type – Int, String, Character, etc.) associated with each member. The raw value will be of the same type for all members and the value for each member must be unique. When integers are use they autoincrement is a value is not defined for a member.
enum Direction: Int {
case up = 1
case down // will have the raw value 2
case left // will have the raw value 3
case right // will have the raw value 4
}
You can use raw values to create a enumeration value.
var direction = Direction(rawValue: 4) // .Right
print(direction) // Optional((Enum Value))
Note: Because not all raw values have an associated member value the raw value initializer is a failable initializer. The type of direction
is Direction?
not Direction
.
10.1 Game
You are working on a game in which your character is exploring a grid-like map. You get the original location
of the character and the steps
he will take.
A step .Up
will increase the x coordinate by 1. A step .Down
will decrease the x coordinate by 1. A step .Right
will increase the y coordinate by 1. A step .Left
will decrease the y coordinate by 1.
Print the final location
of the character after all the steps have been taken.
Input:
var location = (x: 0, y: 0)
var steps: [Direction] = [.up, .up, .left, .down, .left]
Output:
(-2, 1)
Input:
var location = (x: 0, y: 0)
var steps: [Direction] = [.up, .up, .left, .down, .left, .down, .down,
.right, .right, .down, .right]
Output:
(1, -2)
Input:
var location = (x: 5, y: 2)
var steps: [Direction] = [.up, .right, .up, .right, .up,
.right, .down, .right]
Output:
(9, 4)
Use a switch statement.
Modify the location
tuple based on what case you’re handling in theswitch
statement.
enum Direction {
case up
case down
case left
case right
}
var location = (x: 0, y: 0)
var steps: [Direction] = [.up, .up, .left, .down, .left]
for step in steps {
switch step {
case .up:
location.y += 1
case .down:
location.y -= 1
case .right:
location.x += 1
case .left:
location.x -= 1
default:
break
}
}
print(location)
10.2 Min Max
Write a function named minmax
that takes two integers and returns both the minimum and the maximum values inside a tuple.
Function call:
minmax(2, 3)
Function output:
(2, 3)
Function call:
minmax(5, 1)
Function output:
(1, 5)
Function call:
minmax(3, 3)
Function output:
(3, 3)
func minmax(_ a: Int, _ b: Int) -> (Int, Int)
A single comparison is enough to determine both the minimum and the maximum value.
func minmax(_ a: Int, _ b: Int) -> (Int, Int) {
if a < b {
return (a, b)
} else {
return (b, a)
}
}
10.3 Rock, Paper, Scissors
1) Define an enumeration named HandShape
with three members: .rock
, .paper
, .scissors
.
2) Define an enumeration named MatchResult
with three members: .win
, .draw
, .lose
.
3) write a function called match
that takes two hand shapes and returns a match result. It should return the outcome for the first player (the one with the first hand shape).
func match(_ first: HandShape, _ second: HandShape) -> MatchResult
Function call:
match(.rock, .scissors)
Function output:
.win
Function call:
match(.rock, .paper)
Function output:
.lose
Function call:
match(.scissors, .scissors)
Function output:
.draw
Handle the case when the hands result in a draw first.
Determine if a win has occurred.
enum HandShape {
case rock
case paper
case scissors
}
enum MatchResult {
case win
case draw
case lose
}
func match(_ first: HandShape, _ second: HandShape) -> MatchResult {
if first == second {
return .draw
}
if first == .rock && second == .scissors {
return .win
}
if first == .paper && second == .rock {
return .win
}
if first == .scissors && second == .paper {
return .win
}
return .lose
}
10.4 Fractions
You are given 2 tuples of a
, b
type (Int,Int)
representing fractions. The first value in the tuple represents the numerator, the second value represents the denominator. Create a new tuple sum
of type (Int,Int)
that holds the sum of the fractions.
Input:
var a = (5,8)
var b = (17,9)
Expected Value:
sum = (181, 72)
Input:
var a = (34,3)
var b = (11,2)
Expected Value:
sum = (101, 6)
To add 2 fractions together you have to get them to a common denominator.
var a = (5,8)
var b = (17,9)
let numerator = a.0 * b.1 + b.0 * a.1
let denominator = a.1 * b.1
var sum = (numerator, denominator)
10.5 Money
You are given the CoinType
enumeration which describes different coin values and moneyArray
which has tuples(ammount, coinType)
. Print the total value of the coins in the array.
Input:
var moneyArray:[(Int,CoinType)] = [(10,.penny),
(15,.nickle),
(3,.quarter),
(20,.penny),
(3,.dime),
(7,.quarter)]
Output:
385
Input:
var moneyArray:[(Int,CoinType)] = [
(2,.penny),
(3,.quarter)
]
Output:
77
Input:
var moneyArray:[(Int,CoinType)] = [
(5, .dime),
(2, .quarter),
(1, .nickle)
]
Output:
105
Remember that .rawValue
gets the numeric value associated with an enum value.
enum CoinType: Int {
case penny = 1
case nickle = 5
case dime = 10
case quarter = 25
}
var moneyArray:[(Int,CoinType)] = [(10,.penny),
(15,.nickle),
(3,.quarter),
(20,.penny),
(3,.dime),
(7,.quarter)]
var totalMoney = 0
for (amount, coinType) in moneyArray {
totalMoney += amount * coinType.rawValue
}
print(totalMoney)
10.6 Counting Strings
You are given an array of strings stored in the variable strings
. Create a new array named countedStrings
containing values of type (String,Int)
. Each tuple contains a string from the strings
array followed by an integer indicating how many times it appears in the strings
array. Each string should only appear once in thecountedStrings
array.
Input:
var strings = ["tuples", "are", "awesome", "tuples", "are", "cool",
"tuples", "tuples", "tuples", "shades"]
Expected Value:
countedStrings = [
"tuples" : 5,
"are" : 2,
"awesome" : 1,
"cool" : 1,
"shades" : 1
]
Input:
var strings = ["hello", "world", "hello", "swift", "hello", "tuples"]
Expected Value:
countedStrings = [
"hello" : 3,
"world" : 1,
"swift" : 1,
"tuples" : 1
]
Keep in mind that you can’t change a tuple when iterating an array using the for in syntax. You’ll have to iterate using an index.
var strings = ["tuples", "are", "awesome", "tuples", "are", "cool",
"tuples", "tuples", "tuples", "shades"]
var countedStrings: [(String,Int)] = []
for string in strings {
var alreadyExists = false
for i in 0..<countedStrings.count {
if (countedStrings[i].0 == string) {
countedStrings[i].1 += 1
alreadyExists = true
}
}
if alreadyExists == false {
let tuple = (string,1)
countedStrings.append((string,1))
}
}
Swift Programming from Scratch
Read more about the book here.
Feel free to ask any questions in the comments bellow.
My solution. Don’t look if you don’t want to see.
Using a dictionary:
var strings = [“tuples”, “are”, “awesome”, “tuples”, “are”, “cool”, “tuples”, “tuples”, “tuples”, “shades”, “awesome”]
var countedStrings: [(String, Int)] = []
var counters: [String: Int] = [:]
for s in strings {
if let count = counters[s] {
counters[s] = count + 1
} else {
counters[s] = 1
}
}
for (key, value) in counters {
countedStrings.append((key, value))
}
print(countedStrings)
Makes sense to use reduce for the coins…
print(moneyArray.reduce(0, {$0+$1.0*$1.1.rawValue}))
Exercise 10.3 — seems to have a problem. I had a solution very similar to the posted solution, but it says the function is not defined correctly. If I paste it in another Xcode playground and use the examples, I get the intended results. If I copy the solution code into the exercise, I get the same error message. I’m going to assume there is something wrong with this one on the platform and move on (but that kills me not to check the box!)