 OK, great stuff. I'll say a couple of things. All right. So yeah, OK, go ahead, go ahead. Welcome, everybody, to another Cloud and DevOps Babies programming-based session. This is a second session programming related from Andrew Elston of Liquid from Africa. I hope you enjoyed the last two sessions. The first one was introducing fundamentals of programming. Last week's very engaging session was around Go Lang. And I hope that you all took away some valuable insights from that. This week, we're expanding upon that and taking it to the next level. So with that, I'm going to stop speaking, hand over across to Andrew Elston, and let's get to the good stuff. Yeah, OK. So firstly, thank you very much for having me once again and to all the people on the stream. I see that we have a couple of people watching on YouTube, a couple of people watching on Facebook. We have lots of people on the Zoom session. Just as a note, I take questions as they come, and I can see those questions. So I will be responding to them as they come in. I will say that we do seem to have a slight bit of a lag between the various streams. So bear with me if you ask a question, and it takes a little while for me to respond. I may have to go back a little bit. So first session that we did last week, we talked about some of the basics of Go Lang, and we ended the call with a challenge from Joe. Thanks very much, Joe, to write something that could find prime numbers between a minimum and a maximum range. So I'd like to see in the chats, the various chats, who attempted this and how did you find it? You can go on, if you're on the Zoom call, you're welcome to go voice. Otherwise, on Facebook or YouTube, you can respond in the chat. How did you find it? What was difficult, et cetera, before I start working through the ways that I've done this in two separate ways? Anyone from the group who would like to go first? I think they're all scared to admit they tried. I think we might be able to pick somebody if they don't volunteer. Yes, I will. I'm going to do that. Joe virus. OK, count one, two, three. Who is starting? Joe's starting. Where's your attempt, Joe? I'm still testing, Andrew. OK, I know that we've had a couple of people that did try this. Saki, yes, in response to your question in the chat, this is open to everybody. So let's start with, OK, I cannot pronounce names. So the last name in the chat said they were finding difficulties on the inner loop. So we'll talk about that. Did anybody manage to actually successfully produce code to find prime numbers? I know at least one person did, so speak up. Yeah, I did. You know I did, Andrew. I know you did. So Simon, in your attempt, what did you find the most challenging? What did you find easy? How did you find the exercise? The thing I found most challenging was when I sent you my first attempt and you told me to go in and try again. OK, so go for it. My first attempt was I had a look for a library that would find prime numbers. I came across a library called Math Big Library, which has a routine called probably prime. And I used that. And I sent that to you and you said, no, go away. Do it again. OK, so let me just explain that. Guys, if you are learning something here, using a library function to produce the task where you're writing one line of code is cheating. And you're not going to learn very much from it. So while I applaud the initiative in finding the command in the library, I do not accept the answer. But what I'm going to talk about now is just to take a brief look at this before we move on to more interesting things as to how to solve this problem. Now, there's a GitHub link which I'm going to paste in both the chats. Well, all three of the chats now. That contains all of the code that it has various examples for the challenges as we give them. It also has other stuff that we'll be discussing this week. And I'll keep adding to it as we go. So it'll probably run ahead of the classes as well and you're free to work ahead on those things. But the code that is on my screen at the moment that you guys should be able to see is our first exercise in creating, well, finding prime numbers. And this is what I want to talk through. So the first thing I did here was I created a function. It's called find prime. And it takes a minimum and a maximum argument. So find prime numbers between this and this. Nice and simple. Then I created a Boolean variable. Now, this Boolean variable is used in this particular case to say that because we're going to be using two loops to do this and you'll see it below, we need to be able to signal from the inner loop to the outer loop whether or not this number is a prime number so that we can actually do something with it. After that, yes. What's a Boolean variable? Good question. A Boolean variable is a variable that simply has two values, true or false. That's it. Imagine it like binary where you've got 0 and 1. So Boolean can be set to either true or it can be set to false. That's the only thing you can ever put into it. Make sense? Absolutely so. Great stuff. Then we have another variable. I called it res short for result. That is an empty slice of integers. Basically, it's just an empty slice at the moment that I'm going to add my prime numbers to before I return them. You'll also note that this function has a return that allows me to return a slice of integers from the function to the calling routine. Then we get to actually starting to dig around and find some prime numbers. So what I've got here is I've got an outer loop first. And this outer loop is iterating through the numbers in the range that I've given. So from minimum to maximum. Every time I iterate through that, I start and say by default I'm going to assume that a number is a prime number. So I set the Boolean variable, which I've defined up here, to true. And then I start an inner loop. The inner loop is now to test whether or not the iteration that I am on in the outer loop is actually a prime number. So I start and I say, start at 2. Because if it's 1, well, it can't be 1. It needs to start as 2 if you're going to find a prime number. And I say, start at 2, go to half of the number that is I'm currently on in my outer loop. So half of the iteration. The reason for that, well, if you're finding a prime number and that number is greater than half of the number, you're always going to get a remainder. It's an invalid calculation, basically. So go to half of the outer number. Increment until I get there. And then I say, if I, which is the number in my outer loop, divided by j, whoops, sorry, I cut and pasted the wrong thing there. Hold on a sec. It helps if I'm not screwing with my mouse. So if I divided by the number in my inner loop is equal to 0, that percent will basically return the remainder of a division. So if I divided by j has no remainder, we know that this is not a prime number. Because if it was a prime number, it would only be divisible by itself and 1. So we said that this is not a prime number. And then we break out of this inner loop. There's no point in continuing on the inner loop if we've already know that this is not a prime number. So this break command breaks out of this inner loop. Once we've broken out of this inner loop, which is all defined over there, we say, is is prime? If is prime, sorry, yeah. If is prime, basically, is that true? If I wanted to test if this is false, I could do that, which would test it for false. I could also do that, which tests it for true. But since it's just true or false, I can leave out the equals equals true and just go if is prime. If I also wanted to test false, I could put an explanation mark in front of it and simply negate it, which is basically the opposite of a true over there. But we want to know if it's true. So if it's true, then take the number from the outer loop and put it into my slice, which is defined over here. At that point, repeat this whole outer loop, which will repeat the inner loop, and it'll keep adding it. Finally, when it's finished going from minimum to maximum, return the resulting slice of numbers into the main function. So has anybody got any questions there before I continue, either in the chat or on the Zoom session? No questions? Or have I lost you guys? I'm hearing silence. No, we're here. I'll put it on my side, no question at the moment. OK, good. Then let me continue from there then. Now, what I did next is to take things a little bit further than just this prime routine, because there are a couple of things that I wanted to show you guys in this code. So we're going to take a look at my main function. This is the function where I'm calling to look for prime numbers. Firstly, I want to see how long this whole thing is taking. So I start off and I create a variable that is of type time dot time. This is actually coming from the Golang internal time package, and it is simply a variable that represents a time value. So I'm defining that, and I'll use it in a little bit. Then I define four more variables. Now, these variables here are using an internal Golang package called flag, and they're used for bringing in command line flags. So effectively, what this says is, give me a command line flag called min, set its default value to 2, and describe it with that help section. Same thing for maximum. Those are both integers it's expecting. The next one is a Boolean flag, which can be true or false on the command line that tells it, I want to dump the list of prime numbers located, or I don't. The next one is a time execution. Again, do I want to dump this, or don't I? That creates those variables. Now with the flag package, and I'll talk about importing the flag package in a second, I'll go back to the imports. Once I've defined those, the first thing that I've got to do is actually tell the code to pause my command line arguments. It's a really simple thing to do, flag.pause, bang. That will go and see if any of those things are set, and it'll fill in my variables. Just as a note before I continue on the imports, because remember I referred to importing packages, you'll see over here that I'm importing the flag package. I'm also importing the time package, which is where that time.start time came from. It's also where the whole flags package is coming from. Those are both built-in packages, by the way, so you don't need to download anything extra for them. Then I get to an if statement. Now there's something a little bit strange here, which I'm not going to go into great detail about at this point, because I think I'm going to do an entire session on pointers, but effectively minimum maximum dump primes and time execution of pointers. So to access them, I need to get at their base value what is this thing pointing at, which is why you will notice that I've got a star and asterisks in front of each of those variables. That simply tells me, give me the value of what is in there, rather than the pointer. I hope that makes sense. Let's send up some. She's been baking some Indian food. There's on three platforms. Sorry, I forgot my Facebook. OK, so, Perry, you've got a question. No, I'm talking in the background, and I should be on mute. Sorry. No problem. OK, so I just had a question in the channel, which I'm going to go back to quickly about on why prime is true is equal to is prime alone. So basically, if you've got an if something and it's a Boolean variable, then that equates to if is true. If you say if not is prime, that equates to is false. So by default with Booleans, if this is equivalent to saying if this is true, does that make sense to the person that asked the question? Yeah, yeah, it makes sense. Thank you, Andrew. OK, perfect. So basically, as I was saying here, we dereference these pointers. I will talk about pointers later for now. What you need to know is if you're using the flag package, any variable in there to get their values, you've got to add the star in front of it to dereference the pointer. And I will talk about that later, but let's leave it at that for now. This says if minimum is greater than maximum, that would be invalid. Or if minimum is equal to maximum, because that's also kind of invalid for the purposes of our calculation, then print this line here. So print minimum in range must be smaller than the range maximum. And then print the flag defaults. And I'll show you when I compile this what that does in a second. Got another if statement that says if minimum is less than two, print a message, print my flag defaults as well. In both cases, I simply return that quits the program. Then I test. Remember, I've got two Boolean flags on the command line, dump prime and time execution. I test whether or not that is set. So is prime, if is time execution is the same as saying is the set, then grab my start time. What is the time that this is starting? Just so that I've got it saved. Then I run my fine prime routine. And then I say, if I am timing this, print a line here, which will tell me how long this took. And I'll show you when I run this in a second. Then after that if statement, I tell you how many primes I've got within the range. So I print that out. Then I say, if this dump primes is true, I'm going to print all the primes that I've got. And I loop through the results that I got up here and I print each of the results. Now, let me show you what this actually looks like. It's going to expand this a little bit. If I run a dash help, the flag stuff falls in all of those values from those defaults that I set when I showed you the code earlier to show you that again, it's printing these last sentences that I've got over here. If I run this thing and I actually specify numbers, so I'm going to say I've got a minimum of two and I want to find all prime numbers to 20. It tells me I've got nine prime numbers between two and 20. At the moment, because my default on dump prime is false and default for timing is false, it's not acting on those. If however I tell it, um, sorry. It now tells me that it took zero seconds to find all the prime numbers. It's actually such a small value that it's not even showing it in milliseconds. It found nine and those are the nine. There's the iteration loop from zero through to eight. Those are the prime numbers. Any questions on that before I move on? And then the next question that I wanted to ask you guys do you want me to go through the more complex example because that's more math than code? And in which case I'm going to ask a colleague of mine to step in and start talking. But if you want to, you've got a choice. We can go into more code or we can go into the math behind the faster version. Which would you like? I think Andrew, one of the thing is we take two numbers and find out the execution speed of the goal line. Like in this case, it just executed in the fraction of not even a second. Yeah, if I take a longer version of this, so I'm not going to dump all of them, but I'm going to tell it to generate like, let's see, 6,000 of them took four milliseconds, right? If I take this to 60,000, it's taking 310 milliseconds to find all of the prime numbers between two and 60,000. Now, without going through the code for the other one, what I am going to do is I'm going to show you what the other one looks like in terms of speed because this gives you an idea about how much difference optimization can make. Let me just build this. So again, similar arguments except this time I don't take a minimum, I'm just going from two, going forward. If I run this and I go max of 60,000 and timing is true, on the faster version that took one millisecond. So 300 times faster and in fact, if I take this all the way up to what is that, that's 6 million, let's make it 60 million. If I run that on 60 million, it does it in half a second for 60 million. Wow, this is, this is like. And the thing is there, it's not because of a code change, it's because in the one case, I'm using an algorithm that is designed to find prime numbers. And the code for all of that, you can find in the GitHub repo, it's over here. I have commented it with fairly deep explanations of this. This code is very similar to code that I found online with a slight difference in the fact that the code online was actually making an entire slice of Boolean variables which it was setting. I decided to skip that and use UND-8 because it allowed me to skip a entire initialization loop in the beginning which sped it up. But if you guys want the original, I can show you the difference afterwards. But my point here is when you're writing code, you've got a choice, you can do it the easy way or you can go and get a proper algorithm to do it. And if you can figure out how to write the code to do the correct algorithm, you are going to get performance benefits. Any other questions on that before we move on? Okay, so today, what I want to do is because I always said to you guys, I prefer to teach fundamentals. And so now we're gonna look at some Golang code that talks to fundamentals. And we're going to talk about binary and binary mathematics within the context of Golang. And this gets quite interesting actually because in terms of the binary, there's a lot you can do. And it may take you a while to see the point of some of what I'm about to show you, but we'll get there. So bear with me. And if you've got questions and you don't understand anything, please stop me immediately because if I lose you on this, it's gonna be quite hard to kind of come back. All of this code is very, very heavily commented. It is all on the URL on the GitHub repo as well. So firstly, what I've got in this code is a series of functions that perform a series of operations. Technically speaking, I could just do the operation straight in line in code, but because I wanted to print out all of the results and show you what was happening here, I put this all in individual functions. So firstly, how many of you guys have worked with binary data or binary as in zeros and ones before? Done any binary math, anything to that effect? Anyone from the group? Rajesh from India, have you done that anything in your day job? Yeah, that's true. I work with IP address teaming. Okay, so you've done some stuff with binary and IPs. That'll help you because when we start getting into the more advanced stuff later, you'll understand some of the application of this a little bit better. But what about, so Andrew, what I'm doing is going around the group and literally taking out the names and finding out if they have done something like that. Kamil from Poland, have you done something like that so far? Nope. Okay, that's good. You will learn that from here. Let me see, who else? Sadiq from India. Hey Jo, I've done the IP addressing part. But not the binary part. Definitely guys, IP address to the binaries. So how the updates will get into it, I've done that. Sounds good. Let me ping somebody else. Okay, who is Fubaraj? Fubaraj, I'm sorry if I pronounce your name a bit different. No problem, Jo, just I'm learning. Still I didn't start here to start. Okay, sounds good. All right. I think Simon is saying, let's see on the chart. Yes, he did. Okay. I'm quite fortunate actually that my primary school teacher over 40 years ago, well, I'd be 45 years ago probably, taught me how to count in binary and how to do math in binary. You're not that old, Simon. Well, no, maybe it's 42 years ago, I'm 50. So I was about eight at the time. I don't binary. So there you go Andrew. That's the overall from the group. Okay, so firstly, let me, I did see a question that came in from one of the viewers on YouTube that asked me, what do I mean by binary data? In this particular case, what I'm referring to is the fact that a computer, a normal computer stores everything in a series of zeros and ones. So if you've got a number, be it five, 10, 20, whatever, it is effectively a series of zeros and ones that the computer then creates a full number out of. Computers typically store numbers when they're storing it in memory in bytes. So eight bits at a time. Obviously you've got 16 bit numbers, you've got 32 bit numbers, the larger the number, the more bits you need to make up that number. So the binary number system, we commonly nowadays think in terms of the decimal number system, which is a base 10 number system. Binary number system is a base two number system. We'll at some point start talking about hexadecimal, which is a base 16 number system. But for now, we'll stick with decimal and binary, which is being the base two number system. So now within binary, when you take two numbers, you can add them and subtract them and do all sorts of things in decimal. In binary, there are a number of very specific operations that you can perform on one number versus another number. So what this does is show you what these operations are. So we start with what is called a logical and. And what I'm gonna do quickly is I'm just gonna drop down and I'm going to comment out some of the stuff so that we can run this one at a time and then we'll go through each example and I will compile this and run it. So if we look at a logical and, which I'm gonna pull up the code back up here again, a logical and says I'm going to perform a logical and and I passed 50 and 60 into that function. Now over here, I'm printing those two numbers. Now you'll notice that I print this thing on that percent 2D, by the way, says print the decimal number one, but print it padded as if it were a two digit number. So I could say print 10D and it would, you know, increase the padding between those two brackets over there, but print the number and print the binary. This 016B says pad this thing to 16 zeros because otherwise if I've got a binary number, it's gonna chop off all the kind of zeros at the beginning of it and to align this nicely and show you how this works. I want to print all 16 bits. So if I look at this, my number 50 is made up of that binary number there. Bunch of zeros, one, one, zero, zero, one, zero. My second number is made up of a bunch of zeros, bunch of ones, two zeros. Then I do a logical and together. Now what a logical and says is if you've got a zero and a one or a zero and a zero, it comes out as zero. If you've got a one and a one, it'll come out as one. That's what a logical and does and you can see it over here. At the beginning 50 had a, what we call the lowest order bit. So binary you refer to highest order and lowest order. The highest order bit, which is bit zero is basically the one that will change the number the most. Over here, this is all zero, it comes out as zero. This is a one and a zero, it comes out as a zero, zero and one comes out as a zero. It's only these two bits that survive and you end up with a decimal number of 48 because of that calculation. Everybody following me, you will see the use of this later. Yes, as I can handle. So all of you in the group, this is very important. If any one of you have any questions before we proceed, we can spend a lot of time from Andrew, but this is a really an important concept if you want to understand. So if any one of you have a question, go ahead and ask. And if you want to repeat, Andrew would be very happy to repeat it. Yep, Rajesh, you have a question? Yeah, so I understand the binary part. This is the logical and our functions are there in that binary, but in any general programming language, how binary is going to help improve either the performance of the program or where we normally use this binary concept in programming? Okay, so Rajesh, we will come back to that question later, which we will go through. At this stage, what I'm looking is, is there anyone from the group or from the other media where they did understood the binaries, the logical and... Andrew, I have a question. So I think, for example, if one means 16, because it's four, like two in the power of four is 16, and the other one also is the below line is also one. So that means if four, like 16 plus 16 is 32. So I think it should be two at the power of five. So it should be one, but at the column ahead of that row. Okay, so you've got to remember that in the logical and, you're not really adding things together. You're kind of negating bits, right? So if you look at a binary of 50 over here, right? Well, you've got all zeros, one, one, zero, zero, one, zero, right? So if you look at a binary of 50 over here, right? Well, you've got all zeros, one, one, zero, zero, one, zero, right? Right? On the binary of 60, you've got a bunch of ones over here and you've got zeros. When you end them, it's simply saying zero and zero is zero. One and zero is zero, zero and one is zero, zero and one is zero, one and one is one, one and one is one. That is why you end up with this 48 because effectively these end bits end up being negated because zero and one will effectively end up as zero. So it's not a normal kind of addition. It's a negation operation where only one and one is ever gonna be one. Does that make sense? Yeah, yeah, okay, sure, sure. So this is not the sum. It is like a logical function. Yeah. So then, this is how a logical end works, right? Now we're gonna talk about a logical or. So I'm gonna go down here again and it's gonna change my comments here that we can just move this down. While you're doing your commenting, presumably you use logical and when you're doing subnetting, as an example. You would use a logical and in particular where you're attempting to find whether or what the beginning and end of a subnet is. So if you're doing networking, for example, and you've got 5.5.5.5 slash 27 and you wanna know where the beginning of the subnet is you can do a logical and against 27 bits of mask, it will chop off the remaining bits and what you will be left with is the first IP in the subnet. And I can show you an example of that in a bit, if you want. Yeah. An exclusive or, so I've uncommented that and we're gonna look at an exclusive or. Now an exclusive or, very similar to a logical and, except that with an exclusive or, zero on an exclusive or with zero is gonna be zero, one exclusive or with one is also gonna be zero and the only time you're going to get a set bit is if zero and one, so basically your two bits are different. It will come out as true. So it's testing difference between your bits effectively. If the two bits are true, set a one, if they are, I mean two bits are different, set a one. If they're not different, set a zero. Right, so if we run the same code again showing the exclusive or, what you'll see here is zero and zero over here come out as zero. One and zero are different, so it sets a one. On the next one, zero and one, they're different, set a one, zero and one are different, set a one. The moment we get to one and one are both set, set a zero. So you end up with a completely different result because it's doing that test and putting stuff in there in that way. Does that make sense? Does the logic behind that make sense? Yep. Yes, yes. Okay, so that's called an exclusive or. You'll see that in the code, a end is just a single ampersand like that, the logical and, an exclusive or it uses that thing there, the carrot, to print that out. Sorry, to do the operation. So that's an exclusive or. Now, the next one that we're gonna talk about and this is actually one that I use a lot more often and you'll see why when I get to it. So let me just comment out my exclusive or and we'll do a logical or. So on the logical or, which we're gonna talk about next, the logical or basically says the result will be one if either of the bits will be one, otherwise the result will be zero. So if it's a one and a one, it comes out as a one. If it's a zero and a one, it comes out as a one. If it's a zero and a zero, it'll come out as a zero. Right? So if you look at this again, same thing. You will see that we get another completely different answer, zero and zero is zero, zero and one is one, zero and one is one, one and one is one, et cetera. So that's what a logical or does. That is simply a pipe sign, single pipe sign. Do not confuse that pipe sign with something like this. That basically is a normal or within the context of the code. So that's saying if this is true or this is true, that is not the same as a single pipe, which is a logical or. They do very different things, right? So be careful of that. So that does a logical or. Now, this particular function here, as we go further down, I'll show you exactly why this is so, so useful. But before we get to that, so I just got asked from the Facebook group if there's a limit to calculate this kind of binary format in Go. Basically 64 bit integers, there are functions in certain libraries to handle numbers far bigger than 64 bits long, where you start going into the big libraries which start handling 128 bit numbers. I've very, I don't think I've ever actually used them. It's rare when you see people who need numbers that large and in the IPv6 world, we typically handle the v6 addresses in two parts. So 64 bit and 64 bit. Okay, so I've just seen. Would you like to take the question from Rajesh where he asked about the real time use cases of this where you use it? I'm gonna get to that right off to the next two functions. Because the next two functions will cover something slightly different and then we'll get to where this all comes into play. I will just notice that Danium in the Zoom chat said that when looking at logical operators, truth tables can be useful. And if you Google around, you can find truth tables which will show you kind of what these operators do. What I'll do is I will see if I can dig one out later and oh, Danium actually pasted one. Thank you very much, Danium. I'm going to paste what he pasted into the other chat as well so that you just open that so that everybody has got it. That also contains a couple of operators that we're not gonna go through today but that's a useful diagram. Thank you, Danium, for that one. Now, the next thing that I wanna talk about are when we start talking about, let me just go down here. I'm gonna uncomment this quickly. That out and we're gonna talk about shifting numbers. Left shift, now left shift and right shift do very similar things just in the exact opposite direction. Effectively, if you look at, before I run this, if you look at a number like this, what happens when you left shift it is that it will drop the leftmost bit over here. Add a bit over here and move the whole number, the binary string off to the left, right? That has the effect of doubling the number in question because effectively you're going and saying, move all your kind of least important bits to a higher importance position so your low order becomes a higher order and then you drop off your highest order bit at the end. If you look at this and I run this, you'll see that I've got the number 50 and I'm shifting it twice. So what's happened, let me just actually modify this slightly so that it's just nicely aligned so you can all see it. We'll just tap that nicely. There we go. So you'll see that I've shifted this by two bits and this is the shift operator over here, that's the left shift operator and it's saying shift number one by x number of bits and it shifts it two places. So what you'll see here is this is the original. I've added two zeros over here and everything else has simply moved to the left and as a result, the first shift I doubled 50, the second shift I doubled the result of that and I end up with 200. So effectively I've now taken this and said, go ahead and double it twice over. Does that make sense to everybody? Anyone, any questions from the group? What is the operator which is actually moving by two, can I show you, I just put a two left. It's this here. So it's simply those two, I can't remember what you call that thing. So it is always two, suppose if it is like three means we have to add three left. No, no, no, no. This is an operator, right? These two are an operator. This is saying take number one and shift it left by the number of bits in number two. Oh, okay, okay, you mentioned number two as two. Okay, got it, got it. Yes, so for example, if I passed in four over here, it would say shift number one by four bits but I'm passing in two over here. Does that make sense? Yeah, it makes sense now, yeah. And I can actually show you that. If I take this and I go down to the code over here, instead of passing in two, I'll pass in four and you can see it over there. I've now shifted it by four and I end up with 800 as an answer because I've shifted it four bits. Makes sense? Yeah, definitely. Okay, so now let me comment out that one and go to the right shift example. As I said, the right shift example is effectively the same thing, just the only differences that we're shifting in the other direction. We'll run that and you can see here, what's happened here is that I've dropped off this one and zero and that's an interesting result. I think that result is wrong. I will check that code in a second but effectively I dropped this one in the zero. I shifted everything to the right. I end up with this answer. Apologies for the bug in the code. I really should have checked that. Danium, any idea why I'm getting funny result there? That's a bit weird because that should be equivalent to 1 1 0 0 which should be in binary. Apologies guys, sometimes mistakes happen. Yeah, that's perfectly fine Andrew. It's absolutely fine. Because that should be a decimal of 12. Oh, I know why. It helps if I don't make that typo over there. Sorry, there we go. I just had the two numbers set as each other and that wouldn't exactly work. So just a typo, apologies. So you can see I shifted that there, 50 becomes 12. Effectively what's happened here is on the first shift it would have divided 50, right? And it would have divided it. End up with 25 divided again. You get to 12 because remember I'm working with integers. There are no floating points, et cetera here. So I end up with 12, it's a division. That makes sense. Yeah, totally makes sense. Yes, Andrew. Okay, now we're gonna start talking about where do we use this type of stuff? So if ever you get into network programming for example or various other types of programming, you will see that sometimes they will give you flags in the middle of a protocol that are made up of a bunch of binary bits. So it's not a single number. It's a bunch of this bit means this, this bit means this true or false within a single number. And that means that you're gonna need to be able to test what those bits are, right? Is this bit set, is this bit not set? So what I've got here in this next function and before I run it, I'm just gonna close it. This code looks a lot more complicated than it is. And I'm gonna walk you through it. So please don't freak out and run away and log off at this point because I promise you this is a lot simpler than what it looks like. Firstly, one of the things that I wanted when I wrote this test bit is keep in mind that if I'm testing the bit position of a number, I have to know how long is this number because I'm gonna be testing from bit zero which is generally your highest order bit on the most left-hand side of the number. That's what I qualify as bit zero. The right-hand side being your lowest order bit will be on the most right-hand side, right? That means that when I want to test in an eight-bit number, what is on the most left-hand side, I have to shift the whole number by seven bits but I've gotta know how much am I shifting by, right? To move that bit that is right on the other side all the way through to my lowest order bit on the right-hand side, right? So what I said here is I'm gonna pass in a generic, what is called an interface type. Now, I also did this so that I can talk to you about interface types very briefly and I will go into this in far more detail in future. But for now, what you need to know is an interface type is basically a generic container for a variable. You can have any type of variable in an interface type, it can be an int, it can be a un8, it can be a un16, it can be a string, it can be a pretty much anything, right? So this says take this generic input and take a number of bits that I want to test. So which bit do I want to test? And then I'm gonna print something about this and I'm saying here, I want to print number one as if it were a decimal, which is the percent D again. I also wanna print it in binary, which is the percent 08. Then I'm gonna start doing a calculation. But notice something about this code. On this generic variable, number one, I need to know what type of variable is this because I'm gonna be changing my calculation based on the different type of variable. So I use a switch statement. Switch statement is basically the equivalent of a case statement in another language, right? And I say switch on the type of this variable. And then I have case entries for each different variable type as well as a default entry that says, well, I don't recognize this type, so go do something else. So that is how you pass in generic variables. You use the interface, then you switch the type and then you've got a case statement doing things for the various types. Before we move on to the binary stuff, does everybody get that? Does it make sense? You then. I would like to have one more repeat on that and listen to me. Okay, so one more just to go over this again. This interface type is a generic type of variable. It can be virtually anything. And it's actually a container. An interface type can contain many, many things. So I can call this function and I can pass into it an 8-bit integer, a 16-bit integer, a 32-bit integer, a 64-bit integer. All these are all unsigned integers. Or pretty much anything else, right? When I get this variable in, the first thing I do is switch it, which is the equivalent of a case statement, to say what type of variable is this? That's what this says. Switch on number one and switch on its type. So tell me about the type. And I've got a case statement that says, is this a UND8? So is this an 8-bit integer? If it is, run this code here. If it's a 16-bit, run this code here. If it's a 32-bit, run this code here. If it's a 64-bit, run this code over here. If it's none of those, return an error. Make sense? Got it. Okay. Now, just one note about switch statements within Golang. The switch statement terminates at the first match, basically. So if it matches this, it's not gonna keep matching down. There are ways to do full throughs, et cetera, but I'm not gonna go into those today. We'll get to that at another point. But effectively, the moment it matches one of these, that's the end of it. Though in this particular case, I'm actually returning at the end of it. So that's irrelevant anyway. Right. Sorry to interrupt actually. Yeah. So when you say interface is a container, it can logically accept any type of variable, right? Can you just give one example and explain this to make understanding easier? For example, if I am giving integer variable number one as some kind of integer, right? So will that accept how this case will work? That will go to the default one in that case? Okay. Let me, I'm gonna open up some scratch code here and I'll give you this in another demonstration. So we're gonna create a function. Here's trunk. That's gonna take in a variable. We're gonna call this a and it's an interface type. And now we're gonna look at what that type is, right? So switch a dot type. And I'm gonna go case int. Now, we'll talk about this assertion at the end here in a second, right? I don't technically need to do this with print because print will figure this out. But when I start setting and working with a, I actually need to tell the thing what this is. And it's something that you've got to get right because you can actually crash stuff if you get it wrong. But for the purposes of this demo, a was a urn 16, a string. So we have a function and default. Okay, so now if I call testfunk int a, sorry, int 10, or testfunk urn 16 10, or testfunk, this is a test, which is a string or testfunk. And I've got a error here, let me just see. Okay, this is fine. Remember, numbers by default are integers, so that's fine. Okay, so let's look at what I've got here, right? I've got this function, and this function takes in this generic type. And I'm then testing the type. If it's an integer, it's gonna print it. If it's a urn 16, it's gonna print that. If it's a string, it's gonna print that. If it's none of those, it's gonna tell me it was an unknown type. So look at the code that I'm calling it with. This is gonna pass it to me. So I'm gonna go ahead and print it out. This is gonna pass it an integer. This is gonna pass it a urn 16. This is gonna pass it a string. This is going to pass it a slice of integers, which is not gonna match those types, right? Now, if I run this, you can see it over there. It's detected the type, and it's acted on them differently. Does that make sense? Yes, yes. What I'll do is in the GitHub repo, I'll add some more code that explains this a little bit better. Does anybody have any other questions about this before we go on, or are we all clear on this because it's quite important to understanding that other code? And you're Bala here. Yeah. What happens if we don't use the interface at the function type? So if I don't use interface, how does it work specifically? Okay, so if I take this and I say A as an int over here, right? Which is telling me that A is an integer, right? Let me just comment out the switch statement because you can't switch type on a non-interface, right? And actually, let me just comment out all of this. Okay, so that's gonna print out A as if it were an integer, right? Because that's all my function accepts, right? But now, look down over here. That is now an invalid function call because I can't pass in a UN16. This is an invalid function call. I can't pass in a string. This is an invalid function call. And if I try and run it, you'll see cannot use UN1610, type UN16 as type int in argument to testfunk. That is why you use interfaces where you need different variable types. Okay, okay, now I got it, thanks Andy. And this goes back to what I said last week about Golang being a strictly type-casted language. It's very particular about your types passing into and out of functions and in various other situations. The types have to match. The interface is there to give you a way around that but use it sparingly. You only use that where you really have to. In the case of the code that I was writing here, I wanted to be able to take in different types for the purposes of demonstration. I wouldn't say that it's a good idea in any way, shape, or form to simply use interfaces everywhere because they can cause you problems, right? If you look at this, so I'm gonna take this back to an interface type again and I'm gonna uncomment this. So we're back at the original code here, right? Now, I said if this is an integer, friends, this is an integer and I'm asserting that this is an integer, right? But look at what happens if I get the assertion wrong. It just panicked. That's an instant crash. You've got to be careful with these things because if you get your assertions wrong, you can crash your code. Does that make sense? You can do things very well. Perfect. So let's go back to this code over here and we've said we're testing for the type and we're saying if this is a Uint8 because now we know that it's got eight bits, we're going to print this line over here and we're gonna fill it all in and I'm gonna walk through what I'm printing out here, right? If number one, which is this integer over here, notice that I do not have to assert in a print. I only have to do that if I'm actually operating on the thing. So at the moment, I can just leave out that assertion over there. I say print the number one and print it again to give me the binary of it. Then print how many bits I'm shifting it by? Seven minus bit because what I'm actually saying there is, I want to see what the bit is on the other end of the number. Then I print out the result, right? Is equal to that number shifted by seven minus bit. Now why seven minus bit, right? Because it's an eight bit number and I want to know what the left hand most bit is, right? From the bit that I'm specifying because I count my bits from zero. So zero, one, two, three, four, five. If I want to find out what bit zero is, I want the bit zero, which is on the far left, moved all the way to the far right so that I can do a test on it, right? So let's just break this code down into multiple lines here quickly. Make it a little bit more easy to read. There we go. Now, you'll see I'm double printing each of these variables because I'm printing it in decimal and binary. So that's why you see num one and num one. Then I'm printing what I'm actually shifting to get. Then I'm printing the result of the shift. The final line here is where this gets slightly more interesting. Once I've shifted this number, right? I had a comment there because I've separated it onto another line. Once I've shifted this number all the way to the right hand side, there'll only be one bit left from the original number. That's the bit that I've moved from the left to the right. What am I doing at the next step? I then end it by one, which will erase everything else in that number and it'll do a logical end of one and one. If the result is one, then that bit I was testing was set. If the result is not one, it wasn't. So what does this look like in practice, right? Because I realized that this can be a bit of a mouthful to understand without good examples. So we'll just run that one. Let's go back to that terminal window. You can see here that at the moment I'm shifting this zero places, right? Because at the end of the day, I'm saying I'm testing bit seven. That's an eight bit integer, numbered from zero, zero to seven, right? So I'm shifting it zero places. I'm gonna end up with exactly the same number and this bit here was not set. So I'm gonna return false, right? But if I change this to take exactly the same number and I'm gonna actually print a bunch of these at the same time to make this easier to see. Let's look at this. This first one, the next one, I'm saying I want to test bit six. Again, numbered from zero, bit six is this one over here. Zero, one, two, three, four, five, six, seven, right? So I'm testing whether or not bit six is set. So what does it do? It shifts it by one, which produces this, right? You can see the zero's been dropped, added at the other end. Then it does an end over here. Over here, this and, right? And what is it ending? Remember, one will be zero, zero, zero, zero, zero, zero, one. So it'll be an end across that and you'll end up with just that one bit set. If this is one, guess what? That bit was set. If it's zero, it wasn't. And then return that result. Does that make sense to everybody? Or did I just really confuse everybody? It's clear. Any questions? I just... Actually, it is going top of my head. Actually, I'm not able to follow you. Okay. Maybe Rajesh, what you do is, after this you rewatch this video and then you can... Definitely, yeah. I don't understand and I will contact Andean maybe of this one offline and then we'll see. Yeah, I mean... Post the questions in the group if you come up with that. Definitely, definitely. Yes, I mean I'm in the group on WhatsApp you're welcome to, if it's not making sense, contact me and I'll work it through step by step by step. Just one other note about this. Danium asked me about exception handling when you're using the interface type. Basically, what he's saying there is, is it a good idea to add exception handling? Yes, an exception handling basically says, is this a type I don't recognize? And you'll look at the end of this code, in my switch statement, I have a default entry. And that says, listen, return an error, I do not know what this type of integer is, so I'm not gonna perform any of the above code. So in a switch statement, that default says, literally this is my default if I don't know what it is and it returns an error. Couple of other things to talk about in this function before we move on. This function has two return values, a Boolean value, which is true or false, and an error type. The error type is a built-in type and it effectively allows you to return an error that can be a string, et cetera, that is identifiable. So what you'll see here is, if I get to default, I return false because well, Boolean has to return true or false. And then I return an error, which is created like that. And that error I can then read in my calling code to see what is this error that it returned and I can print that out. In all of the other cases, you'll see that when I return, I'm returning the error as null because if I've got two returns here, I have to actually return two things. So the error value is filled in as a null value at the end of each of these returns. Does that make sense? I'm hoping it does. Yep. Okay. Now, one other thing as well, you'll notice that this return doesn't say, if this is this, then return true or if this is that, then return false. It simply contains a straight algorithm like that. Basically what this says is, do this bit of math over here. If it's equal to one, that's going to return true. So return true. If it's not, and this entire statement is false, it'll return false. So it's evaluating the statement to true or false. If that makes sense, I hope it does. Yep. Now, the only difference between these 16, 32, 64 is as I said, the number of bits that we're shifting by because to get again from that left-hand most bit to the right-hand most bit, I have to do a different size shift. That's why I divided this into different types. That makes sense. I will say that this code is actually some of the more complex code you'll run into in terms of doing this stuff because it's got all of the assertion and the shifting. So take your time, go through this, but don't let it scare you because not all Golang code is this complicated. I promise you now, right? But now that's how you test bits in numbers and there were a lot of practical applications for that. If you've got, for example, if you ever wanted to write a BGP protocol or you wanted to write a network protocol, many of the network protocols contain different bits that are set in groups and you have to be able to test the bit. That's why you need bit testing functions. The other major use, somebody asked me about the practical applications. I'm gonna show you something else here and this is a function that I created called our VerraticOr function. What this does is, remember above we've got the logical or, which I said takes number or it by number and it says that it'll be one if either of the bits were one, otherwise the result is zero, right? So over here, I created this little function called VerraticOr. Now what this function does is take in a number of integers. You'll see that this function has a slightly different definition where I've got that dot, dot, dot int. That basically says, I can take a whole bunch of arguments in here so long as they're integers and it'll just keep going one, two, three, four, five, six, right? What it actually does is it gets those as a slice. So if I return nothing, I'm gonna have an empty slice of integers coming in here. If I put something in there, I'm gonna have position zero, one, two, three, to reference the arguments. That's how a Verratic works in Golang, but they all have to be the same type and this has to be the last input argument within the function. So I cannot, for example, do something like that. That's not valid because it's not gonna know where this Verratic ends and where this starts. So I can do that, that's valid. It knows that B is one integer. So the first argument here is not part of the Verratic. Otherwise, the rest of it has to be Verratic. Does that make sense? Right, yes, Andrew. Okay. So now what do I do here? Once I've got this whole thing of integers, I'm gonna create a slice. So just, that's purely for printing purposes. This is C, R. I got a question earlier, sorry, that I missed about the differences between interfaces and structures. Totally different in many ways and I'm gonna come back to that question when we talk about functions possibly next week, if you don't mind. Just remind me, but effectively, a structure contains a whole bunch of elements that you directly reference and interface doesn't work in the same way. There are similarities and differences. If you wanna have a more detailed discussion before next week about differences between interfaces and structures, let me know or fly and we'll take it up. Otherwise, I'll take that up next week. But going back here on the string slice, I'm creating a slice of strings just so that I can print this all nicely when I, to show you what this does. Then I create another integer over here. We call that res for result and we start iterating through our inputs. Remember I said that veratics are effectively a slice. So I can arrange through this as if it were a slice. Now you'll see that I've got four i comma in. I will reference the offset of the slice. So the first element of the slice, I will be zero. The second element of the slice, I will be one, et cetera, et cetera, et cetera. The in is the actual value at that position in the slice, right? So effectively, if I've got something like this input zero equals 10, when I will be zero, in will be 10, be the value. That makes sense. So all I'm doing is basically iterating through my input arguments for my function here, right? First thing I'm doing, convert them to strings and stick them in my little string slice over here just for printing purposes. The next thing that I'm doing over here, this is the important part is I'm taking the result and I'm doing an all of that input value, right? So effectively res is becoming a combination of the input values. Does that make sense? Yes, you're sending. Right, so let's look at what this actually produces, right? And it'll probably make more sense. And I'll show you where this is useful. Let me just, by the way, you'll notice at the moment I've got an error up here that's saying this won't compile. The reason is I've created a variable coming out of this function that I'm not using because it's commented out, but I'll print it over here. Just to make things happier again. So I fed into this function one, two, four, eight, 16 and 32, right? What did it do? It took all of those numbers and it all them together to produce a number of 63 because it took all of binary values and it laid them one on top of each other, did its calculation and came out with the number 63. That makes sense. Yes, yes. Okay, can anybody think where that would be useful before I continue? Any ideas? IP address, calculating, you know, host bits? We use it for that. Any other ideas? So anything? Think beyond networking, general application. Okay, let me show you, right? So firstly, once we've created this integer, let me talk about isolating elements in that integer before we move to the next step, right? So you can see what that verratic function does right over here, right? The next function in here, I created something called combined contains, right? What this function does, and again, I'm gonna break this down just to make it easier to read. We'll print the first number, we'll print the second number and then we'll print a logical and, and then we'll print a negated truth or false over here and we'll return that truth or false, right? Let me enable that and we'll enable the final piece of code. Watch what happens when I run this. So in my verratic or over here, right? I included one, two, four, eight, 16 and 32, right? That gave me a number of 63. I want to see whether the number that results contains the binary equivalent of the number four, which is this over here. So if I take this number and I end it with this number over here, right? I should end up with this number over here. That tells me that four is part of that subset that is in there. If I do the same thing with 64, however, 64 ended with that is gonna produce zero. It's not a part of it, right? So I could test whether or not specific numbers are actually part of another number by doing this. Does that make sense? Yes. Okay. Why is this useful? So let's write another little function here, right? We're gonna call this our log function, right? And we're going to take a log type and we're gonna make the log type be a urn64 because we want 64 types of logs, right? Then in this log type, we're going to define a list of log types. In this case, I'm gonna talk about map variables in another point, but let's define a bunch of log types, right? Okay, our first log type, right, is going to be the value of one and we're gonna give this a, sorry. We're gonna say that value one is going to have a string equivalent of, sorry, do, do, do, do, do. I got my syntax, no, there we go. Log type one is going to be equal to test one. Log type two is going to be equal to test three. Log type four, now keep in mind, I'm doubling these each time, right? Because I'm effectively creating numbers that when I, all them are going to fit cleanly with into the same number. So we'll go one, two, four, and eight. So I've got four different log types over here, right? Then I'm gonna pass in a message which is gonna be a string as well, right? And I'm gonna say here, format.printf, this is log type %s, message is %s. And I'm gonna tell it over here that I want to say this log types, right? I'm just trying to think of the exact syntax here. Well, the first thing I'll do is say that if log type, I'm just trying to think of the exact syntax of this. Hold on a second, sorry, I'm going blank here for a second. It happens. Sorry, I'm just looking at my other screen here. So over here, we've got this nice little log type function over here. What I will say is that I wanna print test one, test two, test four, test eight, right? Then the first thing I wanna do is to say, is this log type a valid type, right? So my combined log type over here, valid log types is going to be one or two or four or eight. So that's now the combined, right? If my valid log types ended with my log type log type is equal to zero, which means that this is not a valid log type return, do nothing, let me just see. And I've got to make this a UN64 by the way. You'll also notice here that it didn't like that because, sorry, wrong one. These variables have to be the same type. Here we go. So if that's zero, the log type I've passed in here is not valid, it's not part of my valid log types, right? If, however, it is valid, right? Then I can know very clearly that I can simply say that I'm going to print my log type by saying something like, ah, you're right. So what this does here, effectively has now created four different log types. When I call this, if I pass in one over here, it's going to print test one. If I pass in two, it's going to print test two. If I pass in four, it's going to pass in test four, et cetera. Effectively, I managed to take one variable, one simple UN64 over here, and make it reference four entirely different things because I'm referencing the binary structures underneath it to do that. Does that make sense? Or have I completely lost everybody here? Yeah, can you explain one more time? Okay, so log type over here that I'm passing in, right? It's going to be a number, right? And what I've done here is I've taken all the valid types. So these are the valid types. I've got, in this case, four valid types and the actual better way to, if you really wanted to express this in a way that may be slightly easier to understand what I'm doing here is, remember, one is going to be your first bit, right? Your right hand most bit. So that one statement is fine. Two is going to have your second bit set. So if I take one and I shifted one, I've now got two, right? If I take one and I shifted two, that's going to be four. If I shifted three, that's going to be eight. That's why I'm doubling it, right? Because I'm creating a binary string that is testing against those various bits for validity. So each time I put something in here as a reference, I double the number, I take the binary value and that's what I'm looking for, right? That makes sense? Yep. Then what do I'm doing down over here? I'm saying is this type that I've passed in here part of the combined number that I've got over here? If it's not, this isn't valid. Therefore, I'm going to do nothing. I'm just going to bail on this function. However, if it is part of that, then I'm going to print the message that is in that map at that offset point with the message that I passed in. So one number, four completely different types. Does that make sense? Yes. Yep. I think there are going to be a lot of people who are going to be studying this video for many hours. Ha ha ha ha. But guys, what I've taken you through here now, I was going to see if we could move on to other functions and stuff today, but I think at this point, we've been going for about an hour and 40 minutes. So I think I'm going to leave it here for today and next week we will talk about functions and the rest of it. I'm not going to set a coding challenge this week. What I am going to say is that for everybody on here, take the code that I've given you here. It's on the GitHub URL and go through the video, study the code. If you don't understand anything, you can get me on Facebook, you can get me on WhatsApp, come and talk to me until you do understand it because this is going to get really important as we move deeper and deeper and deeper. And this is also fundamental to understanding programming because keep in mind, the computer thinks in zeros and ones. Everything that you pass into the computer is a zero or a one, right? It doesn't think in terms of strings of text, et cetera. It thinks in zeros and ones. And if you don't understand how a computer is dealing with the zeros and ones, the moment that you want to go and go deeper into code or you want to start doing performance optimizations, et cetera, you will not have the grasp to do that. This is absolutely fundamental to how a computer works. So I really need to stress, you guys need to go through this video if you don't understand anything, go through it function by function, come and ask. Post a question on the Facebook group. Post, you know, ask me on WhatsApp, whichever. If you're asking on WhatsApp, please put the question into the group. There are absolutely no stupid questions, only stupid answers of which I'm sure that I can find plenty of, but there are no stupid questions. And I will also take that question, I will post it onto Facebook along with the answer so that the answer, everybody gets it. I know that this has been quite a probably exhausting session and I've given you guys a lot to think about. I hope that you've learned something. I hope I haven't lost you all. But please, as I say, just ask. Oh, I got another question in here that says the line that shows result as performing 124832 equals 63, yes. So that line of code over here is done over here. Basically, because it's doing it in a loop, right? I'm pouring the last number in the loop to res each time. That's why as I feed in 1248, et cetera, each time I do that, I simply all the result with that and I'm effectively adding it to the end by setting those bits. I hope that makes sense. I also got a question about whether or not this is like an enum data type, not exactly. Danium, how would you respond to that question about the enums? So I think in terms of enums, membership is quite explicit and it's more abstract, right? Because it may not actually be part of the underlying binary. It could be essentially anything where this is more closely tied to the low-level binary. Yeah, I would agree with that definition. I was trying to find you. You see, you've got such an eloquent way of wording things. But yeah, I hope I've taught you guys something. As I said, I realized this was quite a heavy session. I hope that the next session where I start talking about all the function types and function methods, et cetera, may get slightly easier, but I can't promise. And after that, we'll get to start talking about pointers and then things probably definitely won't be easier. Well, depends on how you view pointers. But study this, because knowing this stuff, as I said, is really fundamental to how a computer works and it can be helpful. You will see as we move forward more and more practical applications for this as well. So yeah, that is the session for the day. Has anybody got any other questions, comments, thoughts, anything else that I can assist with before we call it for the day? Andrew, thank you for this wonderful session. All of the people who are in the group, in this session as well as on YouTube and Facebook, they are going to take this as a medicine. Morning one time, afternoon one time, evening one time. Three times, they are going to watch this one hour, 40 minutes video, whether you are on the bed or you are anywhere. And they're going to do this for one week completely. And this is for your benefit. You completely watch, re-watch, ask questions, as Andrew said. Andrew is so nice. He does not mind answering questions and he is almost like 24-7. I can see irrespective of which part of the world you are. Thank you, Andrew. I don't sleep much, what could I say? When you say medicine, Joe, is that medicine for the joint virus? Yeah, no, this medicine is for this particular video. They have to watch every day three times for the next three days so that they get it. And if I don't get questions in the group, then I will be changing after them, after three days. I will also say, Perry, I will also say to Joe, Joe, you owe me some prime code, and furthermore, right? Just because you're making everybody else's life difficult, I want to see from you, this is just for you. I want to see a piece of code that makes practical application out of some of what I've taught today before next week from you. Yeah, sure, I take that. There's the challenge. Perry, you get the challenge for next week. Oh, man. My problem is time, not more. Is there anyone in this group so far who have joined, who are not part of our WhatsApp group and who came from the LinkedIn? If you are, and I saw some of you have filled our seat, I will be sending you the group link where Andrew is part of us, where we constantly talk, discuss, everything goes on there. So is there anyone who is not part? Then let me know in this chat channel. I will send the link. Yeah, I think what you can do, Joe, is give me the exact link offline. I will post it onto the Facebook group as well so that people can find it from the Facebook group. Sure, I will do that, Andrew. And then I will also paste that into anybody who's also reading this. We're gonna try for next week as well to further expand the streaming. So I'll be streaming on LinkedIn as well. But I'll also paste the link that Joe's gonna give me on the LinkedIn comment where I talked about the session. But hopefully by next week, we'll be streaming to YouTube, Facebook, and LinkedIn to make this even more interesting and get the wider audience. Absolutely. Any other comments, thoughts, anything else before we close? Anyone want to say anything? Let me see, 27 from here. How many in the YouTube and Facebook, Perry? We have a total of 16 between them. So another 16 there. And please, guys, invite your friends, you know? I will post the two videos that were on Facebook before this to the YouTube channel as well so that all the videos are both on YouTube and Facebook. So, you know, share the channel, let people come and learn from it, and hopefully everybody gets some benefit out of it. Absolutely, absolutely, Andrew. And thank you for helping us, for helping this community across the globe. You won't find this kind of information anywhere, nowhere in the internet. This level of, the lowest level of code with so much of patience with almost like two hours. Always a pleasure, and I will see you guys next week. Thank you guys. Hey, thank you, Andrew. Thank you, Perry. Thanks, guys. Cheers. Thanks, Andrew. Thank you, everyone. Thank you.