Last active
July 27, 2023 06:53
-
-
Save billbonney/0968920dc29b7d0a677f to your computer and use it in GitHub Desktop.
A Swift Tour - The Swift Programming Language - Solutions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright © 2023 Bill Bonney. All rights reserved. | |
// | |
// The Swift Programming Language: Swift by Tutorials (solutions) | |
// Author: Bill Bonney <billbonney (at) communistech.com> | |
// | |
// This is all the code in The Swift Tour section in the Apple Book on Swift. | |
// This should allow you to play with the code and see behaviours, and help | |
// solve any paticulars you get stuck on when doing the Experiments. | |
// | |
// Please feel free to comment on any part, I am by no means an expert in Swift ;-) | |
// | |
// NOTE: Not all code is identical (since I was tinkering, but should help) | |
import Cocoa // This is all 'pure' swift, no Math libs are used. Uncomment if you want to see the difference | |
// UPDATED to Swift 5: Just because after a decade it seems appropriate! | |
// | |
// Simple Values | |
// | |
var myVariable = 42 | |
myVariable = 50 | |
let myConstant = 42 | |
let implicit = 70 | |
let implicitDoube = 70.0 | |
let explicitDouble: Double = 20.3 | |
// Experiment: Constant Float of value 4 | |
// Note: I made it 20.3 to show the general rounding errors in floats | |
// SIDENOTE: always use doubles on 64Bit systems ;) | |
let explicitFloat: Float = 20.3 | |
let label = "The width is " | |
let width = 94 | |
let widthLabel = label + String(width) | |
// Experiment: Try removing the String conversion | |
// let widthLabel = label + width // (uncomment as it will error otherwise) | |
let apples = 3 | |
let oranges = 5 | |
let appleSummary = "I have \(apples) apples." | |
let orangeSummary = "I have \(oranges) oranges." | |
// Experiment: Use \() notation to include a floating point calculation | |
let greetingPeople = "Hello People \(3 * 9)" | |
var shoppingList = ["redfish", "yellowfish", "bluefish"] | |
shoppingList[1] | |
var occupations = [ | |
"Malcolm": "capitan", | |
"Kaylee": "mechanic" | |
] | |
occupations["John"] = "domestic engineer" | |
let emptyArray = [String()] | |
let emptyDictionary = Dictionary<String,Float>() | |
shoppingList = [] | |
// | |
// Control Flow | |
// | |
let individualScores = [75, 43, 103, 87, 12] | |
var teamScore = 0 | |
for score in individualScores { | |
if score > 50 { | |
teamScore += 3 | |
} else { | |
teamScore += 1 | |
} | |
} | |
teamScore | |
var optionalString: String? = "Hello" | |
optionalString == nil | |
var optionalName: String? = "Fred Shed" | |
// Experiment: change optional name to nil | |
// NOTE: Comment out line 66 and try.. | |
// var optionalName: String? = nil | |
// OR | |
// var optionalName: String? | |
var greeting = "Hello!" | |
if let name = optionalName { | |
greeting = "Hello \(name)" | |
} else { | |
greeting = "Who goes there?" | |
} | |
greeting | |
let vegetable = "red pepper" | |
switch vegetable { | |
case "celery": | |
let vegetableComment = "Add some raisins" | |
case "cucumber","watercress": | |
let vegetableComment = "That would make a nice cucumber sarnie." | |
case let x where x.hasSuffix("pepper"): // Whah: Need to read up on this mystery x. | |
let vegetableComment = "Is it spicy \(x)?" | |
default: | |
let vegetableComment = "Everything tastes good in soup" | |
} | |
//Experiment: Remove default case (see above) | |
let interestingNumbers = [ | |
"Pirme": [2,3,5,7,11,13], | |
"Fibonacci": [1,1,2,3,5,8], | |
"Square": [1, 4, 9, 16, 25], | |
] | |
// Experiment: Add another variable to keep track of which kind of number was the largest. | |
// NOTE: Also added code for the smallest | |
var largest = 0 | |
var largestType: String? | |
var smallestType: String? | |
var smallest = Int.max | |
for (kind, numbers) in interestingNumbers { | |
for number in numbers { | |
if number > largest { | |
largest = number | |
largestType = kind | |
} else if number < smallest { | |
smallest = number | |
smallestType = kind | |
} | |
} | |
} | |
largest | |
largestType | |
smallest | |
smallestType | |
var n = 2 | |
while n < 100 { | |
n = n * 2 | |
} | |
n | |
var m = 2 | |
repeat { | |
m = m * 2 | |
} while m < 100 | |
m | |
var firstForLoop = 0 | |
for i in 0..<10 { | |
firstForLoop += i | |
} | |
firstForLoop | |
// Test to try a 1 to 10 count (sometimes useful to start at 1) | |
var firstForLoop1 = 0 | |
for i in 1...10 { // NOTE: a count from 1 to 10 | |
firstForLoop1 += i | |
} | |
firstForLoop1 | |
var secondForLoop = 0 | |
for i in 1..<10 { | |
secondForLoop += i | |
} | |
secondForLoop | |
// | |
// Functions and Closures | |
// | |
// Experiment: Remove Day parameter and add lunch special greeting | |
// NOTE: I just added lunch the param (D'oh must follow instructions) | |
func greet(name:String, day:String, lunch:String) -> String { | |
return "Helllo \(name), today is \(day) and lunch will be \(lunch)" | |
} | |
greet(name: "Fred", day: "Monday", lunch: "eggs") | |
greet(name: "Wally", day: "Thursday", lunch: "surf'n'turf") | |
func getGasPrices() -> (Double,Double,Double) { | |
return (1.35,145,155) // Note: Prices are in (CAD$/L) | |
} | |
getGasPrices() | |
func sumOf(numbers: Int...) -> Int { | |
var sum = 0 | |
for number in numbers { | |
sum += number | |
} | |
return sum | |
} | |
sumOf() | |
sumOf(numbers: 42,597,12) | |
// Experiment: Create a function that creates the average of the args | |
func average(numbers:Double...) -> Double { | |
var sum = 0.0 | |
for number in numbers { | |
sum += number | |
} | |
return sum/Double(numbers.count) | |
} | |
average(numbers: 10.0,20.0,30.0) | |
func returnFifteen() -> Int { | |
var y = 10 | |
func add() { | |
y += 5 | |
} | |
add() | |
return y | |
} | |
returnFifteen() | |
func makeIncrementer() -> ((Int) -> Int) { | |
func addOne(number: Int) -> Int { | |
return 1+number | |
} | |
return addOne | |
} | |
var increment = makeIncrementer() | |
increment(7) | |
func hasAnyMatches(list: [Int], condition:(Int) -> Bool) -> Bool { | |
for item in list { | |
if condition(item){ | |
return true; | |
} | |
} | |
return false | |
} | |
func lessThanTen(number:Int) -> Bool { | |
print("number is \(number)") | |
return number < 10 | |
} | |
var numbers = [ 10, 19, 7, 12] | |
hasAnyMatches(list: numbers, condition: lessThanTen) | |
numbers.map({ | |
(number: Int) -> Int in | |
let result = 3*number | |
return result | |
}) | |
// Experiment: Show odd numbers as 0 (zero) | |
// Only show even numbers, odd are zero'd | |
numbers.map({ | |
(number: Int) -> Int in | |
if (number % 2) == 0 { | |
return number | |
} | |
return 0 | |
}) | |
let mappedNumbers = numbers.map({ number in 3 * number }) | |
mappedNumbers | |
let sortedNumbers = numbers.sorted { $0 > $1 } | |
sortedNumbers | |
// Objects and Classes | |
class Shape { | |
var numberOfSides = 0; | |
func simpleDescription() -> String { | |
return "A shape with \(numberOfSides) sides." | |
} | |
} | |
var shape = Shape() | |
shape.numberOfSides = 7 | |
var shapeDescription = shape.simpleDescription() | |
class NamedShape { | |
var numberOfSides: Int = 0 | |
var name: String | |
init(name:String) { | |
self.name = name | |
} | |
func simpleDescription() -> String { | |
return "A shape with \(numberOfSides) sides." | |
} | |
} | |
class Square: NamedShape { | |
var sideLength: Double | |
init(sideLength: Double, name: String){ | |
self.sideLength = sideLength | |
super.init(name: name) | |
numberOfSides = 4 | |
} | |
func area() -> Double { | |
return sideLength * sideLength //NOTE: Guessing ^2 is a import math function | |
} | |
override func simpleDescription() -> String { | |
return "A square with sides of length \(sideLength)" | |
} | |
} | |
let test = Square(sideLength: 5.2, name: "my test square") | |
test.area() | |
test.simpleDescription() | |
// Experiment: Make another subclass Circle | |
class Circle : NamedShape { | |
var radius: Double | |
init(radius:Double, name: String){ | |
self.radius = radius | |
super.init(name: name) | |
numberOfSides = 1 | |
} | |
func area() -> Double { | |
return 3.14592 * (radius*radius) | |
// return M_PI * pow(radius, 2) // uncomment when using import cocoa | |
} | |
override func simpleDescription() -> String { | |
return "A circle with a radius of \(radius)" | |
} | |
} | |
var testCircle = Circle(radius: 2, name: "my circle") | |
testCircle.area() | |
testCircle.simpleDescription() | |
class EquilateralTriangle : NamedShape { | |
var sideLength: Double = 0.0 | |
init(sideLength: Double, name: String){ | |
self.sideLength = sideLength | |
super.init(name: name) | |
numberOfSides = 3 | |
} | |
var perimeter: Double { | |
get { | |
return 3.0 * sideLength | |
} | |
set(newPerimeter) { | |
sideLength = newPerimeter / 3.0 | |
} | |
} | |
override func simpleDescription() -> String { | |
return "An equilateral triangle with sides of length \(sideLength)" | |
} | |
} | |
var triangle = EquilateralTriangle(sideLength:3.1, name:"a Triangle") | |
triangle.sideLength | |
triangle.perimeter | |
triangle.perimeter = 9.9 | |
triangle.sideLength | |
triangle.perimeter | |
class TriangleAndSquare { | |
var triangle: EquilateralTriangle { | |
willSet { | |
square.sideLength = newValue.sideLength | |
} | |
} | |
var square: Square{ | |
willSet { | |
triangle.sideLength = newValue.sideLength | |
} | |
} | |
init(size: Double, name: String){ | |
square = Square(sideLength: size, name: name) | |
triangle = EquilateralTriangle(sideLength: size, name: name) | |
} | |
} | |
var triangleAndSquare = TriangleAndSquare(size: 10, name:"another test shape") | |
triangleAndSquare.square.sideLength | |
triangleAndSquare.triangle.sideLength | |
triangleAndSquare.square = Square(sideLength: 50, name: "larger square") | |
triangleAndSquare.triangle.sideLength | |
class Counter { | |
var count: Int = 0 | |
func incrementBy(amount: Int, numberOfTimes times: Int){ | |
count += amount * times | |
} | |
} | |
var counter = Counter() | |
counter.incrementBy(amount: 2, numberOfTimes: 7) | |
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") | |
// NOTE: Try also | |
//let optionalSquare: Square? = nil | |
// OR | |
//let optionalSquare: Square? | |
let sideLength = optionalSquare?.sideLength | |
sideLength | |
// | |
// Enumerations and Structures | |
// | |
enum Rank: Int, Sequence, IteratorProtocol { | |
mutating func next() -> Rank? { | |
if self.rawValue > Self.King.rawValue { | |
return nil | |
} else { | |
return Rank(rawValue: self.rawValue + 1) | |
} | |
} | |
typealias Element = Rank | |
case Ace = 1 | |
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten | |
case Jack, Queen, King | |
func simpleDescritption() -> String { | |
switch self { | |
case .Ace: | |
return "ace" | |
case .Jack: | |
return "jack" | |
case .Queen: | |
return "queen" | |
case .King: | |
return "king" | |
default: | |
return String(self.rawValue) | |
} | |
} | |
} | |
let ace = Rank.Ace | |
let aceRawValue = ace.rawValue | |
// Exepriment: Write a function to compare two Rank values by comparing their raw values | |
// NOTE: got a little carried away ;) | |
let rightHand = Rank.King | |
let leftHand = Rank.King | |
var snap = 0 | |
var handResult: String | |
if rightHand.rawValue < leftHand.rawValue { | |
handResult = "Left Hand Wins!" | |
} else if rightHand.rawValue > leftHand.rawValue { | |
handResult = "Right Hand Wins!" | |
} else { | |
handResult = "Draw!" | |
} | |
if let convertedRank = Rank(rawValue: 3){ | |
let threeDescription = convertedRank.simpleDescritption() | |
} | |
// NOTE: I think choosing a picture card demonstrates this much better | |
if let convertedRank = Rank(rawValue: 1){ | |
let threeDescription = convertedRank.simpleDescritption() | |
} | |
enum Suit { | |
case Spades, Hearts, Diamonds, Clubs | |
func simpleDescription() -> String { | |
switch self { | |
case .Spades: | |
return "spades" | |
case .Hearts: | |
return "hearts" | |
case .Diamonds: | |
return "diamonds" | |
case .Clubs: | |
return "clubs" | |
} | |
} | |
// Experiment: Add a color method to Suit to return "red" or "black" | |
// NOTE: new feature is to group values in one case statement | |
func color() -> String { | |
switch self { | |
case .Hearts, .Diamonds: | |
return "red" | |
case .Clubs, .Spades: | |
return "black" | |
} | |
} | |
} | |
let hearts = Suit.Hearts | |
let heartsDescription = hearts.simpleDescription() | |
let card = Suit.Spades | |
card.color() | |
struct Card: Sequence, IteratorProtocol { | |
mutating func next() -> Card? { | |
guard let nextRank = rank.next() else { | |
return nil | |
} | |
return Card(rank: nextRank, suit: self.suit) | |
} | |
typealias Element = Card | |
var rank: Rank | |
var suit: Suit | |
func simpleDescription() -> String { | |
return "The \(rank.simpleDescritption()) of \(suit.simpleDescription())" | |
} | |
} | |
let threeOfSpades = Card(rank: .Three, suit: .Spades) | |
let thresOfSpadesDescritpion = threeOfSpades.simpleDescription() | |
// Experiment: Add a method to create a full deck to Card (Why to card and not separate) | |
// NOTE: This is a method to create a pack, but it's "not added to card" (?) | |
func newPack() -> [Card] { | |
var pack:[Card] = [] | |
var suits = [Suit.Spades, Suit.Hearts, Suit.Diamonds, Suit.Spades] | |
for suit in suits{ | |
for i in 1...12 { | |
pack += Card(rank: Rank(rawValue: i)!, suit: suit) | |
} | |
} | |
return pack | |
} | |
let pack = newPack() | |
// Experiment: Add third case to ServerResponse and to the switch | |
enum ServerResponse { | |
case Result(String,String) | |
case Error(String) | |
case DaylightSavingsInEffect(Bool) | |
} | |
let success = ServerResponse.Result("6:00am", "8:09pm") | |
let failure = ServerResponse.Error("Out of cheese.") | |
let daylightsavings = ServerResponse.DaylightSavingsInEffect(true) | |
var servResponse = success | |
// NOTE: uncomment the lines below to see how the switch works. | |
//var servResponse = failure | |
//var servResponse = daylightsavings | |
switch servResponse { | |
case let .Result(sunrise, sunset): | |
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)." | |
case let .Error(error): | |
let serverResponse = "Failure... \(error)" | |
case let .DaylightSavingsInEffect(state): | |
var serverRespsonse: String | |
if (state){ | |
serverRespsonse = "Daylight Savings in effect." | |
} else { | |
serverRespsonse = "Standard Time" | |
} | |
} | |
// | |
// Protocols and Extensions | |
// | |
protocol ExampleProtocol { | |
var simpleDescription: String { get } | |
mutating func adjust() | |
} | |
class SimpleClass: ExampleProtocol { | |
var simpleDescription: String = "A very simple class." | |
var anotherProperty: Int = 69105 | |
func adjust() { | |
simpleDescription += " now 100% adjusted." | |
} | |
} | |
var a = SimpleClass() | |
a.adjust() | |
let aDescription = a.simpleDescription | |
struct SimpleStructure: ExampleProtocol { | |
var simpleDescription: String = "A simple structure" | |
mutating func adjust() { | |
simpleDescription += "(adjusted)" | |
} | |
} | |
var b = SimpleStructure() | |
b.adjust() | |
let bDescription = b.simpleDescription | |
//Experiment: Add ExampleProtocol to an enummeration | |
enum SimpleEnum : ExampleProtocol { | |
case One, Two, OneOne, OneTwo, TwoOne, TwoTwo | |
var simpleDescription: String { | |
get { | |
switch self { | |
case .One: | |
return "one" | |
case .Two: | |
return "two" | |
case .OneOne: | |
return "oneone" | |
case .OneTwo: | |
return "onetwo" | |
case .TwoOne: | |
return "twoone" | |
case .TwoTwo: | |
return "twotwo" | |
} | |
} | |
} | |
mutating func adjust() { | |
switch self { | |
case .One: | |
self = Self.OneOne | |
case .Two: | |
self = Self.TwoTwo | |
case .OneOne, .OneTwo: | |
self = Self.One | |
case .TwoOne, .TwoTwo: | |
self = Self.Two | |
} | |
} | |
} | |
// Below is some examples of using the enum conforming to a protocol | |
// and it looks quite confuing, but managing a statemachine type sequence | |
// it could become very powerful in some applications. | |
var c = SimpleEnum.One | |
var enumState = c.simpleDescription | |
c.adjust() | |
var enumNextDescritption = c.simpleDescription | |
c.adjust() | |
enumNextDescritption = c.simpleDescription | |
c = SimpleEnum.TwoOne | |
enumState = c.simpleDescription | |
c.adjust() | |
enumNextDescritption = c.simpleDescription | |
c.adjust() | |
enumNextDescritption = c.simpleDescription | |
// Extensions | |
extension Int: ExampleProtocol { | |
var simpleDescription: String { | |
return "The number \(self)" | |
} | |
mutating func adjust() { | |
self += 42 | |
} | |
} | |
7.simpleDescription | |
var newInt = 8 | |
newInt.adjust() | |
extension Double { | |
var abs: Double { | |
get{ | |
if (self.sign == .minus){ // demonstrates that doubles are not POT in swift. | |
return self * -1 | |
} else { | |
return self | |
} | |
} | |
} | |
} | |
var myDouble = -20.7 | |
myDouble.abs | |
let value = abs(myDouble) // uncomment if using import cocoa | |
let protocolValue: ExampleProtocol = a | |
protocolValue.simpleDescription | |
//protocolValue.anotherProperty // Uncomment to see the error | |
// | |
// Generics | |
// | |
func repeatThis<ItemType>(item: ItemType, times: Int) -> [ItemType] { | |
var result = [ItemType]() | |
for _ in 0 ..< times { | |
result.append(item) | |
} | |
return result | |
} | |
let result = repeatThis(item: "knock", times: 4) | |
print(result) | |
print("end") |
And I did Double extension this way:
extension Double {
var absoluteValue: Double {
get {
return abs(self)
}
}
}
var myDouble = -14.23
print(myDouble)
let absValue = myDouble.absoluteValue
Updated to Swift 5 (and fixed my noddy error from a decade ago!) 😆
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
var newNumber = newInt.adjust // It just shows function in the sidebar???
needs a fix:
This way a comment on the line 665 could be removed too :)