 Hello folks welcome back. It's been it's been quite a long time since since last stream I did my thesis defense, which is up on YouTube. I'll put it in one of these corners. It's like a little YouTube card But apart from that I haven't done like a coding stream in quite some time and even this one so this will be a Crust of rust stream Specifically, so it'll be a shorter stream Where we'll be talking about sorting algorithms and rust and particularly we'll we'll try to implement a few of them and the reason I wanted to do this specifically is because Sorting algorithms are something that you come across in all sorts of languages People use them for coding interviews, even though it's a terrible idea and you shouldn't do that But but even just like in almost every CS education you learn about them in all sorts of language tutorials You learn about sorting because they're so common and like it's unclear whether they're useful like it's very unlikely You'll actually have to implement your own sorting algorithm for anything real that you build But the fact that they're so common means that they're a good comparison point between languages And so the purpose of this crust of rust stream isn't really to teach you sorting You can read up on these algorithms on your own instead the goal with this is to Show you how these algorithms would be implemented in rust in a sort of idiomatic way So that you can compare that to languages you might be familiar with and then over the course of that my guess is that While implementing these algorithms, we might end up sort of stumbling over a bunch of different interesting rusts topics So unlike some of the previous streams where I've been focusing on a particular rust feature this one is more focused on on a broader set of problems for us to Sort of code up and then see what we learn along the way As before here I'll sort of take questions as we go because it might be that there are things there are features of rust that I use While we're doing this that you are curious about and want to see how work or why I did it that particular way I will say so I took a poll on Twitter as to whether this should be the topic of the stream or whether I should do another stream topic which is specifically Implementing an improvement to a concurrent data structure. I maintain. I'm going to do that one too That'll be a longer coding stream that will probably happen in a week or two. I'm not quite sure yet All right, so let's Let's dive into this and and we can't really start the discussion before we talk about the ord trait So the ord trait in rust is the way that you express Types that be can be compared relative to one another now if you look at the ord trait You'll see that ord requires that the type is the eek. So eek is sort of complete equality and Partial ord and partial ord is a partial ordering. It says that the Type is partially ordered and you see that partial ord is generic over the thing that it can be compared with so for example, you'd imagine that Some type can be compared to many other similar types. So for example and a u16 can be compared to a u32 and And and and so therefore there's a that it is possible to order them relative to one another even though they are not the same type And that's what The the parameter to partial ord here is for ord. You'll see that it actually requires that you implement partial order of self and if you read the description of order a little more you see that The requirement for ord specifically is that the type forms a total order and You can see the exact definition here But basically the way to differentiate between ord and partial ord. It's a partial ord might say for for some two elements These do not have a relationship to one another like is the string 42 greater than or less than the Number 8 or the string foobar or the number 8 or is one greater or less than the other It's it's unclear, right? They don't really have an order With ord you require that a given type Must be comparable with itself, of course But also you require that comparison to basically always have an answer to the question is is a greater than or less than or equal to Be it can't just say it must always have an answer. That's roughly what total order means It total order also requires that things are transitive, but this is not a property. We'll really avail ourselves of here The reason that we're using ord instead of partial ord for the purposes of sorting is because if we did partial ord It's really hard to order a thing if the order of some things in that set doesn't matter And so we're actually going to use ord. This does have some weird properties because There are certain types and rust that you might expect to be orderable, but are not specifically the the floating point number types those implement partial ord, but not ord and the reason for this is that Floating point numbers also have this like Oh The name escapes me now, but they have a special value Nan not a number Which is used to express sort of floating point operations that don't have a well-defined answer and and Those it if you have a nan and compare it to some other floating point number It's unclear how they're really to one another. So f64 and f32 do not implement ord Yeah floats are also not Eek for the same reason specifically that if you have two not a numbers, they're not equal to one another Which is weird Which sort of it it's weird for two things that are equal to not compare equal But it's because not a number that if you do one mathematical operation that gives Nan and another mathematical operation that gives Nan They're not necessarily the same Nan. That's why it works that way Okay, so we're gonna be relying on the ord trait and if we scroll up here a bit and look for sort of sort methods You see that there are a bunch of Sorting mechanism all mechanisms already implemented for you and rust the prime of these is if you have a slice You can call sort and requires that the t is ord And it just sorts the slice in place Now you'll see here that it says the sort is stable if you're not familiar with with sorting algorithms a sort that's stable just means that if you have two elements in the list or in the in the slice that are equal it won't Swap them while doing the sort This might seem like a property. That's weird to care about Because you might think if if I have a list of numbers and two of them are eight Why would I care whether it rearranges those eight or not? And it's true that for sort of primitive types is rarely makes a difference But you can imagine you have some complex type that where you have two Elements in the list that compare equal or but are not the same And you actually care about the ordering because say one happened before the other in some other sense But they are equal as far as sorting is concerned and you don't want them to be reordered in general Sorts that are at you sometimes pay a little bit more for a sort that's stable Because the sort during algorithm is more constrained in what operations that can do You'll see that there's a sort unstable as well that does not have this Guaranteed that it's stable and that allows it to either use less memory or or be faster So we'll look at that you'll see also that there's a note here about the current implementation and The current implementation is an iterative merge sort inspired by Tim sort I don't know where that will necessarily be implementing the the rust sort function Like that the specific Implementation they have we might get to look at Tim sort. It sort of depends on how long the other things take. I'm not sure yet You'll also see that there's a sort by so the idea here is that Sort is if you have a a slice of Elements you just want to sort directly Sort by is if you have a slice of elements where you want to sort them by a custom comparison function So one example of this might be that you only want to sort them by a field of that struct rather than the full struct itself It might be that you want to do a reverse sort, right? You want to sort it backwards In which case you sort of want to do that the standard comparison between the elements, but sort of flip the result Doing a reverse sort is probably the most common use of sort by There's also sort by key which is Specifically for the case where you want to sort by a field or something like that Where you just provide a a function that maps from the type in the slice to some other type that is itself ord And that it sorts by that by whatever that that function returns Ultimately We'll probably just implement sort because all these other ones are expressible in terms of what we implement with sort and What we're going to do is essentially define a sort trait And then we're going to implement that trait using a bunch of different sorting mechanisms And there are a lot of them. So here's the Wikipedia page on sorting algorithms And you'll see that like there are a lot of them and they have fairly different properties Some of them are just straight up bad and but many of them there's sort of a trade-off between You see here whether they're stable how much memory they use their average complexity. So this is How many comparisons does it need to do for an for if you like took every possible Slice if you will How long would it take on average to sort it where n here is the length of that slice the best case is What is the fastest it can possibly run? So it might be for example that for certain algorithms If the slice is already sorted they do they just walk the list and check that it sorted and then do no work So that would be the best case is sort of the fastest it can run the best case input and the worst case is like Imagine that the slice was or was adversariously Adversarially ordered before you called sort. What is the longest the algorithm might take and of course you want an algorithm where the The worst case is as good as possible and We're all these values are as good as possible But ideally like the best case and the worst case are the same as the average case and the average case is low And you'll see that in general It tends to be that the best you get to is End log n for the average and there's some mathematical reasons why that sort of has to be the case But you see that some of them have a best case of n which is usually The fact that if they're if the slices already sorted these algorithms do very little work But you see the the worst case often gets quite bad, right like n squared is a very large number Whereas n log n is not that large So I think what we're gonna do is walk this Sort of in terms of the order you tend to learn about these things So we're gonna start with everyone's favorite bubble sort and bubble sort is Real stupid like it's not very good in terms of complexity. It's average performance is n squared, which is not great But it is very very straightforward And I'll show you why the other thing I want to do on the stream is sort of implement a very simple benchmark for these That just counts the number of comparisons that each algorithm makes And then print that out so it prints sort of the length of the input and how many comparisons each algorithm did for that input And then we should be able to very easily see just how much work How much more work one is than the other? Bubble sort is real stupid. Yeah, it's true All right, so let's do a cargo new I guess lip and we're gonna call it I We're gonna call this sorting library we're gonna call it Descending see it's gonna sort an ascending order, but descending is a funnier name Bogo sort is pretty great Sortify sortify is also very funny. Oh sort the letters in sort. That's good. I like that. So that's What? O RST Right. Yeah, or that's great. I love that It's sort sorted All right So here's what we're gonna do. We're gonna mod Bubble sort And we're gonna have a trait here that's gonna be just sort Or I guess actually we're gonna have a trait sorter and the sorter is gonna be given a I guess actually this is gonna be an instance method because the sorters themselves do not we don't expect them to have any state So sort is really just gonna be given a A T and it's gonna be given a mutable slice of T Where T implements ord and I guess this is gonna be the slice And then what we can do is we can have a sort function If we wanted to be really fancy, right? We have a like an extension trait on slice that lets them let's Something sort using a sorter. I think instead what we're gonna do is just have a freestanding sort function that people can call And so this is gonna take a T and an s And the slice is gonna be a mute T Where T is ord and s is a sorter and All it's really gonna do is do s colon colon sort This is our public function and this is our sorting trait And then now all we have to do is implement the sorter trait for each of our algorithms And the we can just call sort on whatever we wanted and of course we could have here The sort of what are we gonna call this like Type actually let's go down here and write this as a test. So we're gonna have a Struct Standard sorter and we're gonna implement sorter. It's gonna be that's fine We're gonna implement Sorter for standard sorter. I used to have a Forget what the implementation of this was CoC Command no, CoC action No, that's too bad I used to have it set up so that I could just like press a hotkey for Automatically adding the methods for the trait, but I forget what the hotkey is and I don't want to look it up because it takes too long How much harder would it be to implement these for iter or iter mute? It's actually much harder to do it for iter because You don't know how many elements you're gonna get like Realistically, it's really hard to it's fairly hard to sort of stream It's significantly limits what algorithms you can use unless you collect the stream into a vector and then you sort the vector, right? So it's unclear that buys you very much And this is why in the standard library to you generally see sort on on slice And all this is really gonna do is just called slice dot sort, right? I guess here we have to use super right, so we're ever gonna have a test here That checks that I Guess we're gonna have like things is gonna be a back of Four two three one and then we're gonna call things dot Actually, we're gonna call sort of mute things And we're gonna do that with the standard sorter And then we're gonna assert that things is now equal to one two three four This is sort of a sanity check that the the setup we have is roughly right So Let's cd into orst and run cargo dust Now, right. I don't have bubble sort yet. So let's comment that out How's the font size do you want it to be smaller? Yeah, random access is also very useful in sorting. You basically need that So you could use exact size inter iterator You would then know how many many elements to expect but ultimately you would need to have all of them before you Could really start sorting them in a reasonable way Right the challenge is that the later elements might end up shuffling a bunch of things around you'd basically be forced to do Sort of something like an insertion sort Which is pretty bad Bit smaller. All right, let's do that better Let's do that All right, so that works for us. So now let's go and implement bubble sort Actually, it's do Do we want to call it bubble sort or just bubble? Let's do bubble sort. That's fine So we want to use super sorter we want us sort of Pub struct bubble sort And notice that these are all These are all Just unit structs because the sorter itself doesn't have any state You could imagine that the sorter had like configuration that was stored in it, but it doesn't really have state Why does Vim say object object in the status bar, I'm not sure It might be that I have that my rust analyzer is outdated or something It definitely normally doesn't do that I also don't know why it says like a heart rust analyzer. I think like something in my setup is broken But who knows why? All right, so we're gonna implement sorter for bubble sort And let me just copy that method from over here and so this is the thing that we have to implement Right and so the question is what goes here My chat window is like no longer auto-scrolling which is pretty annoying I Have coc set to stand Manually compiled rust analyzer because I want to run it on like the latest nightly Okay, so bubble sort bubble sort is a real stupid sort where all it really does is It just walks the array and swaps anything that's out of order You may be familiar with this already. So what we're gonna do is sort of we're just gonna have a loop We're gonna set Swapped equals false at the top of the loop. Then we're gonna walk all the elements of the slice This is just gonna be zero to slice dot Len and If slice I Is Greater than slice J Then we're gonna just do slice dot swap I and J and set swapped equals true In fact, we can do this while swapped Opticals false. All right. So this is why people love bubble sort It's not quite correct yet. It has some boundary problems. So we're gonna have to fix But bubble sort is really really just It's really straightforward to implement. Oh Man, why is this there we go So all we're gonna do with bubble sort is just Walk the slice and anytime we see something be out of order. We're gonna swap them We're gonna keep doing this until we've walked the whole thing and we never had to swap anything Right very straightforward, but also super slow You'll notice that there's a there's one challenge here, which is that I plus one might be out of range and the way we deal with this is just if I Is slice dot Len minus one Then we continue and the reason the reason for this is because we When you're looking at the last element, there's nothing for you to compare it with and it's already become compared with this Predecessor because that was when we were at I minus one, which actually means that we can just do this Same thing and now this will always be in range right so Isn't this going to stop on the first swap? No So while we we continue while we have swapped something, right? And we keep setting swap to true whenever we do do a swap And so we're gonna keep looping until we have walked the whole thing without setting swap to true at which point the while will end I mean we can check this right so Down here you have a little test It works and this will use the bubble sort Super And in theory if we run cargo test now great bubble sort sorts We can even if we wanted to make this sort of be an odd length And See that it's still sorts Why is my chat not auto-scrolling? This is really awkward Don't quite understand what's going on there. Let's hope it just fixes itself Oh, yeah, you're right. We if we wanted to we could do one to slice Len And then do like I minus one to I This has the same effect and it's Maybe shorter on unclear that Unclear that it's nice The YouTube stream delay is real slow So slice swap is sort of the the thing that's kind of interesting here It means you don't have to like read out one and then set the other and then set the other back Like you don't need to temp variable and I think internally it you it does use mem swap Right, so why does this require the underscore this requires the underscore because We have two type parameters to this function one is the type of the thing we want to sort and the other is the sorting algorithm The only thing we could really do here is we could make these be reversed The problem is that here because we're trying to name the we're trying to name the sorting type here Rust won't let you if you're gonna name the generic parameters You have to enumerate all of them You can give underscore for the ones that you don't want to name and have it be Inferred by the type system, but you do have to put placeholders for it. Oh This is extremely annoying Let's see if this helps Okay, so we have bubble sort Let's move on to the next sort. Let's go back here. So we did bubble sort bubble sort is really bad But it works pretty well and in in the sense that the implementation is very straightforward Our next contender that's then is insertion sort insertion sort is also as it says It is much less efficient on large lists than more advanced algorithms, but it has a simple implementation love that all right so What is the algorithm for insertion sort? Well insertion sort is I won't make you read all of this But it might be easier to show in code insertion sort and insertion sort and Let's just copy all this in here so insertion sort is Also fairly straightforward the strategy we're gonna take here is that we are going to Sort of keep we're gonna divide the slice into sorted and not sorted Where the sorted part is sorted and the not sorted is not sorted and sorted is initially empty not sorted is initially the entire list And then we're gonna do is we're gonna take one element from not sorted And we're gonna place it in the correct place in the sorted list and then we're gonna do that until there are no More not sorted left Why not sort or sort directly without the freestanding function? Um, we could do that. So we could do like insertion sort sort Mute things this also works And it's sort of nicer The downside of this one is that you need to have the sorter trait in scope So this will only work if you have a use sorter sorter. I believe But you're right. It is it is a lot nicer That's fine. It obviously doesn't sort yet, but you're right. This is much nicer. Let's do that instead And I guess we'll do it in lib 2 The s not being capitalized. Yeah, fine. We can do that. I Guess let's do that for bubble sort as well All right, you happy now It can't be partial eek because partially it doesn't let you order things And it can't be partial or to because as I mentioned in the beginning you can't really Order a slice if some elements just say meh, I don't know what the order should be here And it's sort of unpredictable which ones, right? Sort doesn't look like a word already. I know right Okay, so You iterate over search results and Vim with the n key so what we're gonna do is sort of have a some some Threshold I let's say that is unsorted and unsorted is initially gonna be zero So the idea is that everything beyond unsorted is gonna be unsorted and everything before unsorted is gonna be well sorted And in fact what we can do is we can immediately start this as one Right because a list of one element is always sorted. We don't have to sort that first element So what we're now gonna do is we're gonna Walk all the elements We're gonna say for unsorted in one two sorted dot Len So we actually need to keep this as a variable because the iterator variable oops sliced up Because the iterator variable can can keep track of where that index is already and at this point slice unsorted and onward is Unsorted is not sorted Take slice unsorted and place in Sorted location in slice up to unsorted. Is that roughly makes sense what we're planning to do here and This is a little weird right because imagine that that you have like Let's say that it looks something like this, right? So we have one two three because everything before there is sorted and then one three four And then the last element is two right so now we're gonna pick up the two and we're gonna place it in Sorted in the right place so the two needs to go here. That means that everything else has to be shifted over So there are a couple of ways you can do this the easiest way To do this is just to keep moving the element. You just took left until The next element is smaller right so what we do here is We end up removing the barrier right because we're moving the element into the into the sorted side So it's gonna go over here. I Guess let's indicate that this way And then we're just gonna keep swapping it oops to We're gonna keep swapping it to the left until the next element is smaller than the current element Right so while slice Is unsorted while slice I minus one is greater than I And again, you'll you'll recall this While I is greater than zero and that And I should say slice Right, so we're just gonna keep shifting it left until it no longer needs to go left That just sounds like bubble sort going through an identity crisis. So it's not quite right. So bubble sort is Even worse than this because bubble sort walks the array and it just Swaps things that are out of order each time whereas the insertion sort doesn't really swap Everything it doesn't walk the whole thing right it walks backwards from the thing that you're now placing And so it it only does the swaps that it sort of needs to do and Okay, so you might wonder Okay, so some people are saying like isn't this more of a bubble sort than an insertion sort It's it does have a little bit of the semblance of a of a bubble sort But What this is really doing is doing the shifting at the same time. Oh, yeah, you're right. I'm Minus equals one So Imagine that we just picked up this to and try to place it here What we would have to do is shift the remaining elements all over by one, right? And I mean, okay, there's a there's an alternative way to do this, right? Which is to walk the array until you find the location that you need to put it in and then like Mem copy all the things from that position forward one over and That would also work. It's a non overlapping mem copy So it's not that efficient anyway because ultimately what it ends up doing here Let me see if I can draw this actually Oh, yeah insertion sort works a lot better if you're someone mentioned this in chat, too if the Storage mechanism you have if it's not a slice But if it's a linked list then insertion sort is great because you don't need to do all this shifting over You just find where it goes and you stick it there You just like if you have a linked list, right? You just pull them apart and stick it in the middle The problem with the slice is that you need to move all the things over So let me try to draw this out So we have this list And it has a bunch of elements it has one three four two Let's say five right and it's sorted up to this point And so now we're trying to move this barrier over to here, right? So what we're gonna do is we're gonna pick up We're gonna pick up the two and we realize that we have to move it to here Now there are two ways to go about this right one is to Go over here right So one is to walk the list left to right Until we find the slot where it has to go into right so it's like oh it has to go here and then Sort of store two somewhere to the side and then take these elements and shift them all right by one right, so that's gonna turn into This goes here and this goes here So that's one swap to swap The other is what we currently implemented, which is swap this left then swap it left again so they both end up being two swaps and The the biggest difference between these is that this one walks from the left and Looks at each element to the sort of left of the target location. Whereas our swap goes from the right It's you don't know in advance whether walking from the left or the right is gonna be more efficient Because you don't know where this element is gonna go They're gonna have slightly different runtime characteristics based on the data, but in terms of complexity. They're the same Would it be cheating to use binary search plus insert? Yeah, I mean we could totally do that It is true that you can be more you can be a little bit more efficient here with a binary insert To reduce the number of comparisons you have to do it still has to swap all the elements, right? So what's being proposed here is that like let's do like if not smart Then do this else do this Smarticles false All right, so this one is gonna use Use binary search to find index then use dot insert to splice in I So here what we have to do is do Slice dot binary search so binary search is a method that's provided by the standard library on slices which gives you a type and a Type that implements ord, and it's gonna tell you where that item should go. Oh, my hair is being weird today So if we do a slice dot binary search for slice of unsorted This is gonna give either an eye Or an eye, let me explain why these are different in a second and This is gonna tell you where that value Should go in slice assuming that slice is sorted and it's gonna use binary search to get there So a binary search if you're not familiar with it is like you look at the you look at the if you have a slice Is this long you look at the element in the middle And if the thing you're comparing is larger than the thing in the middle Then you now look at the middle of the second half if it's smaller you look at the middle of the first half that way you do log n comparisons rather than n comparisons In terms of trying to find that location and then we use slice dot insert. So If we pull back here for a second This might give me really unhelpful Oh That's only implemented on VEC I think slice has a splice method No, I think there's a splice so the reason why maybe there isn't well, that's awkward Yeah So The reason why there's so there's an insert method on vector And what it does is it it does all this shifting for you So you say like I want this a month to go in position 3 and then everything beyond position 3 gets pushed over in the implementation By using some like smarter mechanism than just swapping The reason why this is only implemented on vector is imagine that you told it I want to stick this element here and pushing all of the remaining elements out ended up You ended up exceeding the capacity of the vector that it might have to reallocate Right allocate more memory so that those elements can fit in this case. We know that that's not case We we wanted to overwrite the element like we wanted to sort of drop off the element that the shifts To the right because we know that that is the element that we've picked up and are inserting But slice doesn't have a method like that. So we would have to do all the swaps anyway so here we would do like a Well, I don't even know that I want to write this code But it basically would do this sort of swap loop except it wouldn't have to do the comparison It would only have to do the the actual swaps You're right. This should be Dot-dot unsorted So for that reason I think we're gonna just ignore the smart implementation for now It's true that it would be it would use Log n well n log n it would be n log n instead of n squared Which would be good, but you still end up with n squared swaps so like It's still pretty costly like this is not the way to get better sorting is not to optimize Insertion sort. Oh There's a rotate right. Oh Okay, so rotate right might work Slice rotate right. Oh That's pretty good. In fact, then we can do even better So let's use rotate right So rotate right shifts all the elements in the slice over by one But with a wrap around and this you can do without resizing because you're gonna wrap them around, right? So we're gonna slice everything over by one, but here what we can do is actually be a little sneaky We can say we want to rotate everything right starting from the target location and Ending at the element that we're moving and what that's gonna do is it's gonna shift all the elements that That are before the element we want to put at the beginning or put at the the eye We found over that's gonna cause the last element Which is the one that is currently unsorted to go to the front, which is the position that we want it to end up in oh is rotate right Only on nightly That's interesting. No, it's not a nightly great. So we can use it Okay, so that brings us to you. Why does binary search return? Okay and error It returns. Okay. So okay, so imagine that you're you have a list like One three five Right, and then you do binary search for two Well, let's do let's do three first and you do binary search for three. This is gonna return one, right? Because the three is at the one at index one Right, I guess let's make these a bc. It might be easier or a bc is bad a c Be and we're gonna search for C. That's gonna return one But you can also search for elements that aren't there. So if I search for B, this is actually gonna return a This is gonna return error of zero Let me just double check that I'm not lying to you Sorry error of one So this value is the if you get an error It's I didn't find an element that matched but if you were to insert this element This is the index at which it should be right So in this first case, it's we found an element that has the exact same value In which case we can put it before or after it doesn't matter because it's equal This would matter in terms of whether it's stable, but it wouldn't matter for the purpose of leaving the array sorted For the error case is saying this is where it should be like all the preceding elements are smaller and all the succeeding Elements including the one I just gave you are larger. And so those are all the ones we're gonna shift over So let's let's see if this works, let's do a test run. Okay, so with smart equals false What about with smart equals true? Great. So both the smart and non-smart version works So this is where we can actually have values on this, right? So we can say smart It's gonna be a bull Although that would mean that sort has to take so we actually need to change our sorting implementation a little bit here We need to say that the trait takes a reference to self which means that Let's have this just go away This is gonna take a self and just not use that parameter This is gonna do that And for bubble sort This isn't gonna make a difference because we don't really have a configuration for it It takes a self parameter, but it doesn't use it and this becomes bubble sort So the the change here from a double colon to a dot is that previously It was you can think of it as like a class method from other languages, but it used to be a I'm an associated method of this type Whereas now it is a method of the type. So now actually we're constructing a bubble sort Value and the recalling the sort method on that value And these are different What happens if you binary search on an unsorted slice You basically get a random index. It's not quite random, but but essentially And now for insertion sort what is nice about this is that we get a self and so now down here we can say If not smart and if smart Some people are asking whether there's a better way to write this match I don't think there's an easy way to like Unwrap The unwrap both and assume they're the same type You could probably do You could do this with unwrap or else. So you could do unwrap or else I This will do the same thing but But I like the fact that this actually documents what the two branches are so you don't actually need to use a match here, right like Let me write it down here. It might be cleaner than trying to see it all the way to the far right unwrap or else I It's the same thing But but I prefer this version because it's more explicit in terms of like how it performs is probably about the same All right, so now we have insertion sort we have an error somewhere, right this has to be dot Smart is true. It works dumb And it works smart great Isn't the static function when you don't need an instance of the object to call that method. Yeah So you can think of it as a static method In the future, you'll also be able to do this with or let patterns, which I think someone Mentioned in chat too. So you'll be able to write Soon and I'm very excited for this is you'll be able to write this whatever like slice unsorted etc And the same and they basically let you do so you can already or patterns, right? So you could write this as this You can already have or patterns in in match statements, but you'll be able to do it in in let statements, too, which is really neat Oh the PhD mug. Yeah, my girlfriend got me this after when I graduated. So it says Dr. Jang set on one side. Oh Maybe can't see it and then it says a fucking done on the other side. It's great All right insertion sort Let's move on to selection sort And actually this time let's copy insertion sort to selection sort and Insertion to selection All right. So selection sort. I guess let's go back to the Wikipedia's A selection sort is also and squared. So it's inefficient. It's generally worse than insertion sort But it is very simple One thing that is nice about selection sort is that you can do it like entirely in place You don't need to use any additional memory And when I say additional memory what I mean here is that behind the scenes when you're doing this like swapping You often have to like store something to a temporary variable and in general that just like doesn't matter Like you have to store something of size t in addition and you store it on the stack But there are some cases where you're so memory constrained that you just can't store extra elements And with selection sort you don't actually have to do that so the idea behind The idea behind selection sort is that we're actually just gonna walk we're gonna We're gonna pick this you're gonna find the smallest element of the list and we're gonna stick at the front Then we're gonna find the smallest element in the remainder of the list And we're gonna stick it at the front and we're gonna find the smallest element of the remainder of the list We're gonna stick it at the front and then at the end you've now sorted the array All right, so obviously this is super slow But it doesn't use any extra memory and it's fairly simple, so let's look at what this looks like So we're gonna do for Unsorted in one to slice and just like with insertion sort We're gonna keep the prefix sorted and the suffix is not sorted, but here The implementation we're gonna have is gonna be a little different Selection sort does not have any arguments It is In some sense always dumb Obviously these tests are bad like this should be like prop test or something because this is entirely stupid This only tests a particular case Whereas with something a prop test or quick check or something You could actually have a generate like arbitrary vectors and make sure that they all end up sorted You can you can also swap with XOR as chat is pointing out but For something like the rotate right you can't necessarily or it gets really convoluted if you wanted to do with just XORs So usually you end up using temporaries All right, so so our plan our plan here is gonna be Smallest in rest is gonna be the sort of if we really wanted to cheat here, right? We do a slice Unsorted To the end min And this is gonna be Yep, and then we're gonna slice dot swap unsorted with smallest in rest This I guess this should not be min, but like the min I Fine, well, we'll do this the the slow way so Smallest in rest is gonna be we're gonna assume that it's the current element and then we're gonna walk the the rest of the list for I in Unsorted to slice dot Len Technically, this is gonna be plus one and if Slice I is less than smallest in rest Then the smallest in rest is gonna be I and if I'm sorted not equal to smallest in rest There is wobble That's Yeah, so this what we're doing here we can start from one we don't have to start from zero because the Slice of length one is always sorted oh You're right. It's Not it has to be the smallest element. You're completely right This is different from insertion sort in that we're gonna be moving We're not gonna be moving elements to the appropriate place in the slice We're gonna assume that it always is the largest of the sorted. So think of it this way The smallest in the remainder is gonna be the Largest of the thing that's sorted and therefore we need to make sure that the first element is the smallest of the whole slice And that's why we can't just assume that the first one is already sorted And so here what we really do is we just walk the whole list and Continuously check for the smallest element in the the remainder and shift that to the beginning of the remainder And then we keep shrinking the remainder What I was looking for earlier is There I think there is a Let's see unsorted So there's a there is a I think this rest analyzers give me the wrong thing here. I Guess not I Was pretty sure there was a min, but I guess not oh It's on iterator Yeah So slice is an iterator right so you can call dot min and that gives you an option smallest element The reason it's an option is because the slice might be empty But this gives you the smallest value not the smallest index now We could in theory do this with some like a little bit of magic wrapping by using the Enumerate function on the iterator. So I might as well show that too So a sort of different way let's do or Is to do dot iter dot enumerate dot min by key Dot unwrap or I guess expect Slice is non-empty. So this is smallest smallest in rest actually this is going to be the I and the value and We're gonna have smallest in rest be the index Right, I'll explain this code in a second No That's almost certainly a lie. Why is that? failing Lifetime may not live long enough. That is very interesting indeed Wait, I need to see this error properly from cargo That's why Okay, so what we're doing here is and the annotations from a rest analyzer actually going to be a little bit helpful here So we take the remainder of the slice right everything from unsorted and onwards We created an iterator over it We call enumerate which is a method on iterator that changes the iterator from be just over the value to being an iterator over the Index and the value so a tuple of index and value and then we call min by key which gives us the smallest it walks the entire iterator and it gives you the smallest element according to whichever Value you get from calling the provided closure on each element and in this case what the closure does is it fishes out Just the value right because every value in the iterator after enumerate is a tuple of the index and the value And we only want to find the minimum by the value We don't want to find the minimum by the index for example, and so we fish out just the value and this technically gives an option back because well, it's It it has to because the iterator might be empty, but we know that it's not empty so we can expect and the the lifetime issue I was seeing was because min by key gives you a Reference to each element and the reason it gives you a reference rather than the element itself is because Min by key is going to walk all the elements and then return one to you So it can't give ownership into this closure but V is also a reference itself and it's a reference into slice and The reference to the tuple only lives for the duration of the iteration because it's an Reference to a value that was yielded by the iterator But what we're trying to return is a reference into slice right and after the iterator returns the iterator values, which is what this reference is The iterator values no longer live so we can't return it and that's what the compiler was complaining about So what I'm doing here is I'm telling the closure to de-reference the tuple And then fetch out the V as opposed to giving a reference to the second element of the tuple This might be easier to show if I just said min by key gets a T and Previously what we're doing where was The way I was writing it right I wrote it as just this and the compiler was complaining right this was an error Because what I what that is equivalent to doing is this Right so that gives that same error So this is clearly a reference to the tuple itself, it's not a reference into slice It's a reference to a reference and by rewriting it to be this what I'm really saying is this Think of it as an ampersand in a pattern is the same as a removal of an ampersand in the value Is one way to think about it and smallest in rest here is Also going to be an index into this slice which starts at unsorted. So we actually need to adjust it slightly to be Unsorted plus smallest in rest And then what we can do here is just for the sake of our own sanity is like assert equal Smallest in rest and smallest in rest too And let's see what that does great so that works So the question now is do we prefer this very explicit way or do we prefer sort of the the functional iterator way? In terms of performance, I think they're going to be likely the same the compilers pretty good about optimizing iterators But it's also pretty good about optimizing For loops and for loops are a duration iteration trust So I think what we're going to do is keep the iterator version because it's a little nicer And I think it's not cheating to rely on min by key because in order to implement sorting because min by key is not a sort of Remember that This one was mostly Some people are saying like I prefer the the second version the second version is very straightforward in terms of just like you can really see What it's doing this one is very much there. It's much more readable It's just this part is a little wonky But if you read it your brain is going to ignore this part right and just be like yeah, it probably does what I expect So I think we're gonna keep it this way And in fact if what we could do here if we really wanted to was map the I and the value to be unsorted plus I And then this can be this and this can be this It's a map here because it's It's an option before we expect out Alright, okay, so we have now selection sort beautiful So bubble sort insertion sort selection sort and standard like resort great So far these have been like fairly straightforward sorting mechanisms And so now we're gonna start to look at the ones that are not quite as stupid So if we switch back here the next one and probably one many people have heard about is quicksort Which is its own kind of cool Does the compiler take advantage of assert statements No, well Actually, no, I think it does Because the assert turns into a Conditional with an exit on the Branch where the conditional doesn't hold so the compiler can assume that the conditional holds in the following code How much it takes advantage of this? I'm not sure All right, so we're gonna do a quick sort And this is we're gonna base this on selection sort I guess all right, so quicksort is Quicksort is More of sort of a beast than the others because it's not it's not quite as straightforward, but it's not that complicated So the way quicksort works is you pick You pick an element basically at random in the list and then you walk the list and you put Everything that is smaller than that element to one side and everything that's larger that element to the other side And then you just continue to do this sort of recursively down until all of them are sorted So for quicksort it might be might be helpful to Write a little helper method and we'll get to that in a second So the idea is that you have like unsorted you have a pivot and you have an unsorted And the pivot can be chosen at randomly it can be it can be the first element It could be the last element. It could be the middle element There's actually been some research on like how do you best choose the pivot? And you can get some pretty good improvements by choosing a good pivot in this case I think we're just gonna choose sort of randomly and so the way we're gonna do this is I'm gonna write to this in an allocating way first and then we can look at Having it do be in place instead, but the allocating version is going to be easier to understand So we're gonna do that first So what we're gonna do is we're gonna have we're actually gonna call quicksort We're gonna have a function called quicksort and we're gonna call that on slice on the whole slice And you'll see why in a second basically this needs to be recursive So a quicksort takes a t that's ordered it takes a slice of muti and here's what it's gonna do Let's do It's gonna select a pivot which is gonna be slice. Let's do zero seems fine. Oh Actually, I realized I think someone pointed this out and I was just too foolish to realize Selection sort. Oh, no, it won't matter. I was maybe I don't be here No, I just realized the List were asked to sort could be empty, but I think all of these will deal correctly with that case Okay, so we're gonna pick a pivot And then we're gonna have like a Let's call them left and right and then we're gonna do is we're gonna walk walk all the elements of slice and If slice of I is less than or equal to the pivot Then left dot push slice. Hi Else Right dot push Slice I right so all the things that are smaller than or equal to the pivot are gonna go in the left And all the things that are greater than the pivot are gonna go in the right And then we're gonna quicksort Left and then we're gonna quicksort right And then we're sort of gonna merge them together Now this is technically all you need for a quicksort, right? There's no difference between T colon ord and where T equals ord or T colon ord So this is all quicksort really does And it you'll notice that it's recursive and the recursive part here is what makes it interesting We're gonna left and right. They're gonna keep shrinking right. They're gonna become smaller and smaller and smaller and At some point they'll only be one element long at which point they're sorted So we can actually do here if Slice thought actually we can even match on it match slice dot Len If it's one Then we return because the slice is already sorted if it's two Then if slice zero is greater than slice one Then swap zero and one and then return Otherwise Do this bit Right, so this is sort of the the base case for the recursion that at some point you end up with us List that's so small that it's trivial to sort and then you just sort of propagate it back up And then we're gonna merge the two lists Now you'll notice that this is actually really inefficient right because we have to allocate this left And we have to allocate right and then we have to do this like merge step and this all seems really annoying and You also notice it doesn't compile and the reason is because we keep trying to move these elements around which isn't really okay Oh, right, we also need a zero So zero or one Right we're trying to move out of the slice, but we're not allowed to move out of slice so we would have to do some like tricks to either move the references or like Have or have left and right only hold indices and this gets really annoying But at least now you have an idea for like at a high level. What does the algorithm do? so what we're gonna do is we're actually going to implement quick sort as a As a an in-place sort so we don't need the a vector for left and a vector for right instead what we can do is Is just have left and right to be the left side and the right side of the slice and we keep growing them from the sides So let's see what that looks like We still need to pick a pivot For now, I'm gonna this is still not going to compile eventually But we'll get to that So here's what we're gonna do Actually, I have an idea, but I'm gonna ignore that for now Here's what we're gonna do for I in zero to slice dot line. In fact, we can have this be from one And the reason we can have this be From one is that the pivot if the pivot always goes to the left We don't need to move it and in fact, this is gonna be one of the ways in which we Avoid the borrower checker errors here is we're going to do Is it Titian at index Let me check whether that's the case No, that's not what I want I want split Split mute No, I want maybe just split at Split at mute great split at mute of One So what this is gonna do is Pivot equals Pivot zero So we're essentially sort of stealing the first element out of the slice And what split at mute lets us do is get a mutable reference to the two subslices where one subslice It's gonna be one element long and hold the pivot and one Subslice is gonna be the rest of the thing that we're actually gonna sort and keep in mind, right? That the pivot is already in the correct end of the slice We're gonna instead of having a left vector and right vector We're gonna have the left side of the slice be things that are less than or equal to the pivot and the right side of the Slice be the things that are greater than the pivot. Well, then the pivot is already on the right side Yeah, we will we will write benchmarks in a second Okay, so now we have a reference to the pivot, but we're still allowed to modify the slice Right and this is important because if we took a an immutable reference to the pivot Then we wouldn't be able to modify the slice if we didn't do the split at mute business Because the borrow checker would complain that you already have a reference into the slice So you can't also modify it because if you also modify it Who's to say that you're not also modifying the pivot value which you have a reference to, right? It's on the right side the left side Oh, maybe my camera is reversed might very well be Okay, ooh, that's not at all what I wanted to do Okay, so now what we're gonna do is we're gonna walk the slice And you're gonna see in second this this for loop is actually gonna change in a bit, but I'll show you why And we're gonna do is Again, if slice of I is less than equal to the pivot Then what we wanted to do is then we just want it to stay in place Already on the correct side So this indicates that we need some like left side and some right side indicator right Then Since we're walking this from left to right If the next element is or is less than or equal to the pivot then it's already on the left side And we don't need to do anything with it otherwise We're gonna have to move element to the right side Right because it's on the wrong side This gets really complicated because now The current element we're looking at needs to go over to the other side But that means we have to do a swap and at that after the swap we have to continue looking at this side, right? Which gets weird So the for loop won't really serve as well here because the for loop would just move on to the next element But we given that we did a swap. We actually need to look at this element again So what we're gonna Split first mute. Oh nice. I did not know there was a split first mute Nice Slice is not empty That is indeed much nicer. Oh It's using slice pattern matching. That's cool. Yeah, we could have done that here, too So what we're gonna do instead instead of having this for iron slice is we're gonna have a while loop And for now, let's just make it be a loop And we're gonna do is we're gonna look at the Think of it as everything Below the left index is on the left side everything above the right index is on the right side right So what we're gonna do is we're just gonna continuously look at the element that's on the left And see whether it needs to go on the other side So if slice left is less than or equal to pivot Then it's already on the right on the correct side and we don't need to move it Otherwise We need to move This element to the right side Well, now that this is no longer a for loop. Let me just tidy up this a little bit Now that this is no longer for loop this becomes a lot easier what we can do is we can do a slice swap of The left with the right and then we do right minus equals one So that makes sense so think of this from a useful way to think about especially these kind of iterative Methods it's just like walk through what actually happens. So imagine that we have a slice And we we've already extracted our pivot so this imagine that we just have a pivot somewhere and we have the remainder of the slice Left is zero and right is the end of the slice Okay, we look at the I guess I don't know which side is left for you Ah, let let me draw instead. Yeah all right, so We have our pivot over here Right, and then we have the remainder of the slice I drew this far too long so we're gonna keep is We're gonna have a left And we're gonna have a right and Think of right as pointing to the End of an element and left is pointing to the left the start of an element and So initially left is empty because it's all the elements before left and right is empty because it's all the elements after right So maybe it's more useful to think of these is like pointing to the middle But it doesn't really matter right is everything after the element not including the element itself is left and left Is everything left of the element not including the element itself? And now the the iterator code right is going to look at the element at left So this element is not in left, but it's at left so it's going to look at this element and If that element is less than or equal to the pivot then it should be in left So all we're gonna do is we're gonna like move left. Let me say color This color is the next iteration color now left is gonna point here Right and everything to the left of where left points is in left So the pink one is now in left and everything to the right of right is in right And there are no elements to the right of right. So right is empty So now it looks at this element And imagine that this element is not less than or equal to the pivot So that value needs to go into right. Well, what we're gonna do is we're gonna swap this element with that element Whatever element is over here, right And then we're gonna shift. We're gonna decrement right. So right is now going to point to here Right and blue is gonna end up over here And at this point some other element, let's say green is gonna be here instead of blue Right and now again everything to the right of right is in right So blue is now in right as it should be and now we continue to look at where left is pointing left It's still pointing here. So now we look at green, which is the one that used to be at the end and If green is less than or equal to the pivot and then we keep going So the question now becomes what's the termination condition here, right? Right to the right of right, right? Yeah, exactly Blue and green kind of looks the same. Yeah, sorry about that Hopefully it was clear from the explanation picking colors on the fly is hard I should really just set up like a good play, but So the question becomes when do we terminate Well, we terminate when Left Becomes the same as right. So at some point there is no more elements to There are no more elements to look at this might not be true, this might be an off by one Again when doing these kind of thinking it's useful to think about the boundary conditions like what happens at the beginning and the end So let's say that we have just A two element list left points here, right points here We look at this element. We shift left over This is in left These are now equal, but we have not looked at this element yet So it might be in left or it might be in right. So this is not the termination condition We need to do one more iteration So them being equal does not mean that you should exit you should exit After they were just equal So This should actually be While left is less than or equal to right the moment left becomes more than right then we're done We don't need to leave a hole for the pivot the pivot is already on the correct side Okay And this is probably complaining that we're trying to compare immutable reference to There we go So slice left gives you a t and you're trying to compare a t to a mutable reference to a t We need to compare by reference Okay, so now we have an in place sort of pivoting notice that this on its own is not sufficient Right, that's good. It's gonna like split the thing into things that are smaller than the pivot and things that are larger than the pivot I Am I am not swapping beyond the length of the slice Right is sliced odd land minus one. So no, I think that's I think it's currently correct This does not actually sort right all it does is it moves all the things less than the pivot to the left and all the things That are greater than the pivot to the right But but those things are not themselves sorted. So we still need to do a quick sort of the left and the right So how is this gonna work? Well We basically need to recurse what's nice here is we don't have to do the merge step Because the merge is sort of gonna happen on its own Because we're essentially sorting the sub slices in place What this means is that there is no merge step to do when all the recursion finishes the the whole array is sorted So the only question becomes how do we know what to? What to pass into quick sort here one tricky part here is this Slice business should really be rest because we need to refer to the slice down here Oops, you should say a rest and this should say rest And now what we're gonna do is we're gonna quick sort Let Left and right is gonna be the splice And we're gonna split it and the question becomes where we're gonna split it. Well, we know that everything that is Less than anything before left is in left, right and everything that's after right is in right And split at mute So split at mute takes a mid. I think they call it. Oops not MIT and it gives you two slices one is a mute of Everything Up to but not including mid and the other is a mutable to everything from mid and onwards and So this is gonna be left right so if we provide left here That's gonna be everything up to but not including left Which is indeed what is in left and everything else isn't right and I misspelled right Could you add an extra if else branch to just move the right counter without a spot swap if it's already on the right side? Yeah, so there's one cool extra step we can do here right which is if the thing on the right is greater than the pivot Then we can just decrement pivot Decrement the right Avoid unnecessary swaps back and forth So the reason why this extra clause is useful is imagine that I guess let's go back to the drawing How am I gonna demonstrate this So let's imagine we have a very small slice here, so it has just like Let's put some numbers in here Let's just say it's one three five So initially left points here right points here That's not I need an extra number in here that's annoying I Think I even need an extra box, so let's make this be four boxes Let's say this is one Four two Five so left points here right points here So initially we're gonna find that one is in the correct location, so we're gonna move this to point here Right, so this is left. This is left. This is right Then we're gonna look at the four and say well four has to go in right, so we're gonna go ahead and swap these two right, and so what we're the position we're now in is One five two four with left pointing here and right pointing here But this was sort of a useless swap because the next thing we're gonna do is swap five back into right So five now got swapped twice even though it was already on the right side and This extra if clause avoids us having to do that It basically tries to it tries to move left and right without having to do swaps until that's no longer possible So that's a little bit better Why are we splitting again if we already sorted rest? We haven't sorted rest. We've just sort of I mean it is We've just sort of Then like a binary partition It's not fully sorted instead of While less than or equal to a loop and if left equals right break No, I don't think you could use an If left equals right break Because you want to break After processing the case where left was equal to right. I mean you could it would just be a bit. It wouldn't really be that nice Oh Yeah, you're right actually we can also do left plus equals one So Left Holds a right and right holds the left swap them So we actually make progress on both sides when we do that swap, but it just saves us one iteration really Great Yeah, so and so once we've done that sort of partitioning then we Look at the two partitions and we recursively call quicksort now. There are variants of quicksort where if you if like the the slice you end up with is Sufficiently small like smaller than three or something then you do like a selection sort or insertion sort or something Because those are efficient at if you have small lists that way you don't have because this the actual This part of quicksort is kind of slow And so if you have a small enough list you can use another sorting algorithm But I think we're just going to leave it the way it currently is even this two clause is unnecessary All right, so let's see whether that does the right thing it does not great Your slice left equals one So left remember is an indexing to rest it is not an index into slice. Oh Actually, that's where this is currently wrong It needs to split at left plus one the pivot Right, so left left is an indexing to rest. It's not an indexing to slice and rest is Slice plus starts at slice plus one because of the pivot. So we do need to add one to that. Oh Quicksort has overflowed its stack. That seems not great Um That suggests that these are not shrinking The problem we're running into here is that okay, so basically what this means is infinite recursion Which I think happens because we're always choosing the pivot to be the same element And it might be that the pivot is the largest element In which case everything will always end up in left. So left will never shrink and therefore we just keep We just keep growing we keep calling quicksort on like the entire slice and it never gets any smaller because right is always empty So What we need to do Well, there are a couple of ways to do this one is to pick the pivot randomly I think Realistically what we need is We need to make sure that the pivot is not the Max value. No, we just need to make sure that the pivot does not go in the same Problem is that we can't just put the pivot in the right either because if we put the pivot in the right You have the same problem if the pivot is chosen as the smallest element I Think the problem we're running into actually is that we don't move the pivot So the pivot the same pivot ends up being used over and over again The way we can get around this I free actually let's see what the page says for quicksort for choosing the pivot I Equal vows can go either way After this partitioning the pivot is in its final position. I see it should not include the pivot Why is that I see so that the real algorithm actually does exclude the pivot? hmm Typically, that's a last element the array That's interesting Yeah, you don't have the pivot in either of them Oh Right, it's I know I know why okay So the pivot is supposed to end up in the the final sorted place in the array But the way we've done it is that we chose the pivot as to be the first element And then we didn't move the pivot at the end. So the pivot is still included in both the sub slices So one way that we can do this is place the pivot at its final location explicitly and the final location for the pivot is to place it After all the things in left and before all the things in right because that's how we partitioned this in the first place Right. So the way we do this is just we do one last swap which swaps Zero which is the pivot with With I want to I want to say left minus one Right, so it it swaps it with the last element in left So the last element in left, right? So everything to the left of left is in left So the last element of left is swapped with the pivot and now we have the pivot placed so that everything that is Less than it less than or equal to it It's to the left of it and everything that's greater than it is to the right of it Which must mean that it's in its final assorted location and now this split at mute that That gives us the left on the right We know that the pivot the left now has the pivot at its end, which is where it should be so we can exclude that from left So we can actually do this by in a couple of different ways The ease the nicest one is probably just mute left Actually, we can also swap it to Let's just split this at left and then have this be everything This might seem a little weird, but remember that Right is where the right-hand side starts. So If we split at left After swapping the pivot, what we're really splitting is we're splitting at the pivot Wait, no, this should still be left minus one. Let me think here. Oh, right Left is already okay. Let me first do left is left plus one And let right is right plus one and right to point into To account for the pivot at zero In theory, we could have the math down here just sort of work out, but this just makes it nicer to think about So Now we swap the pivot into the end of left which is going to be left minus one because everything to the left is in left and And everything to the left of left is in left. So if you want to swap with the last element to left It has to be left minus one and when we split we want to split We want to split where right starts If we split where right starts then everything to the left of that Actually, no, this has to be minus one because we don't want it to include the pivot which is at the end of left and Now rather than having to slice this to not include the last element we slice this to not include the first element To avoid this you can choose the pivot at the middle of the slice then Then you about you avoid some moves. I don't think that's true If you chose the pivot in the middle You might run into a position where you have to shift the pivot around which sounds even more annoying Partition at indexed use instead of split at mute will nicely exclude the pivot Oh, is that what partition at index does? Really Feel like partition at partition at index Yeah, but that's cheating. Also, it's likely. I mean partition at index just does quicksort for you Which is not what we want And then here what we could do is like have a an assert for our own sake That Left out last is Less than right dot first All right, I guess less than or equal great Why is this? Oh, I guess we Don't actually use right below here Okay, so now we have a working quicksort great So you see what I mean by this is a more complicated algorithm than the bubble sort or insertion sort or selection court sort But it is also a lot more efficient And in fact, let's try to evaluate a little bit how different these are right now So let's go ahead and add a benchmark So we're gonna do is we're gonna edit benches Let's do main dot RS Now there are many ways to actually do this evaluation and I'm not gonna Don't think I'm going to Pull in anything like criterion here instead what I'm what I'm interested in is Comparing how many Comparisons these different algorithms do which is a good proxy for for their complexity. It's not quite equal to their complexity But it gets there The other thing I realized is that for insertion sort this needs to be a public field if we want to be able to Actually access it These should be pub use So now if you look at the Man benches main so we want to use Orst star And the way we're gonna go about evaluating this is we're gonna have our own little sorting type So like Sort of or I guess. Yeah sort evaluator It's gonna be generic over T and we're gonna derive Actually, we're not I'm gonna show that in a second. It's gonna hold a T, but it's also gonna have hold a Kumps, which is going to be a Sync atomic atomic U size And Actually, we're gonna want our key or two and I think you probably see where this is going. I'm gonna implement I have to implement all these traits manually that's pretty annoying so we have to implement partial eek for sort of evaluator The reason we have to implement all these manually is because I want all of them to compare only the T and just ignore Kumps, but what comes is gonna do is every time we compare an element We're gonna increment the counter and this gives us a nice way to like run a sort And then at the end see how many comparisons did it do? Okay, so Eek I think is an empty trait partial leak is not Let's pull that up here So we have to implement this thing which is easy enough Oh This is just gonna be Self dot T equals other dot T, but we are gonna do self Kumps fetch add one All right. I also need Ordering We I'm not gonna actually talk about how ordering works here And right hand side is It's gonna do self can I avoid that yeah, okay, great And then we have to do the same thing for Ord This is like a little bit annoying boilerplate and for partial or Will we also count swaps? No, so there isn't really a good way to count swaps Because no method is called when two values are swapped So we don't really have a way to count that we would need the like Implementation to cooperate with us like we could have like a trigger that it's supposed to call but that seems somewhat annoying Oh, no We're also gonna end up with two types called ordering because there's atomic ordering and then there's standard compare ordering And these are of course different types So I'm just gonna do this instead and this Again is gonna be basically the same thing as here, but it's gonna do self dot T partial And for Ord Ord This is just gonna forward to self dot t com Now you might wonder well, why don't we add in this case Oh Other t other dot t The reason we don't do that is because usually compare forwards to partial compare I guess in this case actually we know that we're really testing ord. So ord is the right thing to Deal with here even eek. We don't actually need to count Was this faster than just using criterion? So the reason I don't want to use criterion here is both because it pulls in a bunch more complexity that I don't think is that interesting in This case and because criterion wouldn't measure this. So with criterion what you get is an estimate of runtime And it's not We could measure the runtime here, but I specifically want to look at the complexity which is Related to runtime and not quite the same and this gives us an estimate of of the runtime It's true. We could use relaxed actually because this is all single threaded In fact, we could use RC too, but it doesn't matter because we're not measuring the runtime performance So what I'm imagining here is we're gonna do like for n in Let's say so what sizes do we want to evaluate like arrays of size? 0 1 10 100 1000 And then we're gonna do is we're gonna have like multiple iterations Let's say 10 iterations of each one for each one we're gonna construct a A list of values And of course, this is not actually gonna be a vector where all the values are the same I'm just this is just a placeholder for now and then What we're gonna do is I Guess let up here Counter is gonna be arc new atomic use size new zero and Each of the values in this array is gonna wrap some value But all of them are gonna point to the same single atomic use size So we get a full number of comparisons and then what we're gonna do is we're gonna reset that counter Store zero Fine this I guess this will have to be relaxed Relaxed We're gonna store zero in that counter before we start each iterate each Each actual Sort so let's start with something like bubble sort We're gonna do bubble sort dot sort and we're gonna give it a mute to The values Now because the reason why I Construct values at the top here is we actually want to run the different algorithms on the same array of values in the same order Because otherwise we can't really compare how many comparisons they do so we're gonna do sort of a For each one we're gonna do a let values equals values dot clone Reset the counter and sort values. We're gonna drop that clone of values I guess let took equals this And in fact this could be a much nicer like We could have this be a little closure that takes takes a Sorter and does this for us and then took is gonna be Bench of bubble sort and Then we can also do insertion sort Smart true We can do insertion sort Smart false we can do selection sort and we can do quick sort and this is gonna be a slice Is there a performance difference between using atomic size and sell you size if you only use one thread there is a little bit Like we we totally could use something like sell here in fact sure why not So we'll use sell and RC Instead of these and the reason we can do this is because we don't actually need this to be thread safe And then this is just gonna be self comms dot store self comps Load plus one Now it's get and set. I think Great and this is gonna be RC new sell new zero and This is gonna be get Actually, this is gonna be set Nice This complains because right this This is actually going to be a Oh, right. No, this is gonna be sorter here and this is gonna be a a Ref, but let's have this be a din sorter That seems nice Sorter cannot be made into an object. Oh, it's not object safe That's annoying All right, fine. We can write this as a separate function too. It's just makes me sad so that means it's gonna take a T that implements or it's gonna take an s that implements sorter and it's gonna take It's gonna take the s It's gonna take a slice of T And it's gonna take a reference to a cell of u-size And it's gonna return a u-size and then inside it's gonna do this thing I thought that was a from slice, but maybe I'm wrong. I guess we can just do values dot iter dot cloned dot collect Right, so this also needs to implement clone That's what I wanted to avoid but at least now we can do this Takes three arguments and now for each one we're gonna have to give it Values oops Values and counter Values counter I use counter Great It the reason is not the reason the trait does not object safe is because we have a method that's generic in it We can't box a sorter because the trait is not object safe because it has a generic method on it I'm probably not gonna go through what object safe means because it's somewhat complicated and it the stream is already going a little long I Can't you simply sort sort evaluator instead of T? That is what we're gonna do actually this is gonna be a sort evaluator of tea Which also means that we need to derive clone for this Which is fine And I guess Yep Oh, right. You're right. We can just use to back. I'll use stop to great All right, so Now the question becomes how do we construct the thing that we're actually gonna test And here the easiest thing is probably to pull in the rand crate Is Randy even at 1.0 yet? It might not be 0.7. All right, so We're gonna do this. I think there was like a rand to just generate a whole Thread RNG It implements RNG core Which gives us Where is the RNG trait? There's been so much modifications of this great. There we go RNG. I want to And Think it can just generate a vector but even if it can't Beck with capacity and And then we're gonna do four and Zero to N Values doll push Sort evaluator and the T is gonna be I guess we need a we need an RNG Which means we need to use rand Prelude star So we're just gonna generate a bunch of random values I guess we can rand dot gen And the comparisons is just going to be a clone of counter Which I guess technically they want us to write clone this And here we want it to generate a U size Just say we're gonna sort numbers for now. All right, and now we can actually have this print out a Bunch of values specifically it should print out Bubble then it's gonna print out the N and then it's gonna print out how long how many comparisons that took Similarly here. This is gonna be insertion smart This is gonna be insertion dumb This is gonna be selection and this is gonna be quick. All right cargo bench See what we get. Yeah prop test would let us generate these two um Ooh, why did it is it just bench? Oh, I need to declare it, don't I? Uh Path equals No, I don't think I should have to uh I thought Bench would just do this I mean, that's fine. We can just move benches to be source bin Uh, and then move source bin main to be source bin bench who Quicksword panicked that's good Uh, the length is two, but the index is a very large number And quicksword at 24. So it seems like quicksword is not quite done length 24 um interesting Oh Right can underflow Can left ever Overflow Yeah, that's not ideal Hmm How do we want to deal with this though? I guess left won't really overflow that seems unlikely But right can underflow I think the way I want to deal with this is to have them be um Kind of want to have them be eye sizes rather than trying to deal with the Rather than deal with the underflow Um right can definitely underflow here Imagine that all the elements get added to right Right will end up moving one past zero and then we do one more execution although I guess this is So the while conditions should check that if right ever becomes No, right becomes equal to left And right becomes zero. So we continue executing We decrement right Uh right underflows um Yeah, right underflows Left is still less than less than right. Uh, we execute this line and right is now a big number Um so I I mean there are a couple of ways to deal with this the easiest one Is to just have these be eye sizes Uh, but that's also not great Um It can't be a saturating sub either because we need to detect the case when left and right cross. That's when we're done Uh So I think the Yeah, I guess that's true. I guess one easy way to do this is While right is greater than or equal to zero and this But that I guess okay. This is this is awful, but That would be the way to do it because underflow Which also in debug mode would not work Because in debug mode, uh, this panics No, we we do have to decrement right because otherwise we don't detect. Um, uh, detect the way exit Um So it it it's not like the the decrement is not wrong the decrement does need to be there It's just that the decrement causes it to not realize that we have to exit um So like Right Wrapping sub one like it's awful, but Um, I also realized one one other thing we can do over in the benchmark. Oops bench is that Um, we only need to generate this once for every end Uh, and then each of the iterations we can just do values dot shuffle And that will work just as fine It won't work in debug mode because um in debug mode rust panics on overflow and underflow Yeah, so so the other alternative is some people are Wrong file The other people the thing that some people are pointing out is can't you just check Here like if right is zero then break So that that is another way to do it. I don't know if it's nicer I mean, okay, so it would be right, uh, my minus equals one This would stay right equal minus equals one. It would be, uh, if right is zero Then break and it would be the same here. It's more verbose But you know, uh, we could also use check sub, uh, it still ends up being pretty verbose um Okay, so this now runs although quick check apparently is doing no comparisons which seems not quite right um in fact, it's reporting No comparisons for insertion dumb or quick or bubble That's because they all use partial compare So the the less than operator and the greater than operator in rust, um Both call partial compare not compare Uh So there are two ways to solve this one is to not use those operators and use call compare directly Um, and the other is to move this into partial compare Um, oh my why is this being annoying? This is the file I want So we can move this into partial ord Um Actually, yeah, that's the way to go about it do this and then have this do self dot partial compare other dot Unwrap Is there a reason the loop body needs to run if left equals right? Yes Yeah, we're not done if left equals right. That means there's one element. We haven't looked at yet Uh, is incrementing left correct? Yeah incrementing left is fine because it's not going to overflow in theory left could overflow um, but it's but like overflow is a very large number underflow is not a very large number because Anything any small array is going to trigger it Um, can you add a check that the list is sorted after bench just to be sure sure Uh Values I think there's an is sorted Yeah Um, oh, it's not fine. Um, oh, that's four i in One two values dot len Technically, it's this but that's a nightly thing. So instead we're up to do it ourselves Uh, and we want to assert that values i is greater than or equal to values i minus one Yeah, that seems to be the case I think we should have right equal length Instead of length minus one. No Can't you increment on both ords since the ord implementation will never call ord on the wrapper but only on t um Oh, we could do that too. You're right um Because this ends up redirecting or ends up calling directly to the t We can do this. You're right And in fact, we can do the same thing for uh equality Won't checking if the array is sorted also increment the counter. Yeah, you're right. You're completely right. Uh Let uh I count is this and then we check and then we count oops All right, so this is now at least giving numbers Currently, those are a lot of numbers and they're not very easy to to get at so let's stick them in like values dot dad How bad is it using nightly for binary? I just prefer to not use nightly if I don't need to um But it's not bad to use nightly. It's just like inconvenient usually um Now I guess to to end this let's I was sort of hoping we would get to merge sort and heap sort because they're kind of Interesting at the same time. I think we've covered a bunch of potentially interesting like rust topics So I don't think we need to like the goal of this again is not really sorting algorithms Um, it's more about how we implement them Um, but I do want to plot these just to give you a sense of what this looks like So let's see if what's the easiest way for me to do this. Um Ooh So we could cat that into gnu plot dash p dash e uh plot using uh one I'm using two colon three title one Let's see if that does what I wanted to do No, that did not really help I thought there was a way for you to tell gnu plot to use a given field as the title Yes, yeah, I want to avoid having to write a complicated script, but maybe I can't really get away from it Uh, all right fine. We'll do it in another way. Uh, let's just write a simple r script Because our scripts are nice Uh So we're gonna do t is read dot table Uh values dot that In fact, let's stick some headers on here and say this is um This is algorithm and and comparisons Um We want to include uh gg plot two and then we want gg plot two Let me have the pull up the quick start here somewhere. That's not oh quick guide great That's not a quick guide. You're lying to me That's terrible All right, so This library I very rarely write our code. I just have this like same plotting script that I keep just Um Copping over and over again, but it does produce really nice visualizations This is not an official guide. This is a bad guide. Give me the real guide Here we go Uh, that's what I want So I want uh t and I want the x to be n I want the y to be comparisons and I want the color to be the algorithm And I want it with points Uh, oh fine Uh library gg plot two This uh was not intended to become a uh our tutorial All right shows what great so it pulled in t correctly Uh And now I want This line up here Object n not found Is that not what I called that column? Oh, that's because I'm stupid headers equals true R read table We're not pivoted to the crust of r. Yeah, I know right? You missed tight algorithm. I probably did that too. Um, is it titles? There's definitely a oh header Now I'll go right. I I've written right so many times that uh Beautiful All right, uh and let's have this also with uh geom point plus geom smooth Right This might not be very easy to see because the font size is kind of bad Um, but here what we have is along the x axis is the length of the array The y axis is how many comparisons did you do? Uh, and then this shows the the sort of the relationship between the two, right? um and You can see that this is not this is not good for bubble sort, right? You can see how bad bubble sort is right, uh, you can also see that the uh, Let's see. So insertion dumb and insertion smart are almost the same and this should not be surprising Right. Oh my head is in the way. Sorry. Let me go ahead and do Uh Me I'm still, uh Where is the Well, that's fine. I can just do this instead Ha That should be easier to look at So, uh, you see that the difference between insertion sort smart and dumb is basically zero This shouldn't be terribly surprising. So if you remember back to what the algorithm actually does um, the algorithm was just we do a binary search for finding the insertion point, but we still need to Uh We still need to do all the swaps Actually, maybe that's weird Yeah, the the the number of um The number of comparisons you do is still just very very large The fact that you do less for each insert doesn't end up mattering as much as The overall algorithms just have to do a lot of comparisons double sort, of course terrible selection sort Is worse than insertion sort, which is also what wikipedia told us it would be But the implementation was simpler. It uses less memory, right? So that's nice And quick sort you see is just way way smaller right We can we can totally compare this with The standard library sort too if we want here, so Let's add a benchmark of The standard library sort So standard sorter So i'm gonna go ahead here and do this All right, so let me read that in again and then plot it. Uh, you want to uh, log Why so let's go ahead and look at what that is. I forget I think it's just gg log y Scale y log 10 scale y log 10 Oh, did I do something silly again? I must have but I don't quite know what Oh, right last time I modified values to have a header. So that's algorithm n and uh comparisons All right, so this is log scale. So that makes it a little hard to read um Quick sort is now duplicated Uh header is gone. Yeah headers has been saying Oh, yeah, we can make the x axis logarithmic instead Uh, actually, let's do let's maybe do both. So we're gonna do um Scale x log 10 as well 10 Okay, so this is at this point a very weird plot Uh But keep in mind here that a Straight line here. I guess the smoothing is at this point fairly unhelpful. Um Where do we have right? So the colors have changed. So we have quick sort down here The standard library sort is down here selection sort Bubble sort Insertion dumb Insertion smart is down here. Why? That seems Weird Oh, it's yeah, it's because um Okay, so Yeah, let me turn off the smoothing May I I'll leave it. Um, okay So insertion sort smart the reason it looks like insertion sort smart is so good here is because This is remember only measuring the number of comparisons. It is not measuring the number of swaps Right, so it's not really the complexity of the algorithm. It's just the complexity in terms of number of comparisons and if you remember for insertion sort Um, the insertion sort dumb, right does a comparison for every swap Insertion sort smart does very few comparisons, but it does the same number of swaps So really the figure here is giving us a skewed image of what's going on because we can't also measure the the swaps themselves Now, of course, uh, if we did this with runtime, we'd get a picture that was much noisier But it would include the cost of things like swaps um There are algorithms that do zero compares they uh, well sort of uh, those are counting algorithms So generally only works for things like numbers. Um, and they can get complexities below and log in Yeah, the the smoothing function is not great for this particular use case Nice, um We could we can totally compare the actual runtime to um here. Let me type that up real quick um So we're gonna have this in outside a u-size and an f64 Let time is time instant now Took is time elapsed Uh, and then we want count and took The s6 f64 Uh, and now we can do This this this Uh, I should really have written just like a quick macro for this, but I did not I was a fool Uh took dot zero took dot one Zero one zero one zero one zero one Run this again Uh, I think my computer has enough course that this shouldn't really matter because it's a single court experiment. Anyway algorithm uh and comparisons and uh time So now let's first let's stop this insane scaling um and Keep gm smooth probably, uh, we want The y-axis now to be time Whoa This this smoothing is just like not helping. Let's let's get rid of that smooth Uh I guess technically this is a scatter Uh, I forget maybe they don't have scatter So this is going to be much harder to read Um, so this is now log y scale still so all the lines would have gone like this if they were smoothed Um Yeah, so the runtime is very very uh hard to read But here you'll see that both quicksort and the standard library sort are are generally much faster Insertion smart is faster than the dumb insertion smart sort because it has to do fewer comparisons But it's nowhere near what the standard library and quicksort um managed to do So here we see that the the delta here right is that we're also counting the swaps But we do get a much more noisier signal We see how easy it was to switch up though, which is nice All right, let's do a log x as well and see scale x log 10 nice So gm line will not do what you want here because there are multiple points for every x point Um, so gm line will do something weird Although it'll actually it might not look too bad, but it's not really what you want Yeah, it it ends up drawing a line, um It ends up drawing a line between the data points for the same x-coordinate to But regardless Yeah, so here you see just how much of a difference is right remember that this is log y scale So the difference here is is quite large Uh, be right like insertion the the smart insertion sort is a much better than the dumb insertion sort nice Um, okay, I think that's all I wanted to go through today I think like it could be interesting to implement more sorting algorithms But I suggest that's something you just sort of take on yourself Like it's just fun to implement these algorithms anyway, and it teaches you a bunch of just nice things about How how to write rust code What I'll do is I'll post this as a Git repository I'll publish it on github And feel free to like if you have additional implementations of algorithms Just like post them and and we'll keep a little repository there for people to look at Are there any questions before we sort of end for the day this ended up longer than I had planned, but you know It's because it's fun Can we try an all decreasing slice? I think quick gets pretty bad with that. So Uh, currently we're sort of randomizing them, which means that the the range you see, right? You could think of the bottom is more like best and the top is more like worse than the middle As sort of the average, but we would need way more data points to actually sample the whole thing Um, we could generate sort of known worst and best-case scenarios Uh, but I don't think it adds too much value to this particular stream Right. Remember, this is not really a stream on comparing sorting algorithms because like you would probably use something like tim sort instead you wouldn't actually use any of these um Like you would use like I think um tim sort is really like a merge sort combined with quick sort or merge plus selection sort I forget So I don't think it's worth digging too much into these. Uh, it's more about the rust code. We ended up writing Sweet. All right. Thanks everyone. Thanks for watching. I hope that was interesting. Uh in a week or two I will do a stream on um, this this modification I want to make it to ev map that will be a much more complicated stream in some sense like this is sort of a more of an introduction Sort of introductory stream to rust or or for those who are not that familiar with rust in and of itself Whereas the ev map stream will be more like the older streams. I did with our like pure implementation streams Um, it will probably be longer And it will deal specifically with like concurrent data structures and and abstraction around them I think it will be really interesting. Uh, but it will be a different flavor to this one. So, um, keep an eye out I'll I'll post on twitter whenever I know when I end up doing it and of course as usual I'll post the recording on youtube afterwards All right until next time. See you all Bye