Treehouse Intermediate Course - Part 12 (Closures 4)

Right so I've connected to my personal hotspot and am going to code on the coach! I'll aim to do around an hour. Continuing with map, let's go!

Start Time - 10:33

Flat Map

Oh an example about blogs. How apt!

So far, all clear -

struct Post {
    
    var post: String
    var tags: [String]

}

let blog = Post(post: "Hi all!", tags: ["first", "newbie"])


let secondBlog = Post(post: "Now a bit about me!", tags: ["personal", "info", "details"])

We are going to do something to combine these blogs I reckon!

OK forget the above - I got confused!
let blog = [
    
Post(post: "Hi all!", tags: ["first", "newbie"]),
Post(post: "Now a bit about me!", tags: ["personal", "info", "details"])

]

let tags = blog.flatMap { $0.tags }

And extension time on the array again!

extension Array {
    
    func customFlatMap<T>(_ transform: (Element) -> [T]) -> [T] {
        var result = Array<T>()
        
        for x in self {
            result.append(contentsOf: transform(x))
        }
        return result
    }
}

Still not sure what the 'flat' part of map really means....

So looking this up, the flat part means it gets rid of nils, gets a single set of elements...it sorts it out basically. So this is useful when you've got several arrays you want to sort out as one clear array. So one array rather than multiple values of arrays etc. Ok!

A note about the signature. This is what the 'standard' swift one would be. A bit different to what we have done. The rethrows we will cover another time apparently!
 

Next example - copied and pasted straight from Pasan - 

struct Account {
    let username: String
    let billingAddress: String?
}


let allUsers = [
    Account(username: "pasanpr", billingAddress: nil),
    Account(username: "benjakuben", billingAddress: "1234 Imaginary Street"),
    Account(username: "instantNadel", billingAddress: "5678 Doesn't Live Here Dr."),
    Account(username: "sketchings", billingAddress: nil),
    Account(username: "paradoxed", billingAddress: "1122 Nope Lane")
]

Again the for loop would be long-winded. Flatmap has all of that handled for you!

let validAddresses = allUsers.flatMap {$0.billingAddress}

Swift comes up with the partial (yellow warning) error of using flatMap - that compactMap is better. This may well be that a later version of Swift has evolved beyond this. Doesn't matter for now. 


let numbers = [[1,2,3], [4,5,6], [7,8,9]]

let flattenedNumbers = numbers.flatMap { $0 }

Very easy challenge again! The good thing is that I'm getting used to this syntax.

I'm going to crack on and finish this section today. It all seems logical so far. 

Filter

One of the most common operations is to iterate over the contents then match for a certain predicate. So e.g. picking out even number values etc. Filter function is built in for this!

Let's just try this out first!

Not quite as easy as I thought. 

let evenNumbers = (1...100).filter { $0 % 2 == 0 }

I didn't realise I could use a closed range so easily as that! But all good. 

Next example with extending the array - 

extension Array {
    
    func customFilter(_ isIncluded: (Element) -> Bool) -> [Element]{
    
    var result = [Element]()
    
        for x in self where isIncluded(x) {
            result.append(x)
        }
        return result
    }
}

The closure is passed into the input parameter. Eventually the array is returned. This time it is to see if something is true, then the array is returned with that result shown. 

Ah an issue here is that 'characters' was made obsolete a little while ago. 


let someP = allUsers.customFilter {$0.username.first == "p"}

But a useful person has showed that you can still do the same code without even having to mention the characters property - as above. 


let numbers = [10,423,802,765,943,12,405,230,1348,128,237]

let oddValues = numbers.filter { $0 % 2 != 0 }

Again, no problem here. Syntax becoming more and more familiar!

Reduce

Last bit with a challenge then I'm done for today!

let scores = [5, 7, 12, 10, 7, 8]


let totalScore = scores.reduce (0, { total, score in total + score })

OK so there was no way I would have got that! The 0 is the INITIAL VALUE. Then we pass in a closure that shows our combine function. A variable to hold the combined value and the element from the array. Initially, total = 0, then every element from the array, we add to. 

Another extension - 

extension Array {
    
    func customReduce<Result>(_ initial: Result, nextPartial: (Result, Element) -> Result) -> Result {
        var result = initial
        
        for x in self {
            result = nextPartial(result, x)
        }
        
        return result
    }
}

Complicated this one! Result is a custom generic type. The for loop version isn't actually that bad compared to this!

I see that reduce isn't just sum of - it could be multiplying or other things. That's why the closure expression just needs that same signature for it to work. 

Reduce doesn't need to return the same type as the elements in the array. 

Right some powerful stuff here - 

let dataSet = (1...100).filter { $0 % 3 == 0 && $0 % 7 == 0}

let identifier = dataSet.reduce("", {string, number in string + "\(number)"})

The first bit is getting only numbers that are divisible by 3 and 7 (21, 42, 63, 84). Then the identifier is a combined string. 


OK this is harder. 

let ages = [12,10,11,42,35,27,91,82,26,33,37,15]

let total = ages.reduce(0, { sum, total in sum + total })

WOW! That worked without me even looking at anything on Xcode or the blog! Cool!

Finish Time - 11:35 (1 hour 2 minutes total)

So a productive session and I've learned much more about the map functions and how they work alongside closures. Great stuff! And a good time to stop - typing and travelling can only work for so long. Back on Friday!

Comments

Popular posts from this blog

*Xcode Project Entry 2* F1 Quiz - part 1

Angela Yu Course Part 10 (up to lesson 112)

Angela Yu Xcode 12 Course - Part 7 (lectures 74 to 79)