Created
December 3, 2016 04:10
-
-
Save misbell/97ffc4aaa9c81f7e53ede3c28c95051e to your computer and use it in GitHub Desktop.
The Map , Filter, Reduce code chapter from Fatih Nayebi, adapted for Swift 3.0.1 from Swift 2
This file contains hidden or 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
// This code runs in a playground in Swift 3.0.1 | |
import Cocoa | |
var str = "Hello, playground" | |
/* | |
MPI | |
dec 2 2016 | |
Functor | |
Applicative Functor | |
Monad | |
Map | |
Flatmap and flatten | |
filter | |
Reduce | |
apply | |
join | |
chaining higher order functions | |
zip | |
practical examples | |
*/ | |
// what is 'the category theory' | |
//what is a morphism | |
// what is a functor | |
// a functor is any type that implements the map function | |
// e.g. Dictionary, Array, Optiona, Closure | |
// a functor is something where we can call the Map function | |
// over it and transform it | |
// a functor is a functional design pattern | |
/// ===================== | |
// applicative functor | |
// if you have an optional functor you can't apply the map function on the Optional type without unwrapping it first | |
// the apply function, you need to apply the Functor before you can use map on the Functor | |
//======================================== | |
// Monad | |
// a type of Functor | |
// ========================= | |
// so let's look at Maps | |
let numbers = [10, 30, 91, 50, 100, 39, 74] | |
var formattedNumbers: [String] = [] | |
for number in numbers { | |
let formattedNumber = "\(number)$" | |
formattedNumbers.append(formattedNumber) | |
} | |
let mappedNumbers = numbers.map { "\($0)$" } | |
print (mappedNumbers) | |
// two things, I didn't know you could use an interpolated value and assign it to a string, not just use it in a print statement | |
// I probably SHOULD have known that but I didn't | |
// internal iteration vs external iteration | |
// map is internal iteration | |
// for loops are external iteration | |
print (formattedNumbers) | |
func calculate<T>(a: T, | |
b: T, | |
funcA: (T,T) -> T, | |
funcB: (T) -> T) -> T { | |
return funcA(funcB(a), funcB(b)) | |
} | |
func calculate2<T, U>(a: T, funcA: (T) -> U) -> U { | |
return funcA(a) | |
} | |
func calculate2ArraysToo<T,U>(a: [T], funcA: ([T]) -> [U]) -> [U] { | |
return funcA(a) | |
} | |
// leading to a half baked map | |
func map_a<T,U>(a: [T], transform: ([T]) -> [U]) -> [U] { | |
return transform(a) | |
} | |
func map_b<ElementInput, ElementResult>(elements: [ElementInput], transform: (ElementInput) -> ElementResult) -> [ElementResult] { | |
var result: [ElementResult] = [] | |
for element in elements { | |
result.append(transform(element)) | |
} | |
return result | |
} | |
let twoDimensionalArray = [[1,2,3], [4,5,6]] | |
var onedimarray: [Int] | |
// so flat map is the order of operations on the array, NOT a kind of thing | |
// first you do flat, then you do map | |
var oneDimensionalArray = twoDimensionalArray.flatMap { $0 } | |
// first turn it into a single array, then run the transform | |
let transformedOneDArray = twoDimensionalArray.joined().map {$0 + 2} | |
print (oneDimensionalArray) | |
print (transformedOneDArray) | |
var xxx = twoDimensionalArray.flatMap({ $0 } ) | |
// filter | |
// implement the filter function | |
func filtera<Element> (elements: [Element], | |
predicate: ((Element) -> Bool)) -> [Element] { | |
var result = [Element]() | |
for element in elements { | |
if predicate(element) { | |
result.append(element) | |
} | |
} | |
return result | |
} | |
let filteredArray = filtera(elements: numbers) { $0 % 2 == 0 } | |
print (filteredArray) | |
// reduce | |
// reduces a list into a single value | |
// also called a fold or aggregate | |
// takes a starting value and a function | |
// it returns a value created by combining (adding?) elements in a list | |
// filter and map return the same type | |
// reduce returns a different type | |
let total = numbers.reduce(0) {$0 + $1} | |
print(total) | |
let total2 = numbers.reduce(0, +) | |
print(total2) | |
func reducea<Element, Value> (elements: [Element], initial: Value, combine: (Value, Element) -> Value) -> Value { | |
var result = initial | |
for element in elements { | |
result = combine(result, element) | |
} | |
return result | |
} | |
// reduction is so powerful that every other function that traverses a list can be specified in terms of it. | |
func mapInTermsOfReduce<Element, ElementResult>( elements: [Element], | |
transform: (Element) -> ElementResult) -> [ElementResult] { | |
// note reduce is now function on the array | |
return elements.reduce([ElementResult]()) { | |
$0 + [transform( $1 )] // transform all elements in array | |
} | |
} | |
let resultm = mapInTermsOfReduce(elements: numbers) { | |
$0 + 2 | |
} | |
// | |
//// I need to get this bit with $0 and $1 right | |
//// $0 is ElementResult [transform ( $1 is element ) | |
//// you can always inspect them | |
//// an array of transforms means a list of ElementResults because THAT'S WHAT IT RETURNS | |
//// ok I get it | |
// | |
func filterIntermsOfReduce<Element>(elements: [Element], predicate: (Element) -> Bool) -> [Element] { | |
return elements.reduce( [] ) { | |
predicate($1) ? $0 + [ $1 ] : $0 // test predicate for true or false, including new element or not | |
} | |
} | |
let resultf = filterIntermsOfReduce(elements: numbers) { | |
$0 % 2 == 0 | |
} | |
func flatMapIntermsOfReduce<Element>(elements: [Element], | |
transform: (Element) -> Element? ) -> [Element] { | |
return elements.reduce( []) { | |
guard let transformationResult = transform($1) else { | |
return $0 | |
} | |
return $0 + [transformationResult] | |
} | |
} | |
let anArrayOfNumbers = [1, 3, 5] | |
print(anArrayOfNumbers) | |
let ooneDimArray = flatMapIntermsOfReduce(elements: anArrayOfNumbers) | |
{ $0 + 5 } | |
print (ooneDimArray) | |
func flattenIntermsOfReduce<Element>(elements: [[Element]]) -> [Element] { | |
return elements.reduce([]) { | |
$0 + $1 | |
} | |
} | |
let flattened = flattenIntermsOfReduce(elements: [[1,2,3], [4,5,6]]) | |
func apply<T, V>(fn: ([T]) -> V, args: [T]) -> V { | |
return fn(args) | |
} | |
func incrementValues(a: [Int]) -> [Int] { | |
return a.map { $0 + 1 } | |
} | |
let applied = apply(fn: incrementValues, args: numbers) | |
func join<Element: Equatable> (elements: [Element], separator: String) -> String { | |
return elements.reduce("") { | |
initial, element | |
in | |
let aSeparator = (element == elements.last) ? "" : separator | |
return "\(initial)\(element)\(aSeparator)" | |
} | |
} | |
let items = ["First", "Second", "Third"] | |
let commaSeparatedItems = join(elements: items, separator: ", ") | |
struct User { | |
let name: String | |
let age: Int | |
} | |
let users = [ | |
User(name: "Fehiman", age: 60), | |
User(name: "Negar", age: 30), | |
User(name: "Milo", age: 1), | |
User(name: "Tamina", age: 6), | |
User(name: "Neco", age: 30) | |
] | |
// extract into an array and then add them up (combine them) | |
let totalAge = users.map { $0.age }.reduce(0) { $0 + $1 } | |
print (totalAge) | |
// did it change | |
let alphabeticalNumbers = ["Three", "Five", "Nine", "Ten"] | |
let zipped = zip(alphabeticalNumbers, numbers).map {$0} | |
print(zipped) | |
// sum of an array | |
let listofnum = [1,2,3,4,5,6,7,8,9,10] | |
let sumofnum = listofnum.reduce(0, +) | |
// product of an array | |
let productofnum = listofnum.reduce(1, *) | |
print (productofnum) | |
// remove nil values from an array | |
// not sure I know why this works | |
let optionalArray: [String?] = ["First", "Second", nil, "Fourth"] | |
let nonOptionalArray = optionalArray.flatMap { $0 } | |
print (nonOptionalArray) | |
// remove dupes from an array | |
let arrayWithDuplicates = [1,1,2,3,3,4,4,5,6,7] | |
let remdupes = arrayWithDuplicates.reduce([]) { (a: [Int], b: Int) -> [Int] | |
in | |
if a.contains(b) { | |
return a | |
} else { | |
return a + [b] | |
} | |
} | |
print (arrayWithDuplicates) | |
print (remdupes) | |
// partitioning an array | |
typealias Accumlator = (lPartition: [Int], rPartition: [Int]) | |
func partition(list: [Int], criteria: (Int) -> Bool) -> Accumlator { | |
return list.reduce((lPartition: [Int](), rPartition: [Int]())) { | |
(accumlator: Accumlator, pivot: Int) -> Accumlator in | |
if criteria(pivot) { | |
return (lPartition: accumlator.lPartition + [pivot], | |
rPartition: accumlator.rPartition) | |
} else { | |
return (rPartition: accumlator.rPartition + [pivot ], | |
lPartition: accumlator.lPartition) | |
} | |
} | |
} | |
// here's how you can make it generic | |
func genericPartition<T>(list: [T], | |
criteria: (T) -> Bool) -> (lPartition:[T], rPartition: [T]) { | |
return list.reduce((lPartition: [T](), rPartition: [T]())) { | |
(accumlator: (lPartition: [T], rPartition: [T]), pivot: T) -> ( | |
lPartition: [T], rPartition: [T]) in | |
if criteria(pivot) { | |
return (lPartition: accumlator.lPartition + [pivot], | |
rPartition: accumlator.rPartition) | |
} else { | |
return (rPartition: accumlator.rPartition + [pivot], | |
lPartition: accumlator.lPartition) | |
} } | |
} | |
let doublesToPartition = [3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] | |
print(genericPartition(list:doublesToPartition) { | |
$0.truncatingRemainder(dividingBy: 2.0) == 0}) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment