Swift Programming from Scratch
The Swift Sandbox is integrated, making the exercises interactive. Read more about the book here.
Chapter 11: Dictionaries
A dictionary is an unordered collection that stores multiple values of the same type. Each value from the dictionary is asociated with a unique key. All the keys have the same type.
The type of a dictionary is determined by the type of the keys and the type of the values. A dictionary of type[String:Int]
has keys of type String
and values of type Int
.
Declare Dictionaries
To declare a dictionary you can use the square brackets syntax([KeyType:ValueType]
).
var dictionary: [String:Int]
You can initialize a dictionary with a dictionary literal. A dictionary literal is a list of key-value pairs, separated by commas, surrounded by a pair of square brackets. A key-value pair is a combination of a key and a value separate by a colon(:).
[key
:value
, key
:value
, ...
]
var dictionary: [String:Int] = [
"one" : 1,
"two" : 2,
"three" : 3
]
Keep in mind that you can create empty dictionary using the empty dictionary literal ([:]
).
var emptyDictionary: [Int:Int] = [:]
Getting values
You can access specific elements from a dictionary using the subscript syntax. To do this pass the key of the value you want to retrieve within square brackets immediately after the name of the dictionary. Because it’s possible not to have a value associated with the provided key the subscript will return an optional value of the value type.
To unwrap the value returned by the subscript you can do one of two things: use optional binding or force the value if you know for sure it exists.
var stringsAsInts: [String:Int] = [
"zero" : 0,
"one" : 1,
"two" : 2,
"three" : 3,
"four" : 4,
"five" : 5,
"six" : 6,
"seven" : 7,
"eight" : 8,
"nine" : 9
]
stringsAsInts["zero"] // Optional(0)
stringsAsInts["three"] // Optional(3)
stringsAsInts["ten"] // nil
// Unwaraping the optional using optional binding
if let twoAsInt = stringsAsInts["two"] {
print(twoAsInt) // 2
}
// Unwaraping the optional using the forced value operator (!)
stringsAsInts["one"]! // 1
To get all the values from a dictionary you can use the for-in
syntax. It’s similar to the array for in syntax with the exception that instead of getting only the value in each step you also get the key associated with that value inside of a tuple.
var userInfo: [String: String] = [
"first_name" : "Andrei",
"last_name" : "Puni",
"job_title" : "Mad scientist"
]
for (key, value) in userInfo {
print("\(key): \(value)")
}
To get the number of elements (key-value pairs) in a dictionary you can use the count
property.
print(userInfo.count) // 3
Updating values
The simplest way to add a value to a dictionary is by using the subscript syntax:
var stringsAsInts: [String:Int] = [
"zero" : 0,
"one" : 1,
"two" : 2
]
stringsAsInts["three"] = 3
Using the subscript syntax you can change a the value associated with a key:
stringsAsInts["three"] = 10
You can use the updateValue(forKey:)
method to update the value associated with a key, if there was no value for that key it will be added. The method will return the old value wrapped in an optional or nil if there was no value before.
var stringsAsInts: [String:Int] = [
"zero" : 0,
"one" : 1,
"two" : 2
]
stringsAsInts.updateValue(3, forKey: "three") // nil
stringsAsInts.updateValue(10, forKey: "three") // Optional(3)
To remove a value from the dictionary you can use the subscript syntax to set the value to nil, or theremoveValueForKey()
method.
stringsAsInts["three"] = nil
stringsAsInts.removeValueForKey("three")
Type Inference
Thanks to Swift’s type inference, you don’t have to declare the type of a dictionary if you initialize it with something other than an empty dictionary literal([:]
).
// powersOfTwo will have the type [Int:Int]
var powersOfTwo = [
1 : 2,
2 : 4,
3 : 8,
4 : 16
]
// userInfo will have the type [String:String]
var userInfo = [
"first_name" : "Silviu",
"last_name" : "Pop",
"job_title" : "evil genius"
]
Copy Behavior
Swift’s dictionaries are value types. This means that dictionaries are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.
var stringsAsInts: [String:Int] = [
"zero" : 0,
"one" : 1,
"two" : 2
]
var justACopy = stringsAsInts
justACopy["zero"] = 100
print(stringsAsInts) // [zero: 0, one: 1, two: 2]
print(justACopy) // [zero: 100, one: 1, two: 2]
Keep in mind that this is not true for Objective-C dictionaries (NSDictionary
and NSMutableDictionary
).
Mutability
If you create a dictionary and assign it to a variable, the collection that is created will be mutable. This means that you can change (or mutate) the collection after it is created by adding, removing, or changing items in the collection. Conversely, if you assign a dictionary to a constant, that array or dictionary is immutable, and its size and contents cannot be changed. In other words if you want to be able to change a dictionary declare it using the var
keyword, and if you don’t want to be able to change it use the let
keyword.
var stringsAsInts: [String:Int] = [
"zero" : 0,
"one" : 1,
"two" : 2
]
stringsAsInts["three"] = 3 // [zero: 0, one: 1, two: 2, three: 3]
stringsAsInts["zero"] = nil // [one: 1, two: 2, three: 3]
let powersOfTwo = [
1 : 2,
2 : 4,
3 : 8,
4 : 16
]
// this will give a runtime error because powersOfTwo is immutable
powersOfTwo[5] = 32
powersOfTwo.removeValueForKey(1) // this will give a similar error
11.1 Encode
You are given a dictionary code
of type [String:String]
which has values for all lowercase letters. The code
dictionary represents a way to encode a message. For example if code["a"] = "z"
and code["b"] = "x"
the encoded version if "ababa"
will be "zxzxz"
. You are also given a message
which contains only lowercase letters and spaces. Use the code
dictionary to encode the message and print it.
Input:
var code = [
"a" : "b",
"b" : "c",
"c" : "d",
"d" : "e",
"e" : "f",
"f" : "g",
"g" : "h",
"h" : "i",
"i" : "j",
"j" : "k",
"k" : "l",
"l" : "m",
"m" : "n",
"n" : "o",
"o" : "p",
"p" : "q",
"q" : "r",
"r" : "s",
"s" : "t",
"t" : "u",
"u" : "v",
"v" : "w",
"w" : "x",
"x" : "y",
"y" : "z",
"z" : "a"
]
var message = "hello world"
Output:
ifmmp xpsme
Input:
var code = [
"a" : "b",
"b" : "c",
"c" : "d",
"d" : "e",
"e" : "f",
"f" : "g",
"g" : "h",
"h" : "i",
"i" : "j",
"j" : "k",
"k" : "l",
"l" : "m",
"m" : "n",
"n" : "o",
"o" : "p",
"p" : "q",
"q" : "r",
"r" : "s",
"s" : "t",
"t" : "u",
"u" : "v",
"v" : "w",
"w" : "x",
"x" : "y",
"y" : "z",
"z" : "a"
]
var message = "wow this problem is hard"
Output:
xpx uijt qspcmfn jt ibse
If a character doesn’t have a corresponding encoded character leave it unchanged.
Build the encoded message step by step by getting the corresponding encoded character from the dictionary.
var code = [
"a" : "b",
"b" : "c",
"c" : "d",
"d" : "e",
"e" : "f",
"f" : "g",
"g" : "h",
"h" : "i",
"i" : "j",
"j" : "k",
"k" : "l",
"l" : "m",
"m" : "n",
"n" : "o",
"o" : "p",
"p" : "q",
"q" : "r",
"r" : "s",
"s" : "t",
"t" : "u",
"u" : "v",
"v" : "w",
"w" : "x",
"x" : "y",
"y" : "z",
"z" : "a"
]
var message = "hello world"
var encodedMessage = ""
for char in message.characters {
var character = "\(char)"
if let encodedChar = code[character] {
// letter
encodedMessage += encodedChar
} else {
// space
encodedMessage += character
}
}
print(encodedMessage)
11.2 Decode
You are given a dictionary code
of type [String:String]
which has values for all lowercase letters. The code
dictionary represents a way to encode a message. For example if code["a"] = "z"
and code["b"] = "x"
the encoded version if "ababa"
will be "zxzxz"
. You are also given a encodedMessage
which contains only lowercase letters and spaces. Use the code
dictionary to decode the message and print it.
Input:
var code = [
"a" : "b",
"b" : "c",
"c" : "d",
"d" : "e",
"e" : "f",
"f" : "g",
"g" : "h",
"h" : "i",
"i" : "j",
"j" : "k",
"k" : "l",
"l" : "m",
"m" : "n",
"n" : "o",
"o" : "p",
"p" : "q",
"q" : "r",
"r" : "s",
"s" : "t",
"t" : "u",
"u" : "v",
"v" : "w",
"w" : "x",
"x" : "y",
"y" : "z",
"z" : "a"
]
var encodedMessage = "uijt nfttbhf jt ibse up sfbe"
Output:
this message is hard to read
You’ll have to invert the code dictionary. Create a new dictionary for this.
var code = [
"a" : "b",
"b" : "c",
"c" : "d",
"d" : "e",
"e" : "f",
"f" : "g",
"g" : "h",
"h" : "i",
"i" : "j",
"j" : "k",
"k" : "l",
"l" : "m",
"m" : "n",
"n" : "o",
"o" : "p",
"p" : "q",
"q" : "r",
"r" : "s",
"s" : "t",
"t" : "u",
"u" : "v",
"v" : "w",
"w" : "x",
"x" : "y",
"y" : "z",
"z" : "a"
]
var encodedMessage = "uijt nfttbhf jt ibse up sfbe"
var decoder: [String:String] = [:]
// reverse the code
for (key, value) in code {
decoder[value] = key
}
var decodedMessage = ""
for char in encodedMessage.characters {
var character = "\(char)"
if let encodedChar = decoder[character] {
// letter
decodedMessage += encodedChar
} else {
// space
decodedMessage += character
}
}
print(decodedMessage)
11.3 Names
You are given an array of dictionaries. Each dictionary in the array contains exactly 2 keys “firstName” and “lastName”. Create an array of strings called firstNames
that contains only the values for “firstName” from each dictionary.
Input:
var people: [[String:String]] = [
[
"firstName": "Calvin",
"lastName": "Newton"
],
[
"firstName": "Garry",
"lastName": "Mckenzie"
],
[
"firstName": "Leah",
"lastName": "Rivera"
],
[
"firstName": "Sonja",
"lastName": "Moreno"
],
[
"firstName": "Noel",
"lastName": "Bowen"
]
]
Expected values:
firstNames = ["Calvin","Garry","Leah","Sonja","Noel"]
var people: [[String:String]] = [
[
"firstName": "Calvin",
"lastName": "Newton"
],
[
"firstName": "Garry",
"lastName": "Mckenzie"
],
[
"firstName": "Leah",
"lastName": "Rivera"
],
[
"firstName": "Sonja",
"lastName": "Moreno"
],
[
"firstName": "Noel",
"lastName": "Bowen"
]
]
var firstNames: [String] = []
for person in people {
if let firstName = person["firstName"] {
print(firstName)
firstNames.append(firstName)
}
}
Keep in mind that persons is an array of dictionaries, you’ll have to process this array to get the required data.
11.4 Full names
You are given an array of dictionaries. Each dictionary in the array contains exactly 2 keys “firstName” and “lastName”. Create an array of strings called fullNames
that contains the values for “firstName” and “lastName” from the dictionary separated by a space.
Input:
var people: [[String:String]] = [
[
"firstName": "Calvin",
"lastName": "Newton"
],
[
"firstName": "Garry",
"lastName": "Mckenzie"
],
[
"firstName": "Leah",
"lastName": "Rivera"
],
[
"firstName": "Sonja",
"lastName": "Moreno"
],
[
"firstName": "Noel",
"lastName": "Bowen"
]
]
Expected values:
fullNames = ["Calvin Newton","Garry Mckenzie","Leah Rivera",
"Sonja Moreno","Noel Bowen"]
var people: [[String:String]] = [
[
"firstName": "Calvin",
"lastName": "Newton"
],
[
"firstName": "Garry",
"lastName": "Mckenzie"
],
[
"firstName": "Leah",
"lastName": "Rivera"
],
[
"firstName": "Sonja",
"lastName": "Moreno"
],
[
"firstName": "Noel",
"lastName": "Bowen"
]
]
var fullNames: [String] = []
for person in people {
if let firstName = person["firstName"] {
if let lastName = person["lastName"] {
let fullName = "\(firstName) \(lastName)"
fullNames.append(fullName)
}
}
}
var people: [[String:String]] = [
[
"firstName": "Calvin",
"lastName": "Newton"
],
[
"firstName": "Garry",
"lastName": "Mckenzie"
],
[
"firstName": "Leah",
"lastName": "Rivera"
],
[
"firstName": "Sonja",
"lastName": "Moreno"
],
[
"firstName": "Noel",
"lastName": "Bowen"
]
]
var fullNames: [String] = []
for person in people {
var fullName = " "
for (key, value) in person {
if key == "lastName" {
fullName += value
} else {
fullName = value + fullName
}
}
fullNames += [fullName]
}
print(fullNames)
11.5 Best score
You are given an array of dictionaries. Each dictionary in the array describes the score of a person. Find the person with the best score and print his full name.
Input:
var people: [[String:Any]] = [
[
"firstName": "Calvin",
"lastName": "Newton",
"score": 13
],
[
"firstName": "Garry",
"lastName": "Mckenzie",
"score": 23
],
[
"firstName": "Leah",
"lastName": "Rivera",
"score": 10
],
[
"firstName": "Sonja",
"lastName": "Moreno",
"score": 3
],
[
"firstName": "Noel",
"lastName": "Bowen",
"score": 16
]
]
Output:
Garry Mckenzie
Keep track of the person with the best score that you’ve encountered.
var people: [[String:Any]] = [
[
"firstName": "Calvin",
"lastName": "Newton",
"score": 13
],
[
"firstName": "Garry",
"lastName": "Mckenzie",
"score": 23
],
[
"firstName": "Leah",
"lastName": "Rivera",
"score": 10
],
[
"firstName": "Sonja",
"lastName": "Moreno",
"score": 3
],
[
"firstName": "Noel",
"lastName": "Bowen",
"score": 16
]
]
var topPerson = people[0]
var bestScore = topPerson["score"] as! Int
for person in people {
if let score = person["score"] as? Int {
if bestScore < score {
bestScore = score
topPerson = person
}
}
}
if let first = topPerson["firstName"] as? String,
let second = topPerson["lastName"] as? String {
print("\(first) \(second)")
}
11.6 Leaderboard
You are given an array of dictionaries. Each dictionary in the array describes the score of a person. Print the leaderboard in the following format:
1. full name - score
2. ...
...
Input:
var people: [[String:Any]] = [
[
"firstName": "Calvin",
"lastName": "Newton",
"score": 13
],
[
"firstName": "Garry",
"lastName": "Mckenzie",
"score": 23
],
[
"firstName": "Leah",
"lastName": "Rivera",
"score": 10
],
[
"firstName": "Sonja",
"lastName": "Moreno",
"score": 3
],
[
"firstName": "Noel",
"lastName": "Bowen",
"score": 16
]
]
Output:
1. Garry Mckenzie - 23
2. Noel Bowen - 16
3. Calvin Newton - 13
4. Leah Rivera - 10
5. Sonja Moreno - 3
Sort the list of people using a function that compares two dictionaries.
var people: [[String:Any]] = [
[
"firstName": "Calvin",
"lastName": "Newton",
"score": 13
],
[
"firstName": "Garry",
"lastName": "Mckenzie",
"score": 23
],
[
"firstName": "Leah",
"lastName": "Rivera",
"score": 10
],
[
"firstName": "Sonja",
"lastName": "Moreno",
"score": 3
],
[
"firstName": "Noel",
"lastName": "Bowen",
"score": 16
]
]
func compareScores(_ first: [String:Any], second: [String:Any]) -> Bool {
if let a = first["score"] as? Int {
if let b = second["score"] as? Int {
return a > b
}
}
return false
}
people.sort(by: compareScores)
for (index, person) in people.enumerated() {
if let firstName = person["firstName"] as? String {
if let lastName = person["lastName"] as? String {
if let score = person["score"] as? Int {
print("\(index + 1). \(firstName) \(lastName) - \(score)")
}
}
}
}
11.7 Frequency
You are given an array of integers. Find out the frequency of each one.
The frequency of a number is the number of times it appears in the array.
Print the numbers in ascending order followed by their frequency.
Input:
var numbers = [1, 2, 3, 2, 3, 5, 2, 1, 3, 4, 2, 2, 2]
Output:
1 2
2 6
3 3
4 1
5 1
Use a dictionary to keep track of the frequency.
Keep track of all the unique numbers of the array
var numbers = [1, 2, 3, 2, 3, 5, 2, 1, 3, 4, 2, 2, 2]
var frequency: [Int:Int] = [:]
var uniqueNumbers: [Int] = []
for number in numbers {
if let oldFr = frequency[number] {
frequency[number] = oldFr + 1
} else {
uniqueNumbers.append(number)
frequency[number] = 1
}
}
uniqueNumbers.sort(by: <)
for number in uniqueNumbers {
print("\(number) \(frequency[number]!)")
}
Swift Programming from Scratch
Read more about the book here.
Feel free to ask any questions in the comments bellow.
The “a” is a typo?
var dictionary: [String:Int] = [a
“one” : 1,
“two” : 2,
“three” : 3
]
Thank you!
Thank you for the really helpful exercises! Could you please explain the as? Int component of 11.5? Thanks!
11.6 – Leaderboard Solution
The compareScores function definition does not compile, I think you meant :
func compareScores(first: [String:Any], second: [String:Any]) -> Bool {
// code
}
hi can we create dictionary and arrays as optionals?
sth like var Dict:[String:CGFloat]?
I don’t see why not
In the last solution:
”
uniqueNumbers.sort(<)
for number in uniqueNumbers {
println("\(number) \(frequency[number]!)")
}
"
Will unique.Numbers.sort(<) actually affect the variable?
Shouldn't it rather be inside the for-loop? Like so:
for number in uniqueNumbers.sort(<) {
… }
uniqueNumbers.sort(<) will affect the variable. If you want to create a new sorted array without affecting the variable you can use the "sorted" method.Note that in Swift 2.0 this behaviour will change. "sort" will work like "sorted" (producing a new array instead of affecting the variable). There's a new method "sortInPlace" that affects the variable without producing a new array.
What about ‘Class’ Syntax..
We have a different section for Object Oriented Programming – https://www.weheartswift.com/object-oriented-programming-swift/
This section focuses on learning the basics of programming.
Hope this helps! Happy coding
yea Thanks..
>// this will give a runtime error because powersOfTwo is immutable
>powersOfTwo[5] = 32
This is a compile time error. Not runtime?
yes. powersOfTwo is a constant and cannot be mutated.
My solution for 11.6 leaderBoard:
https://gist.github.com/mattyoung/49ebd775801d877d9bb7
Using an Array of [String:Any] as data structure require a lot of type casting from Any to the actual type. Change the array to hold a struct of specific type climate the type casting.
https://gist.github.com/mattyoung/b7cae1c39d7d0f21fae6
A dictionary may be copied but if it’s values are reference types they will still be referenced, not copied. I found this out with [String: [SCNNode]] dictionaries after losing some nodes from the original as well as the so-called-copy.
very nice tutorial thank you
Hi, you have errors/bugs throughout the Dictionaries tutorial. Problems 11.4 to 11.6 will not execute. It’s saying “internal error”
On problem 11.7 it’s saying I got the correct answer, but when comparing my results with the expected results, I clearly got it wrong.
Actually, I might have gotten it right on 11.7. My answers are correct, just not in the same order. Does this mean that when you add keys and values to a dictionary it stores them in random order?