 All righty, welcome back to 105. So more link lists today. Everyone's most fun thing. So we get some more practice with that. So we get to create some more functions today. We've done a lot already. So we could present our link list as a library so that the user doesn't have to use nodes and then it kind of looks like Python or other languages. So what we wrote before were things like we could create a link list which initializes the head to be null. We can write a function called free that frees all of the nodes, cleans up the list, empty, length, to check whether or not the list is empty. It's length. I just renamed the function to start with link list underscores because that's how C developers usually write that but that's not the recommended way to do it in this course but I just do it here because it looks a bit better. We wrote how to insert just a value to the front. So we created node, put it at the front, played with the pointers and then there's now this function to remove an element from the front. So it'll just remove it, free the node, give you the value back and then here's some new functions we can write today. So we can write a function that returns true or false whether or not a particular value is in a link list. We're assuming our nodes just have integers and then we could write a function to remove the first node that matches the value and then another function to remove everything that matches a value. So they'll pretty much look like what we've already done before. So let's go ahead, write a function to check if a value is in the link list or not. So here is the function prototype, boolean. So it's gonna return a yes or a no whether the link list contains a value should return truth, the values in the link list false otherwise. So when we write this, typically we'll have the link list and the link list structure. Remember that structure just has a head which points to the node that starts the list and then every other node has a next pointer in it. So this kind of looks like anything else that we do that iterates through the list. So we create a temporary value probably called current set it equal to the link list head. Whoops. Wow, I can't type today. And then while current does not equal null, we can iterate through everything and update current equal to current.next and this will essentially iterate through everything in our list. So it's complaining that we don't have a return value. So what should I write here to check if a certain value is in my list yes or no? Yeah, yeah, so we can just have a simple check here. So if the current, so remember a node has like a next pointer and then the way I wrote it, it has a value but you could put whatever you want in it. But here if the value of the current node equals the value that we pass into the function that means we have found it. We can just simply return true and we don't have to check anything else we've already found it. If we make it to the end and we have not returned true so nothing matches or the list is empty. It covers both case. We can just go ahead and return false. So any questions about that? Lots of fun, right? It's just more iterating through the list and playing with values. So here's the code on the slide. So you have it. We can create more functions. Ooh, more functions, more fun. This pretty much looks like a combination of ones we've already wrote. So we can create a function to remove the first matching value. So we take a link list as a parameter and then value and we want to try and remove it from the list. Actually free the node. So we don't have any memory leaks or anything like that. And then just return true if it got removed, false if it was in the list and that did not get removed and cleans up all the memory. So, whoops, swap back. So let's think about how we would write, whoops, something like that. So, give you a little bit to think or I could just start writing it to try and remove the first matching value. So maybe I could start off and let's do the old copy paste. Oops. So if I find the current value instead of just returning true, I want to actually remove that node, right? So how would I remove the node without breaking everything else? So can I just do, I don't know, let's say I want to free the current node and then that's it. Probably not the greatest idea, right? Because while some node probably still points to current and well, that would be a segmentation fault if we had to go over it. So we have to go update some pointers and things like that. So anyone want to help me? What do I need? Anyone remember how to actually successfully remove a node from the list? So have a temporary pointer that points to that node, so like something like that and then what do I want to do with that? Okay, so I need to make the previous node point to that next one, right? So I probably need to keep track of the previous node. So let's do what we did before. We'll create a node called previous, initialize it to null and then here I can set previous equal to current. So I always keep track of the last node I had. So that's why I had two pointers. So this will make sure previous is always one behind current and initially it starts off as null. So now if I want to remove it, well, yeah, I do need to free current but what pointers do I need to change? So I have current in temp which shouldn't help me too much because if I free it, well it's the same address that I just freed. I also can't use it then. So that doesn't quite help me. Yeah, yeah, so the thing I want out of current that I want to save is it's next. So maybe I just save it in a temporary variable. Maybe I call it next. So after I get next I can free it. That's good, okay. So now what do I want to do? So now I delete it, I got it's next. So now there's kind of two cases here, right? Yeah, yeah, so there's two cases here. So if previous is null, I want just the previous next equal to next. Is that all we need? Are we good? Looks like it should be good. We free the node and then we return that. We return true. So we either update the head to point to the next that we just currently deleted or update the previous to point to the next which would just skip over the one we just deleted, right? So this should, so here is very good. And that would remove the first match value. So any questions about that? All I did is kind of make it go compact on the slide a little bit. Pretty much the same thing we had before. So now we can create a function that removes all matching values because, well, removing one thing is fun. This could be even more fun. So this should remove all the matching values and then return the number of values removed. So the function should also just like free the nodes used to store all the values. So let's think about how we would write a function like this. And let me check. So one hint might be this is a lot to think about but kind of ish like the idea of recursion. We've already kind of solved this problem with the last function we wrote. So if we remove the first element and it returns yes or no whether it has removed it, we might be able to just reuse that function instead of playing with all the list pointers again and all that fun stuff. So any ideas of what we could do to write this really simply doesn't have to be efficient at all. All right, let's just steamroll through it. All right, so easiest way to write it is just like this. So to remove everything, we could just create a variable called it removed and just since our removed first returns yes or no whether or not we have removed something, well, we can use a while loop that can just keep on removing stuff as long as we've removed an element. So as long as this function returns true means we've removed something so we can keep on doing it until it eventually returns false. And when it eventually returns false, that means we have removed every matching value. And here we just increment the number of elements we have removed each time through the loop because while each time it returns true we removed a single value. So now with that function do do do. So here's a little test function, oops. So here is our link list before. It's one, two, one, three, one, four. So if we call remove all on it and we're removing all the values one, hopefully if it works it removes one, two, three. So it removes three ones which indeed it returns we have removed three and then the link list after has the elements two, three, four in it. Fun, right? All right, it's not that fun. I pretty much beating a dead horse with just these link list things over and over again. So any more link list things I need to do or everyone more or less okay with that I beat you over the head with it enough. All right, cool. So you might also ask is there anything useful we can do with link list? So we can actually solve some problems with link list. So in general you could like implement some more complex arithmetic using link list. For instance, one silly example is we could just sum together all the values in the link list without having to explicitly iterate over everything. So if we run this algorithm which is we remove two numbers from the front of the list and then add their values together and then put their result on top of the link list we could repeat this process over and over and over again until eventually there's only one element in the link list and magically that one element in the link list will be the final sum. So this is kind of like how you would do it if you were doing it by hand and you could only add two numbers together at once. So here is the code that would do that. Actually here, let's just write it. So the code that would do it, the idea behind it is each time through this loop. So first I can check that the list is not empty. That would probably be an edge case. So if I'm computing the sum and the link list is empty, I'll just return zero. Otherwise I will have my loop that will just keep on running while the length of the link list is not equal to one. And the idea behind this is, well every time we iterate through this code, we're removing two elements. So we're removing two numbers from the list and then adding one back in. So every time we do this, it gets one smaller and the value doesn't change. We just have one less value. So we take a and a b off. So in this example, so if our list is one, two, three, four, then first time through it, if we're removing from the front, we take off a one and a two and then add them together, put a three on. And then after that, we would take off the front two. So that would be the three and the three, add them together, get a six, put it on. And then we take off two numbers. It would be the six and the four. Take them off, add them together, it's a 10. Now there's only one element in the list. And we can get the final sum. So now just doing that, which doesn't really look like a sum at all. Well, it actually computes the sum of the linked list with, I don't know, with fun. And this is how basically your computer works by like all the additions you do. C will only ever do like an addition between two numbers at a single time. So what it would do is something similar to this. So it would just keep adding numbers together, getting the result and then adding that result to another number, so on and so forth, kind of like you would do it in math. And when you use a linked list like this, just like adding stuff to the front and removing stuff from the front, kind of reminds you of like that stack of plates at like the cafeteria or something where you put one on the top, take one from the top. So this is a data structure that you will see come up and it's also kind of the same way that your local variables are stored in the function. They're kind of created and deleted in the same order you would put stuff on. So it's just put at the front, take off the front. All right, other fun stuff. So any questions about that? That's just a fun little example. All right, so other stuff that I think should be part of this. So one of the reasons to use linked lists, I know one of my third year courses loves using linked lists all the time, even though realistically, they're actually really, really slow. So you should typically not use linked lists unless you're like constantly adding and removing from the front. Arrays are generally always faster, even if you have to resize it from time to time and they use less space because they don't need to store pointers or anything like that. Arrays are packed really tight in memory. So we could think about how to make an array larger without. ["Ear Damage"] I officially have ear damage, says my watch. Oh, I have to follow up with that. All right, so now we get to resize an array. Wow, that's distracting. So if we want to make an array larger, wow, that seems like a very bad segue, doesn't it? All right, so if we want to make an array larger while keeping everything, our code could look like this. So let's just walk through it really, really quick. So say we create an array, we set its length equal to four, so we malloc four bytes. Again, we're going back to arrays. Check that malloc. Actually, we didn't run out of memory. So check that malloc did not return null. If it did, we'll just exit with a failure. Then let's say we populate our array with the values one through four. Print it, we should see just our array is one to four, right? So if I wanted to resize my array, well, it looks probably like a lot of code. So if I want to resize my array, well, then what I have to do is, let's say I want to resize it to fit seven elements instead of four. So what I might have to do with what we know right now is, well, I need to malloc at least space for that new bigger array. So I need to malloc seven ints. So I can do that. Go ahead, check that I didn't run out of memory. If I did run out of memory, well, I can just free that allocation that succeeded before, exit with the exit failure. And then what I could do is I would copy the contents of my old array to the new array. So in this case, I want to, I'm just trying to make it bigger. So I don't want to lose my first four values. So I could copy, in this case, since it's smaller, I could copy up to length elements to that new array. So I would copy index zero, one, two, three. So I copy it to my new array and then boom, I can now use up to seven because I have enough valid memory for it. In here, I put min of length and new length because if we make it smaller, yeah, we could shrink it by two. So we only want to, if our new array only has two elements, I can only copy two elements from the old array because, well, now I don't have enough room to fit element at index two and three. And then what I can do is I can free that old pointer. So now I'm done with that old array. I can free it and then set my array pointer equal to that new array. And then maybe I want to initialize all the other values to zero and then say what my new length is. So if I run something like this, before my array had four elements, I wanted to resize it for whatever reason. So I just did a new malloc with seven. I copied the old values over, so I didn't lose them and I just initialized everything else to zero just in case. So does this make sense to everyone? How I would do this? So, spoiler alert wasn't taught to you before but there is a function in the C standard library that will do this step for you. So re-sizing an allocation, it actually exists in the C standard library. The function prototype, it's called realloc instead of malloc, so it's like reallocating. So try and resize this for me, please. So it takes two arguments. One is the pointer, so whatever you got back from malloc before and then the next is the new size. So that is the total size of the new allocation you want. So you can either shrink it or you could grow it and it will deal with copying any of the old values actually for you. So you can also optionally, this pointer doesn't have to come from malloc but if you give it null, so if you set this to null then well suddenly you give it null and then a size and it behaves exactly like malloc. So you don't have to use malloc if you really wanted to for some bizarre reason. You could just use null as the first argument to this function instead but otherwise kind of behaves the same as malloc. So it will return null if there's an error so it can't do the reallocation for you. It can't grab enough memory or it can't shrink it for whatever reason. And the original pointer you gave it is not automatically freed for you so you would still have to be responsible for cleaning it up. Otherwise, if it does not return null then you have a new pointer to new size bytes and it will go ahead and copy all of the old values for you like I did previously. So it would copy up to old size bytes. You don't have to tell it. You just kind of get that for free. And the nice thing about this is C might not actually have to copy all the values. It just might make that allocation bigger because it's the one dealing with it. So the original, the pointer you might get back from this might be the same as the pointer you gave to it originally but now the memory allocator knows that okay well instead of having four ints worth of memory valid you now have seven worth. So that would make our code much, much simpler. So we could just rewrite it to use realloc like this. So if we wanted to make our array bigger well we would say realloc array. So this would be what we got back from malloc previously. And then we could say okay well we want a new length number of bytes. So we could assign the return value of realloc in the variable called new array. Check if there's an error. So if it returns null that means we have run out of memory and we didn't automatically free array. So we can free array and then exit. Otherwise if realloc actually was successful it would have freed the original memory allocation for you if it didn't just automatically resize it. And then after that we could just reset array equals to new array and length equals to new length. So any questions about that? So that will do most of the work for you. So you can resize arrays all you want. It's not really that big of a deal and computers really like memory being contiguous and being really close together. So even if things look like it might suit a link list better typically arrays are simpler and what you should go to. But sometimes link lists are make it easier to solve some problems if you really need to care about the order but you kind of need to get practice with them and kind of get some intuition behind them or you could just try implementing your solution with link lists and arrays and then seeing which one is better. So there's also other versions of malloc that actually initialize value for you. So if you ever write code that you malloc a bunch of memory and then you just initialize everything to zero well there is a function already that will do that. So there's a function called caloc. Don't ask me why it's called that. I don't know. So it takes two arguments so then typically it's used to initialize like dynamically allocated arrays. So it takes two arguments the number of elements you want in your array and then the size of each element so it will do the multiplication for you. Again, don't ask me why they wrote it this way. I don't know. And the benefit of using this function is it will actually initialize all of the memory to zero. So if we look at that so we can see the difference. And I was looking at the piazza posts over the weekend there were a lot of them and a lot of them were like random memory bugs of like, hey, I tried this on ECF and it didn't work. Typically at least most of the cases I saw that were asked to me is someone didn't initialize memory and they just got lucky somehow. Probably happened, if it happened to you, yeah, it kind of sucks but this is basically like what happened. So if I malloc say for integers and I print it, well, technically it should be any old random value, I don't know what the value would be but if I do the same thing, if I use call lock so I give it length, number of int and then print that array well then the first one might be random, the second one might not be random. So if I go ahead and do that looks like the first array that I got from malloc whole bunch of random values that all happened to be the same and the second one is they're all zeros but the bad thing about using the first malloc example is all these elements could have been zero and they could have been zero just to downright luck and if you depended on them to be initialized to zero well, if you tried it on ECF which did not do that because it's not guaranteed then guess what your program didn't work and it crashed and it did some weird things. So if you need to add this so it's something else you know so if you want to make sure you initialize everything to zero you can use call lock it will guarantee initialize everything to zero for you because here let's say let me do a bit of magic so malloc all right so I just played with my operating system a little bit so that code I didn't actually touch that code right looks the same as before so I just have a malloc and a call lock all right so now when I run it now so if you depended on it now your program works by sheer luck I just tweak something in my operating system that I won't tell you about but there's basically a way to just make see screw with random values where it can so now if I depended on zeros hey my program works didn't change my code at all and well that is the bad thing with using undefined values is because they might work sometimes if they work probably won't realize it and this is what makes C programming especially really really hard so any fun questions about that so another thing to add to your repertoire so before it was just malloc and free but now you have realloc if you want to resize your allocation and have it copy all the old values and any new memory from realloc it's undefined just like malloc but if you go ahead you call the array ahead of time while all the uninitialized values will be guaranteed to be zeros no matter what all right another fun skill you should know no all right so there's another data structure because if you resize an array often you might be like oh you might want to keep track of like the current length of it maybe in some you might see this again when you look at like C++ or other languages that kind of support a dynamically sized array so a vector is basically a data structure for an array that we resize and we always keep track of the current length with it because like a link list we kind of want to keep track of everything together in a structure so we can create a structure called a vector so that can keep track of like a pointer to the array typically they just call it data maybe the number of elements in that array we could call it length and maybe just for like optimization we could have another field called capacity which just keeps track of the number of valid elements we could use and the reason we might want that is it could save us some calls to reallocation if we want to be like lazy about it so this just keeps track of the capacity and holds everything so similarly the link list we could create a vector dynamically so this looks a bit ugly because to create it we could dynamically we would malloc the structure itself so we could malloc the size of the vector so that would hold like the pointer and then the capacity and the length and then we could initialize the data to hold that number of integers so we could malloc length times the number of integers check that you know we actually had memory for it check that it's not null if we ran out of memory we'll just exit our program and then we could set the length to the length and the capacity to also the length because we can hold that number of elements and then return a pointer to the vector now if we want to resize a vector we could write some code that looks like this so we could write vector resize that takes a pointer to the vector as the first argument and the new length and this is one thing that could save us like we might want to do this for some program so if we're making it smaller so if the new length is less than our capacity maybe we want to just not tell C that we want to make this even smaller we'll just note that our new length is shorter now and we'll keep the capacity the same so that if we want to make it bigger well we don't have to call realloc or anything like that it's already there for us for free again this is like what makes programming fun because that might be a good idea sometimes might be a bad idea some other times it's just optimizations we might make and you will get more familiar with this as you write bigger and bigger programs otherwise if I want to make my pointer bigger well I could call realloc like before give it the data then give it the new length and then check that it doesn't we didn't run out of memory and then I could record the length as the new length and then the capacity as the new length because I just made it bigger so any fun questions about that? so fun things I could do with it now is now it kind of looks a bit better so here I could create a vector of length four which is basically just a dynamically sized array that's all nicely packaged together in that structure just so it's a little bit easier to use so I could create length four and then to iterate through them well we know how to access pointers in our structure so it would just be like a for loop before but now that length is actually kind of associated with that vector so we don't have like a random pointer and then a like array length int and we have to remember that they're tied together so now that they're in a struct they're tied together in the struct and probably a lot less error prone so we can actually go over the length of the vector and then to access all the individual elements well we just access the data field that's a pointer to an int so we can just use the array subscript like we used before we could set it all so that in the array at index zero it's equal to one and then so on and so forth we could print the vector and then we could resize it so we can make it seven and then print it again so it should show like one, two, three, four then zero, zero, zero then we could make it smaller we could shrink it back down to three and then it should just print one, two, three so if we run something like that whoops, should look something like that makes it a bit easier to use a dynamically resized array just keeps track of everything like LakeGlyce it makes things a lot nicer to use so any questions about that probably not useful in this course but might be useful for you later especially if you have, you know lots of different dynamically sized arrays floating around in your program all right, looks neat some stuff that is outside the scope of the course so now just to make sure we understand stuff we can do a final question on linked list because you'll probably get questions like that so here is a question from the final in 2022 says write AC function called reorder the prototype of which is given below that reorders the nodes in the linked list such that the nodes with the value zero appear at the front and the nodes with any other integer value appear at the end of the linked list while maintaining the original order of the non-zero nodes so they give you two examples so if your input is zero, zero, fifteen, zero, zero, thirteen, ten then all of the zeros should come before so there's four of them so it should be one, two, three, four and then all the rest of the numbers should maintain the same order so should have fifteen, thirteen, ten and then second example for a list has one, zero, nineteen, zero, zero, five, zero well, same idea, all four zeros should go to the front of the list and then the other one should be maintained with the same order so we should get one, nineteen, and then five so it also says you're not allowed to copy or modify the data member of any of the nodes so you should be able to just reorder them without creating new nodes, without deleting nodes just by rearranging their next pointers or the head of the linked list in order just to reorder them so they tell you that the prototype is just reorder, link list they like doing type defs with like capital letters I like to, most C programmers like doing them in lower case with underscores between them and then underscore T at the end again, personal preference up to you this is more of the case you'll run into in like Java or C++ but just comes down to personal taste doesn't really matter let's think how we would solve this for a second and then we can try and take it up all right anyone want to describe an idea don't have to use code or anything for it so I could go through the list keeping track of the last non-zero pointer and then if I encounter a zero I put it after that one okay so that sounds good so I could go through the list as soon as I encounter a zero I could be like okay well I need to move that to the front of the list or behind the last non-zero so in this case there wouldn't be anything before it right so I could be like oh well if it's already at the front of the list shouldn't really matter that much I probably don't have to touch it if I have a zero here well I could move it to the front of the list in this case I could leave it alone if I have a 15 okay it's already ahead of the zero so that's good hit this zero and then yeah easiest thing to do I could try and move it before the 15 or what might be a bit easier is just to move it to the front of the list so I could like the order doesn't really matter so in that case yeah I could and if I hit another zero I could move it to the front of the list and then make sure that I keep track of the last non-zero value which should just be whatever the last previous is if I moved it to the front so it should be 15 so then yeah I just keep updating it like that and that seems like it should work so let's try coding that in the last five minutes so I went out and I wrote a test thing so it gives the two examples so our input and then our output after the reordering right now we haven't written anything so it does the same stuff so now here in our reorder well let's keep track of previous and then current can start at the list head so we could have our normal while loop so while the current does not equal head or sorry null could iterate through things check if the current value is zero if it's zero then I want to move it to the front of the list right so if I want to move it to the front of the list well what should I check so first thing I should probably check is if if previous is null well I probably don't need to do anything right previous is null means it's the first thing in the list means I don't really have to do anything at all so I can just kind of ignore that so I only have to do something if previous does not equal null right which means that well I've actually encountered something it's not the head of the list or it's not a zero so if previous does not equal null what do I need to do so some things I could do is well current is like I might want to do what I did before and take the next of current save it in a variable so that's currents next right and then I also want to move that to the front of the list so if I want to move current to the front of the list well then I have to set the currents next equals to the linkless head whatever it's currently pointing to right and then set the linkless head equal to current and then I want to set the previous nodes next equal to the old next of this current right so I'm just throwing if it's a zero and there is a previous I'm just throwing it to the front of the list over and over and over again right am I missing anything does this look good so one thing we could do that unfortunately don't have the benefit for in the exam is we could just run it if we run it we have been looped ourselves since we only have a minute you might have to think of this later when you watch it we have to be careful it what's happening here so what's happening here is change the current kind of incorrectly so current is currently head of the list yes so the current node currently still the head of the list is now the head of the list and then here we're pointing to the next element and then eventually we'll run to a zero again because essentially restart the beginning and just keeps throwing itself at the front of the list over and over again so we need to kind of update current to be next and then we could actually use the keyword continue so we don't accidentally run this code that sets previous to current and current equal to current next because here if we move the current node to the front of the list then we don't have to update its previous value because the previous node is still the previous node because we move the current node to the back so we don't have to update the previous anymore so we keep it the same and then we set current equal to next and if we run that it works yay sorry we are out of time so I will leave you with that you can ask any questions later I will be around so just remember phone for you we're all in this together