Skip to content

Instantly share code, notes, and snippets.

@billbonney
Last active July 27, 2023 06:53
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save billbonney/0968920dc29b7d0a677f to your computer and use it in GitHub Desktop.
Save billbonney/0968920dc29b7d0a677f to your computer and use it in GitHub Desktop.
A Swift Tour - The Swift Programming Language - Solutions
//
// 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")
@yugaego
Copy link

yugaego commented Jul 4, 2018

var newNumber = newInt.adjust // It just shows function in the sidebar???
needs a fix:

  var newNumber = newInt.adjust()
  print(newNumber.simpleDescription)

This way a comment on the line 665 could be removed too :)

@yugaego
Copy link

yugaego commented Jul 4, 2018

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

@billbonney
Copy link
Author

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