Transcript from the "Dictionary map, filter & reduce" Lesson
>> So this is a little exercise. And our motivation is as follows. So we have a dictionary here. It's a bunch of a bunch of fruits. They have a color, and they have a mass in grams. We have an interface, the type parameters can be used with interfaces here.
[00:00:24] You see I'm saying it's a dictionary of type T. Well, what can I find in this dictionary? A bunch of Ts, right? So you can use this however you like. So what we want to do is create array.map array.filter and array.reduce, but for dictionaries. And below this, I have a test suite similar to what we saw for the JSON types exercise.
[00:00:54] So I'll give you, I'll say, two minutes, just to read through this. And then I would like your help in implementing this. But just look through the test cases so you can try to understand where we're going with this. So at the bottom of the starter code, of course, we have the usual Try button, when we click that, we should find ourselves on the TypeScript playground.
[00:01:27] And I would like you to click this Logs tab, and we can run the code. And right now, nothing is gonna be able to run cuz we haven't really defined these functions. But once we have something a bit more reasonable as our starting point, we should start to see some good feedback there.
[00:01:47] So, really, around these lines here, this is what we want to change. We don't wanna modify anything above or below this point, just these three functions here. And the way these are defined, they're the absolute most generalized functions that we could possibly have. Meaning they take any number of arguments, which could be anything, and they return anything.
[00:02:14] So, if it's our job to implement these, I think I'm just gonna pick one to start with. And filter seems like a good place to start. So I'm gonna begin with that one first. So this is going to need to have a type parameter, and we'll call it T.
[00:02:36] In fact, all of these will need at least a T, possibly a second parameter. And let's just think about these in terms of like what they take in as arguments and what they return. And we can worry about sort of filling in the algorithm once we have that pinned down.
[00:02:52] So filter, it's going to take in a dictionary, Of type T. And it's gonna return a dictionary of T, as well. It's just gonna have fewer properties on it. It's gonna be the subset of properties on the dictionary that meet some certain condition. And it's the caller's job to state what that condition is.
[00:03:23] And for now, we can just return an empty dictionary. And we'll also define the filter. Call it filterCb, for callback. This is going to take in an argument of type T and return a boolean. And we can move these onto multiple lines to make it really easy to see everything.
[00:03:55] So there's our first argument, there's our filterCb. Now we may need more callback arguments here, I know from using array filter, array reduce, sometimes you get the id here. Let's only worry about that if we absolutely need to. So that's filter. Map, so map is used to transform a collection of one thing to a collection of something else.
[00:04:21] And the transformation is defined by a mapping function. So we might need two type parameters here to represent the before and the after. So we could have T, and then let's say U.. U comes after T. So, like filter, we're going to take in as an argument, input, A dictionary of Ts, and what we're gonna return is a dictionary of Us.
[00:04:56] And our mapping callback is going to take as an argument, a T, and return a U. We'll start out by just returning an empty dictionary, which is technically the right type to return, it's just not a meaningful thing to return right now. Reduce is a little more tricky, let me just see if the test suite works without me defining the signature for reduce.
[00:05:27] Nope, we're gonna have to do reduce as well here, no problem. So the point of reduce is to iteratively, while processing a collection, build up and eventually return a singular value of some sort. And the iterative assembly of that value is to be defined by a reducer function.
[00:05:53] So, again, input, a dictionary of Ts, and then we have whatever value you're looking to arrive at, let's call it V, ffor value. So we're gonna actually return a V, not a dictionary. And we could have a reducer. And let's do a multiline, as we did before, Just to spread things out.
[00:06:19] So this is going to take a current value, which will be V, And then an item from the dictionary, which will be a T. And it'll return V, right? So sometimes reduce is great for a summation, where you have a running total, you add each item to the total, ultimately return the whole total.
[00:06:46] So this is gonna take our running total, the thing that we might factor into the total. And then here's the new total that would be returned by the reducer. We also have an initial value, which will be a V. We can get away with just returning the initial value right away here.
[00:07:13] Let's go back to filter and tackle this. So what we're gonna do is iterate over the dictionary, so we'll create a new dictionary that we aim to return. And then we'll iterate over the dictionary we're given, see which values pass the filter, and if they pass the filter, we'll add them to the new dictionary.
[00:07:32] So we end up returning the subset of properties that pass through the filter. So call this toReturn, eventually will return it. This will start out empty. Great, and we can take advantage of the for of loop. That's a really nice thing to use when iterating over an object like this.
[00:08:14] We can verify that this is the right thing to do. Let's see, input. Maybe it's the for in loop that I'm thinking of. Yep, it is. Yeah, for us is for an iterable. This is iterating over the properties of an object. So here we've got our input. Here we see that key is a string, that's a good signal that it's probably the dictionary key.
[00:08:42] Great, so we could say thisValue is the input dictionary, And retrieve an item by key if the filter callback passes when we give it this value. We're gonna add this to the dictionary. toReturn[key] = thisValue. Great, so that filter, that should work. Let's look at map. So I'm gonna start with the same code as we use for filter, cuz a lot's gonna be similar.
[00:09:28] The difference here is we have just a mapping callback that transforms from original dictionary to whatever, it transforms each item from A to B, whatever those things are. So the thing we'll eventually return, it's a dictionary of Us, not a dictionary of Ts. So we'll just make that little adjustment there.
[00:09:49] We're still gonna iterate over the input dictionary. We're still going to grab the value for each key. The difference here is we're always going to append things to the new dictionary. It's not a condition, it's a transformation. So we'll say toReturn. And then this is gonna be a map, mappingCb.
[00:10:20] So effectively here, for each, We're going to get value and then perform the transformation. I wanna look at my type errors here and see what's going on. So it looks like our map dictionary, one of our tests, it wants to be provided the name of the item, the key, rather, right?
[00:10:47] So we can go back up top and make sure we accommodate that. So key is a string. And we'll just pass that along here. Great, no more errors. Seems like that was the only little extra feature of a callback we had to add. Let's run this code. Great, now we're starting to see some tests passing, and see where we're at.
[00:11:14] So we have a failed assertion here around reduce. Makes sense, we haven't really implemented reduce yet. But let's check out, so map, sorry, let me zoom in here, somehow I got scrolled, there we go. So we're passing all of our tests for map, we're passing all of our tests for filter.
[00:11:36] We're actually passing some tests for reduce already. We have only one test that hasn't passed yet. And that's because reduce is just first making sure that we return something, if it's a truthy thing. So this is the meaningful test that we still have to pass. So let's look at reduce.
[00:11:57] So we're gonna start out with an initial value. This is gonna be a let declaration, Because we'll keep writing over it with every loop over the array, or the dictionary, rather. So we'll first initialize the value with what we're given, we'll return it at the end. We're still going to loop, so I'm gonna borrow some of that code from filter.
[00:12:25] Just the for in loop. And this actually, while conceptually a bit abstract, it is implementation wise pretty simple. So the new value is reducer. And we'll pass in the last thing we had for the current value and the item. Let's try to run this and see where we're at.
[00:13:05] All right, looks like we passed all the tests. So we just wrote higher order functions for dictionaries using generic types. These will work for any dictionaries that you might have in your code base. And you'll preserve all of your type information as your collections pass through these functions.
[00:13:29] Hopefully this illustrates why type params and generics are an incredibly powerful tool. We get the ability to use these flexibly without giving up all of our type safety.