Transcript from the "Exercises 15-17" Lesson
>> Jafar Husain: Now I want to introduce you to a new function. So this is the first new function in a while you'll have been introduced to. We introduced you to map and filter, and we talked about concatAll in the presentation. We learned about concat map which is just a shorthand for writing map followed by concatAll.
[00:00:17] Now I'm gonna show you a function called reduce. What reduce does is it takes an array, with many, many items, and reduces it to an array with one item. So we're effectively aggregating up all the values in an array and producing an array with a single result. Now I'm gonna call your attention to something which is that I've actually mucked with the definition of array.
[00:00:55] So here's an example of reduce, if I want to show you why I would use a reduce, well, the canonical example for reduce is a sum.
>> Jafar Husain: So here's an example of using the reduce function to create a sum of all the items in an array. So this is a function which takes an accumulated value and the current value.
[00:01:24] This is typically the convention when you're building a reduce. And so the very first time I can also pass in a value to use for acc the very first time around, so here I passed in zero. Now if I was to omit zero, it would still work and it would still produce the exact same thing.
[00:01:37] If you don't produce an initial value for acc, it uses the first item in the array, does that makes sense? So what's gonna happen is the very first time through this function, acc will be 1 and current will be 2. And the result of this function will be the addition of these two things, which will create, of course, [LAUGH] my Dad's trying to call me.
[00:02:03] Which will create 3, right? Now the way reduce works is it feeds the result of the function back into the function. And so the next time around, acc, the accumulated value is 3 and current is the only remaining item in the array we haven't processed yet which is 3 and then the final value is 6.
[00:02:50] So why would I say that? In fact, lots of languages have the same definition of reduce, and I think they're all wrong.
>> Speaker 2: Combine two of those, the answer would just be compatibility, right?
>> Jafar Husain: Compatibility how and compatibility what?
>> Speaker 2: Did you use the function calls or whatever?
>> Jafar Husain: Interesting, you mean I can chain a map function off of this and chain a filter function, is that what you're getting at?
>> Speaker 2: Yeah.
>> Jafar Husain: Cuz if it returns an array, I can keep on chaining. Yes, that is a very good reason why you should return it inside of an array, there's an even better one which is, what's this return?
[00:03:30] Anybody want tell me what they think that returns?
>> Speaker 3: D array?
>> Speaker 3: Undefined.
>> Jafar Husain: Maybe, I'm not sure. [LAUGH] Let's find out, I'm curious.
>> Speaker 3: Nan.
>> Jafar Husain: Nan, possibly.
>> Jafar Husain: Let's try it out. I don't remember, it's been a long time. But I can tell you whatever it returns is probably not what you want.
>> Jafar Husain: Does returning undefined make your life easier here? Probably not. So let's find out.
[00:04:18] It will happily give you, wait, I've already redefined reducing here. [LAUGH]
>> Speaker 3: [LAUGH] I'll do it.
>> Jafar Husain: Yeah, let me know.
>> Jafar Husain: So that gives me six, but what does the same thing give me when I have an empty array? Oo, error, that's even less fun, right? Well, actually it's kind of understandable, right?
[00:04:54] There's really no way to answer the question of what it reduces over an empty array, right? But if we return an array of the final result, there's a very logical thing for it to return. Anybody want to take a guess what that is?
>> Speaker 4: Empty array.
>> Jafar Husain: Empty array.
[00:05:08] I suggest this is much better than an error, right? And that's effectively why we should do it. That's a good enough reason alone to do it for array. But now apply reduced to an observable. If you apply reduce to an observable, that's an asynchronous collection that arrives over time.
[00:05:26] You can't return a simple value from a reduce over an observable because you would have to block, right? In order to give you this, right, this is the observable, the fake observable syntax. There's absolutely no way to return this as a single value because we'd have to block and wait for the observable to complete and then and only then can we return it as a simple value, does that make sense?
[00:06:05] That's why we return this.
>> Jafar Husain: That's what reduce returns when you apply it to an observable. It returns you an observable that eventually just completes with the one result. Does that makes sense? Reduce is written in such a way that it cannot be implemented over an observable using that definition.
[00:06:24] And that's actually a smell, it's a smell it's because it seems like reduce is actually doing too much. People were just abbreviating, they were like, well, if this function always returns one value, why keep it inside of the collection? Let's just take it out of the collection cuz it's just one value, right?
[00:06:38] That's exactly the type of shortcutting that works when you're looking at the array definition. But when you get down to the mathematics of it, and you look at the fact that observable and array can both be expressed as collections and streams, there's only one definition of reduce that's general enough to work for both of them.
[00:06:52] And that is for reduce to return an array of one or observable in this case to return an observable of one. So that's why I'm gonna have you implement reduce, what I think is the right way, and then we're gonna use that reduced definition later on when we use observable.
[00:07:04] So now you guys should have a pretty good idea of how reduce works, right. What's interesting about reduce, when you're trying to figure out what function should I apply to solve a problem, right? Well, if you're doing substitution, if you've got an array, and you got stuff inside, and you want to substitute each item for another item, what operator was that?
[00:07:21] Map, I have an array, I've got items and I wanna transform each item inside and sort of substitute it in that location in the new array, map. What if I have an array with a bunch of items and I wanna apply some test function to each of them and only keep the ones that pass the test?
>> Speaker 5: Filter.
>> Jafar Husain: Filter, right? What if I have a two dimensional array and I wanna flatten it out?
>> Speaker 5: Okay.
>> Jafar Husain: Reduce is for the case where you need to look at least two values at the same time to do your job. Notice that map and filter have got tunnel vision, they only look at one item at a time, but in order to do a sum, for example, you would need to at least look at two values at a time to do that job, right?
[00:07:58] You couldn't do a sum with a filter operation or a map operation even, right? Cuz map can only, you only have the one value at a time. So does that makes sense? So those are the types of tasks you'll be using reduce for. So let me jump back to here, I think, okay, so this is 15, so we're gonna assume you guys finished this one.
[00:08:17] So our job here is to find the largest box art. So aggregation operations, sums, maximums, minimums, these are the types of things that you would use the reduce function for. So in this case, I'm gonna use for each for it, and this is the way you would kind of approach it if you had a loop.
[00:08:33] You'd create a bunch of variables to store, what's the current size, what's the largest box art I'm currently keeping track of. All right, and then you would for each over box arts and then we say, okay, well, what's the current size? We compute the size. If the size is larger than the current max size, then we reset the largest box art variable to the new box art and we reset the maximum size to current size.
[00:08:56] And by the time the loop is done, we'll have a largest box art. This is very similar to the way you guys have probably written this type of code in the past, right. Loop through an array, use a variable to keep track of your accumulated value. Well, you can take this exact same pattern and abstract it into reduce.
[00:09:11] Anything where you're doing this form of aberration, you can replace it with reduce. Question?
>> Speaker 2: Would the custom implementation of reduce always return an array of one element?
>> Jafar Husain: Yes, cuz reduce always takes the entire collection and reduces it to one element. But by returning it in an array, we have a logical thing that we can do if we get an empty array as an input.
[00:09:30] We have a logical output that's not so bad as throwing an error and we can continue chaining on operators after the reduce operation. This is a very common mistake that's made when stream processing, which is taking APIs like reduce and having them return scalars instead of vectors. So what we're gonna do is were gonna implement reduce.
[00:09:51] Now this is actually already implemented for us. We already explained how reduce worked, right? There's basically an optional initial value you can pass for reduce but let's go over it one last time, inside a reduce here. If I write this reduce expression, what reduce does is the very first time around, if I haven't specified an initial value, it just uses the first value as the initial value.
[00:10:15] So that's 1 and then it uses the next value as the current value and so that's 2. And what comes out of this function is gonna be 3 and whatever comes out of the reduced function is gonna be fed back in as the accumulator the next time around.
[00:10:27] So next time around this is gonna be 3 and this is gonna be the only function, the only item in the array we haven't processed yet, which is 3. And then the final one is gonna be 6, and then reduce is going to return an array of 6, and so if we, however, were to use an initial value, then it would work like this.
[00:10:44] It would start with out with 0 as the initial value, which is what we pass in first as the accumulative value, and then we'd get 1 and then we would get, predictably, we'd get 1 from the function. The next time around, 1 would be the accumulated value and 2 would be the current value, cuz now we've gone here, all right, and then we'd get 3.
[00:11:03] Next time around a 3 is the accumulated value and 3 is the current value. And we get 6, so we get the same result regardless of whether we passed in an initial value of zero or not. So that's how reduce works, I won't spend too long looking at this function because you know it.
[00:11:20] That's a pretty straightforward thing, we're just using a while loop to do that the expected thing. But notice that in the end, the key thing I want to call out is that we make sure to keep it inside of an array. Now, I wonder if I've got a bug in here, I wonder what happens if we have no items in this collection, right.
[00:11:38] So here notice if the length is zero, we just return the exact same array. So a zero length the ray, if you attempt to reduce it, produces a zero length array.
>> Jafar Husain: So let's run that, that works. So now we're gonna use reduce to find the largest rating.
[00:12:01] So we have an array of movie ratings, and so we're going to call reduce. And pass in a function which starts an accumulated value and a current value. And we're just gonna return whatever the largest value is. So we're gonna say if accumulative larger than current will return the accumulated.
[00:12:25] Otherwise, will return the current, and I believe that's gonna get us the value. So what reduce is saying is, look, if the accumulated value is largest, larger than whatever value we're looking at right now, just return whatever was currently the largest value. But if the current value happens to be larger than the accumulated value, reuse that as the accumulated value the next time around.
[00:12:44] And so if we run this, it should work, yes, so that works. Does anybody want to slow down take a look at that a little bit more or did we did we get past 17? Okay.