let shortWaitRides = ridesWithWaitTimeUnder(15, from: parkRides)
functestShortWaitRides(_ testFilter:(Minutes, [Ride]) -> [Ride]) { let limit = Minutes(15) let result = testFilter(limit, parkRides) print("rides with wait less than 15 minutes:\n\(result)") let names = result.map { $0.name }.sorted(by: <) let expected = ["Crazy Funhouse", "Mountain Railroad"] assert(names == expected) print("✅ test rides with wait time under 15 = PASS\n-") }
var ridesOfInterest: [Ride] = [] for ride in parkRides where ride.waitTime < 20 { for category in ride.categories where category == .family { ridesOfInterest.append(ride) break } }
let sortedRidesOfInterest1 = ridesOfInterest.quickSorted() print(sortedRidesOfInterest1)
/// Copyright (c) 2018 Razeware LLC /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to deal /// in the Software without restriction, including without limitation the rights /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell /// copies of the Software, and to permit persons to whom the Software is /// furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in /// all copies or substantial portions of the Software. /// /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, /// distribute, sublicense, create a derivative work, and/or sell copies of the /// Software in any work that is designed, intended, or marketed for pedagogical or /// instructional purposes related to programming, coding, application development, /// or information technology. Permission for such use, copying, modification, /// merger, publication, distribution, sublicensing, creation of derivative works, /// or sale is expressly withheld. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN /// THE SOFTWARE.
import Foundation
//: # Introduction to Functional Programming
/*: ## Imperative Style Command your data! */ var thing = 3 //some stuff thing = 4
/*: ## Side effects Holy mysterious change! - Why is my thing now 5? */ funcsuperHero() { print("I'm batman") thing = 5 }
print("original state = \(thing)") superHero() print("mutated state = \(thing)")
/*: ## Create a Model */ enumRideCategory: String{ case family case kids case thrill case scary case relaxing case water }
typealiasMinutes = Double structRide{ let name: String let categories: Set<RideCategory> let waitTime: Minutes }
/*: ## First class and higher order functions. Most languages that support FP will have the functions `filter`, `map` & `reduce`. ### Filter Filter takes the input `Collection` and filters it according to the function you provide. Here's a simple example. */
let apples = ["🍎", "🍏", "🍎", "🍏", "🍏"] let greenapples = apples.filter { $0 == "🍏"} print(greenapples)
//: Next, try filtering your ride data funcwaitTimeIsShort(_ ride: Ride) -> Bool { return ride.waitTime < 15.0 }
let shortWaitTimeRides = parkRides.filter(waitTimeIsShort) print("rides with a short wait time:\n\(shortWaitTimeRides)")
let shortWaitTimeRides2 = parkRides.filter { $0.waitTime < 15.0 } print(shortWaitTimeRides2)
/*: ### Minor detour: CustomStringConvertible You want to make your console output look nice. */ extensionRideCategory: CustomStringConvertible{ var description: String { return rawValue } }
/*: ### Map Map converts each `Element` in the input `Collection` into a new thing based on the function that you provide. First create oranges from apples. */ let oranges = apples.map { _in"🍊" } print(oranges)
//: Now extract the names of your rides let rideNames = parkRides.map { $0.name } print(rideNames) testOriginalNameOrder(rideNames)
let sortedNames2 = sortedNamesFP(parkRides) testSortedNames(sortedNames2)
/*: ### Reduce Reduce iterates across the input `Collection` to reduce it to a single value. You can squish your oranges into one juicy string. */ let juice = oranges.reduce(""){juice, orange in juice + "🍹"} print("fresh 🍊 juice is served – \(juice)")
//: Here you **reduce** the collection to a single value of type `Minutes` (a.k.a `Double`) let totalWaitTime = parkRides.reduce(0.0) { (total, ride) in total + ride.waitTime } print("total wait time for all rides = \(totalWaitTime) minutes")
/*: ## Partial Functions A function can return a function. `filter(for:)` returns a function of type `([Ride]) -> ([Ride])` it takes and returns an array of `Ride` objects */ funcfilter(for category: RideCategory) -> ([Ride]) -> [Ride] { return { (rides: [Ride]) in rides.filter { $0.categories.contains(category) } } }
//: you can use it to filter the list for all rides that are suitable for kids. let kidRideFilter = filter(for: .kids) print("some good rides for kids are:\n\(kidRideFilter(parkRides))")
/*: ## Pure Functions - Always give same output for same input - Have no side effects */ funcridesWithWaitTimeUnder(_ waitTime: Minutes, from rides: [Ride]) -> [Ride] { return rides.filter { $0.waitTime < waitTime } }
let shortWaitRides = ridesWithWaitTimeUnder(15, from: parkRides)
functestShortWaitRides(_ testFilter:(Minutes, [Ride]) -> [Ride]) { let limit = Minutes(15) let result = testFilter(limit, parkRides) print("rides with wait less than 15 minutes:\n\(result)") let names = result.map{ $0.name }.sorted(by: <) let expected = ["Crazy Funhouse", "Mountain Railroad"] assert(names == expected) print("✅ test rides with wait time under 15 = PASS\n-") }
//: when you replace the function with its body, you expect the same result testShortWaitRides({ waitTime, rides in rides.filter{ $0.waitTime < waitTime } })
/*: ## Recursion Recursion is when a function calls itself as part of its function body. Make `Ride` conform to `Comparable` so you can compare two `Ride` objects: */ extensionRide: Comparable{ staticfunc <(lhs: Ride, rhs: Ride) -> Bool{ return lhs.waitTime < rhs.waitTime } staticfunc ==(lhs: Ride, rhs: Ride) -> Bool { return lhs.name == rhs.name } }
/*: Next add a `quickSorted` algorithim to `Array` */ extensionArraywhereElement: Comparable{ funcquickSorted() -> [Element] { ifself.count > 1 { let (pivot, remaining) = (self[0], dropFirst()) let lhs = remaining.filter { $0 <= pivot } let rhs = remaining.filter { $0 > pivot } return lhs.quickSorted() + [pivot] + rhs.quickSorted() } returnself } }
//: test your algorithm let quickSortedRides = parkRides.quickSorted() print("\(quickSortedRides)")
/*: check that your solution matches the expected value from the standard library function */ functestSortedByWaitRides(_ rides: [Ride]) { let expected = rides.sorted(by: { $0.waitTime < $1.waitTime }) assert(rides == expected, "unexpected order") print("✅ test sorted by wait time = PASS\n-") }
testSortedByWaitRides(quickSortedRides)
/*: ## Imperative vs Declarative style ### Imperitive style. Fill a container with the right things. */ var ridesOfInterest: [Ride] = [] for ride in parkRides where ride.waitTime < 20 { for category in ride.categories where category == .family { ridesOfInterest.append(ride) break } }
let sortedRidesOfInterest1 = ridesOfInterest.quickSorted() print(sortedRidesOfInterest1)
functestSortedRidesOfInterest(_ rides: [Ride]) { let names = rides.map({ $0.name }).sorted(by: <) let expected = ["Crazy Funhouse", "Grand Carousel", "Mountain Railroad"] assert(names == expected) print("✅ test rides of interest = PASS\n-") }