 OK, let us get started. Today the plan is to finish off one last problem with regard to arrays, and then we will move on with 2D matrices. So the last problem that we will discuss with regard to arrays is indirection. And in particular, once you have a data array with an indirection array on it, how we can use it for search. But first let's repeat or recap the last part of forming the indirection array and sorting the original data array in place. So remember we had indices i 0 through 5. And data was offered to us as shown in the previous slide. But once we compute the pause array, so pause 0 will be 2, pause 1 will be 4, and so on. If you indirect through the pause array into the data array, you will see that the data pause result is sorted from A to Z. And then our problem was we wanted to actually reorganize the data array to become in this sorted order without using a second data array of the same size. And the insight was that permutations can be decomposed into cycles. So for example, here 0 goes to 2, 2 goes to 5, 5 goes back to 0, so that's the first cycle, 0 to 5. Now if we pick any other element such as 1, 1 goes to 4, 4 goes to 3, 3 goes back to 1. That's the second cycle. So in other words, this permutation implemented by pause can be expressed by these two cycles, 0, 2, 5, and 1, 4, 3. Yes? Why do these cycles? Why do this? Cycles. So if you think about a permutation, and you take a preimage into an image, you can always keep doing it. And by definition, because there is a finite number of elements, you'll have to come back to some point sooner or later. Now you will not necessarily cycle through all elements before coming back. That will happen only in case of certain permutations. Otherwise, you'll cycle back faster, and you have to pick off another element. And by definition, because permutations are bijective, you can only have cycles. You cannot have any other kind of graph. So once we have cycles, we can generalize our idea of swapping two variables using a temporary to shifting the cycle forward by one turn. Now last time, because I started updating data in place, some people found it a little hard to understand. So I have somewhat newer graphics this time. So first pretend that data was this original array for which I got pause. And I had a second temporary data array available to me. Suppose my job is to place the elements in data in sorted order into temp data, but without doing a sort. Because the sort has already been implicitly implemented by the pause array. So now first pretend that temp data is available. But we still choose to copy from data to temp data according to a special schedule. We already have pause. So we could have just done temp data of i equal to data of pause of i. But instead of doing that, we will step through the indices in a special order using the cycles. So let's see how that goes. The first cycle was 0, 2, 2, 2, 5, back to 0 in that direction. That means that the item at temp data 0 should be the item at data 2. So whereas the arrow goes from 0 to 2 in the forward direction, let's pretend that we are copying the data back across the array in the reverse direction. That's what's going on. As I trace from 0 to 2 forward, the data item a moves back to position 0. The next operation I have to do is to copy the thing at position 5 of data into position 2 of temp data, namely this operation. So the second operation takes m from position 5 and copies it to temp data 2. And the final operation takes data at 0 and copies it to temp data at 5. So although temp data was a separate storage entity and I could trample over it in any old order I pleased, I didn't do that. I used the cycle to copy only three elements correctly to their final destinations. But this makes you realize that during doing this copy, ideally did not need temp data separately. Because all other elements of temp data are unaffected and only this cycle is affected. A goes to this one, m, and then this one. Because in the end, the cycle has to fold back. This order gives me the ability to dispense with the need of temp data and do this in place within data itself. I would only touch the places involved in the cycle. And by using one extra temporary variable, I can save one variable, keep shifting the others, and poke in the same variable at the end. So then you realize that you don't need temp data. So hopefully this clarifies what we are doing with data and temp data. So the important thing is that once you detect the cycles, an arrow going from 0 to 2 means that you have to copy back from 2 to 0. So if I have to do this in place, then data already had z in it. That's why I had to preserve z in a temporary variable, then start the copy. So a overwrites, then that a is overwritten by m. And finally, the saved z goes to the last place. That is how you update permutations in place. So this I trace the first cycle copy in the array and so on. So this I will not repeat. Now the next thing we discuss is what use our indirection arrays. And I started with an application, but let me start this time instead with a simpler sub-problem. So suppose I have a data array, same data array, z, p, a, y, b, m. And they have the pause array, which is 2, 4, 5, 1, 3, 0. The pause array corresponds to those addos. If you follow the addos, you will recover the data array in sorted order. Now suppose the problem is to enumerate all elements in the data array between c and p. More formally, the query says, from data, enumerate all strings which are between c and p inclusive. Now observe that c does not even appear in the array, but p does appear in the array. So the correct answer would be m and p. Because only m and p are greater than equal to c and smaller than equal to p in this array. So suppose I have to implement this. If the original data array were unsorted, then there is no good way to do this except to just go end to end. And that's what we are trying to avoid by sorting and searching. Now the next thing is you actually sort data in place and make it sorted. And then everything becomes easy. Then you can just binary search for these two things and scan the range between them. But as we saw, the pause array is helpful if you want to induce multiple sort orders on the same data. For example, student records, one sort order may be by name, the other may be by marks. And I may want to do different queries on these. So for that, we need to modify our binary search to deal with both a pause array, but also with two distinct situations. One is where the query is found matched with some element in the array. And the other is the query value is not found in the array. It should actually be inserted between two adjacent values in the array. So for example, if I take my two query points, so when I am doing this called a range search, list all elements between c and p. That's a range query. That's not a point query. A point query would be like find q, if there. So the status, if I look up c in this array, would be that c has not been found. But if I wanted to insert c into the array, I should have inserted it at position 2. Because there are two values before it, a and b. So 0 and 1 are taken by a and b. Currently, the third position was taken by, let's see. So m is the third largest, third smallest value. So m should be pushed out. And c should be inserted in place of m. So the return status on searching for c should have been not found and 2. Whereas if you search for p, the return status should be that yes, p was found and was found at index 3 in a sorted array. So after that, it's very easy to deal with the rest of the problem. If I wanted to enumerate all values between c and p, all I have to do is sweep between logical positions 2 and 3. So 2 would be included because c should have been inserted before 2. And p should have been included because p has been found. So this way you can create a piece of logic which says, depending on found or not found, how should I scan the indices? But that's for later. So I'll discuss two minor things. First of all, I just said that the status on searching for c was not found and 2. Whereas the status on searching for p is found and 3. So one question is, how do you return two values from a function? We haven't quite started with functions yet, but as we get into operations on vectors and matrices, we will find it increasingly annoying to not use functions. For example, this method of indirecting an array to create a permutation. I don't want to write a different copy of that piece of code for dealing with the name vector, dealing with the marks vector, dealing with the pan number vector, dealing with the income vector. Who wants to write four copies of the same piece of code, logically? First of all, there's a bigger chance of making mistakes. Second is, if you actually debug one routine and you find a small index error somewhere, you fix that one, you forget to fix all the four copies. So it's generally very bad business to replicate code which is logically the same. You should only write one copy of it. First of all, you should write only one copy of it for dealing with at least integer arrays, and then keep reusing it whenever you have to process indirections for integer arrays. Even better, if you can write a genetic method which can deal with integer arrays, floating point arrays, string arrays, that's even better. So we'll see how to do that later. But we'll slowly get into defining simple functions. And it's not going to be very complicated. So how do you return two values from a function? Let me show you some small fragments of code by hand. And then we'll get to coding a little bit. So you have already seen and used several instances of system functions. For example, libraries like square root, or log, or pow. And what do they look like? To us, it looks like a box which takes in, say, one double and outputs one double. Or inputs one float, outputs another float. Now if we broke up that box, we know how to solve some of them, at least in elementary ways. We know how to raise a number to a power. There's a piece of code which does that. We know how to approximately take the log of a number by summing a series. But once we implement it, we package it up in that box and we forget about how it works inside. It's only contract with the outside world is that it's given, say, a float. It outputs a float. Now there could be functions with more than one argument. For example, we wrote the max function, which takes in, say, two ints and outputs an int. Most simple functions defined in C++ or C libraries look like these. They take primitive values. They output a primitive value. But now our situation is we would like to write a routine called binary search. It takes in some, say, vector of some type. T could be in double, et cetera. And it takes in what to search for. That's another thing of type T. So you can have a vector of ints, and you can look for one int in it. You can have a vector of strings. You can look for a string in it. Earlier, I was saying that the result of binary search should be one thing, which is the position at which T was found. If the second, so this is the query, that's the original vector A, say. If Q was found in A, then you have to output the index at which it was found. If Q was not found in A, earlier we were just returning minus 1, just to mark that it was not found. But as I was saying, to do range searches, it is very helpful if, in case, the query and element does not appear in the area, that I don't return minus 1. Instead, I return the insertion position. That will help me sweep the range. So how can I do both? I basically said I want to now return two things. I want to return a Boolean, which tells me whether the query was found or not. And I also want to return an int, which is an index. In case this Boolean is true, that means that integer corresponds to the place where the entity was found, element. In case this Boolean is false, it means that integer corresponds to the insertion point. So I really want to return two different things from a function in one shot. Now, how do I do that? And C++ already defines something called a pair. So what's a pair? So suppose you are applying to universities. And two of you have packages destined to UT Austin. To save cost, you take a bigger envelope. You stick in your two envelopes into it. And then you seal the outer envelope and pay for postage only once. So that's a pair. Your applications are not interchangeable. Therefore, pairs have a distinguished first element and a second element. So suppose I say pair bool int. This is a type. It now becomes something like an integer. To the system, this is now a synthetic type, which, like an envelope, holds two smaller envelopes. One of those smaller envelopes is a bool. The other smaller envelope is an int. Suppose I declared a variable like this, pair p. Then I can pull out the first envelope by saying p.first. That's a bool. Or I can write p.second. That will be an int. And what you place here is entirely up to you. You can put more pairs. You can put vectors. You can put strings, anything at all. So this is a very handy tool to return more than one thing from a function. In a pair, only two things. But now we can write things like pair t1, pair t2, t3. This is cumbersome. So generally, you want to avoid that. But it turns out that for a vast majority of function returns, which cannot be enough with one, two is enough. So there's a huge number of applications where being able to return two things is enough for you. So that's how you return two things from a function, yes. It is basically very similar to a structure. It's just that you don't have to declare anything. This is already predeclared for you, and you can just type it in. You don't have to declare anything. And the other thing is that your types are all checked for free. You already know that first thing is a bool, second thing is an int. Again, you don't have to give names to the variables inside. It's just called first and second standard, yeah. Any questions? So the second thing we need to ask is really how to implement the new binary search. Remember earlier, in the binary search, we started off with low equal to 0, high equal to n minus 1. And then we kept on bisecting the range. And the condition for containing the loop was while low less than equal to high. So the convention was that the segment that we are still testing was low to high, both inclusive. That's why low was initialized to 0, but high was not initialized to n. It was initialized to n minus 1. So the assumption was both sides are included. And therefore, this corresponds to while the check to be checked interval is non-empty. Now we'll need to modify this a little bit, because we want to catch the low equal to high case separately. Because, see, your last check is where you have a one element adder or sub adder. And you are checking that against the query. If that doesn't match, you know that the value doesn't exist. Fine. So here's the code. So here's the code called binary search. So anything that annoys you in the code you just should ignore. For example, I'm trying to write this for a vector which can store any type t, not just integers or doubles. So I declared this function before saying that this should work for any class t. Other than that, we don't need to understand anything else. The important thing is I'm now returning a pair of a boolean int. I'm not doing void. I'm not saying int, because I want to return two things, whether the query was found in the vector vector, and depending on whether it was found or not, the other integer return value. Now, excuse me. Yeah. So I start off with setting an equal to vector size to reuse it, then low equal to 0 and high equal to an minus 1 as before. And then, sure, while low less than equal to high, that's the outer loop. But inside, I catch the situation where low has become equal to high. So if my array is down to one element, then I do a special case. If the query is equal to low, then what do I have to return? I return this pair, so I have to declare this that I'm returning a pair. And I found the item in vector of low. So I return true low. If query is less than vector of low, what does it mean? See, I'm now down to just one element. Low and high are pointing to the same place. And here is a low. And here is my query queue. In the first case, you see that query is equal to vector of low. So I return found and true and low, same as true and high. In the second case, if I see that query is less than vector of low, then it should go to the left. It has not matched. And I'm sure that queue is greater than this element. Queue is smaller than that element. Therefore, this is the insertion position between this and that. So I return false and low. As it's not been found, the correct insertion point is low. This should shift right, and the query queue should be inserted here. That's what I'm saying. In the third case, if query is greater than this, because I've already bracketed down to one thing, I already know that this item is greater than queue. And therefore, the insertion point is here, before the low plus one element. So I return false and low plus one. Perfectly clear? What's happening? And then otherwise, if I have an array segment surviving which is more than one element large, I go as before, I bisect it up. So I catch that last case so that I can return the present absence status and the index properly. So imagine, also, the special case where I have, say, an array sorted 1, 2, 5, 17, 19, and say queue equal to 21. What will happen? Search will finally lead to low and high pointing here. Remember, the indices are 0, 1, 2, 3, 4. And I'll return what? 4 plus 1. I'll return 5. And that's consistent. I'm basically saying that the query is larger than everything else, so it should go to position 5. So that's how you implement the new binary search routine, which can return to you two things. And once we have that, then doing range search is very easy. Now I can use that output to sweep a range between two animals or two names or two role numbers very easily. So let's look at an example. I have my old array of animal names. And of course, to do binary search, I have to sort it first. I print after sorting. And then I'll query for various things here. Suppose I want to query for 0. So I say pair, Boolean and int, who is the return response from binary search? Binary search on names with query queue. And then I'll just print foo.first and foo.second. Now, so here is alligator, jidaf, jackalope, et cetera. And the answer is true and 1. True is printed as 1. And the correct position where I found jidaf was 1. So now let's try, say, see what that gives us. I did not find it. And Zimbabwe goes after zebra. So that is how your new binary search would work. So I would keep bisecting exactly like before. The logic is the same. The two extra facilities I've added is the ability to call this as a black box on any vector with any query, where the vector stores t's. And the query is something of type t. The second facility is that it returns not just one index, which is minus 1 if the item is not found. If the item is not found, it returns the correct insertion point. And the advantage to this is you write it once, use it from many places. In particular, I also wrote the indirection routine like this in another function. This time it was not returning anything. And it was inside doing selection sort to find the pause array. Now you can use that multiple times. For example, if I have a names array and then I have a marks array, I don't want to rewrite my indirection routine twice. I can invoke indirect on names with pause. And I can also invoke indirect on marks with pause. As long as the two arguments past are different, this one will produce the output in pause. This one will produce the output in m pause. So everything will be fine. So we'll see more details of this when we come to functions. So any questions about the extended binary search? So once you do this, the second problem we need to discuss is what to do if there is an indirection array involved. So far, our binary search routine did not have an indirection array in it. Binary search just took vector and queries. Let's make it so that we write a different version which takes, which is indirect binary search, which takes not only a vec, but it also takes a vector of ints, which is the pause array, and it takes a query. So this time, we cannot assume that vec is sorted. Vac is not sorted. And pause is the indirection array through which, if I access vec, I'll get a sorted sequence. And I want to find new binary search like before using pause. Now, basically, you realize that nothing changes. So you need three inputs, and the output is that same pair. Now you realize that hardly anything changes inside the code. All that happens is whenever I access vec, I have to access it through pause. That's all, unfortunate name. So I have pause low. Now, the question is what do you return? See, if I return pause low, that's not too useful. Why is that? It's much better to return low itself, because the guy outside can always look up pause low. And the reason why low as an output is more useful than pause low is because you could have multiple gang arrays, like names and marks. If I return to you low itself, then you know where to get that detail. So otherwise, I'll return that. That remains all unchanged. This bisection business, I have to do again, pause of mid. So never let vec appear bare. The other thing is, in case query is equal to this, I've already found it, so I return true and mid, that I forgot to mention earlier. So even in the earlier code, if you're doing the bisecting and you suddenly find that vec is equal to mid, then you're done, and you return true and mid. So here nothing else changes, except that I'm always accessing vec through pause. Just check that. So let's test the new routine. This time, I shouldn't need a sort. So I'm not going to sort names. Names is going to remain unsorted, except that I'm going to indirect it. So after names is created, I'm going to create the n pause. I'm going to indirect it. And now, I'm going to prepare, I'm going to call the indirect binary search, where I give n pause as the second argument. So now let's see what this does. So it still says 0, 6. Except this is 6 as in the index. If you try to get pause 6, that will be a bug. You cannot do that. So you have to handle whatever you get back carefully. So let's look for, say, GDAF. So printing names will be an arbitrary order. So I'm printing names, n pause, and finally what happens as a result of the search. So zebra, alligator, GDAF, wolf, lynx, jackalope, and this is the pause added. The answer is yes, I have found GDAF. And it should be in logical position 1. If you access 1 here, you'll not get GDAF. You'll have to first take 1 of this, which is 2, and that gives you GDAF. The nice part is if each of these animals had some calories per day that they need to live, and that was gang to those positions, you can now take that 1 and also read that off. See the reason why we are doing this. So namely, so let me just write this down on a separate piece of paper for you to see. So I have zebra, alligator, GDAF, wolf, lynx. So this was n pause. This was names. And remember, if I look for GDAF, I get back found and 1. Why found and 1? This one is here. This 2 is there. And I found GDAF at position 2. That's why the return response is found and 1. Where is GDAF gives you found and 1? 1 gives 2, 2 gives G. Now, suppose I have calorie intake required for these animals. And I put them some random numbers, 10, 20, 30, the lost are probably 40, 50, 60. Now, so in this unsorted order, that is their positions. So GDAF needs 10 calories. Those are always ganged. So I look up 1 in n pause, and I find 2, and then cal 2 is equal to 10. So if someone tells me what is the calorie requirement of a GDAF, I will first binary search in through n pause and names. I'll get this 1. I'll look into n pause. I'll get 2, and I'll look up those 2 ways. If someone tells me what's the total calories, for some reason, of all animals named between, say, B to Q, then similar to before, what will the query on B get me? B doesn't appear in that. Not found. And B should appear where? 2. It's after A, but it doesn't appear. Similarly, if you look up Q, should be 1. Similarly, Q would be false. And the sorted order is A, G, H, I, J, K, L, W. So Q would be here. So 0, 1, 2, 3, 4. So B is false 1. Q is false 4. So I need to take those three positions, add up their calories. So that's why it's handy to have binary search which can take into account indirection arrays. So I can look up the indirection array to give you in every array what I need. Any questions on this? So the summary is data. We have indirection array pause. We saw how to do general binary search, reporting found and not found and indices properly. We saw how to return two values. We saw how to deal with indirections. And now we can do range searches on various fields. So names and marks. I can query fast for marks obtained by a given student, report names of students you got a range of marks. So now we should really move on to matrices. Any questions so far? So did a huge collection of example problems on 1D arrays. Too many to recap right now. But in some tutorial sections, we'll cover the salient features of each of the segments of examples we tackled. Selection sort, mod sort, searching of various forms, medians, ganged arrays, sparse arrays, operations on sparse arrays, and indirections. Those are the salient points we touched. So now we'll get into matrices into different files. Yes, you can. So when we discuss functions, we will also discuss how to separate out codes in multiple files. I'll see if we can do that. So the next topic is matrices. And we started on this last time. And we said that there's nothing special about declaring and using matrices. It is very familiar, looks essentially the same as 1D arrays, except that there is one more index involved for 2D matrices. So you declared an integer matrix by giving the number of rows and number of columns. Or you can declared a double matrix by again specifying the number of rows and number of columns. And understanding is the total number of cells in the matrix is rows times columns. So those many cells will be allocated contiguously in RAM, exactly as in the case of 1D arrays. The new question is, how are cells numbered in this 2D space? Access is exactly like 1D vectors. You can say imatRxCx equal to some right hand side value, which is any integer. Or you can say L value equal to 5 times imatRxCx minus 3. Once again, Rx and Cx are arbitrary expressions which evaluate two integers. You have to make sure that Rx is between zero inclusive and rows exclusive. And Cx is between zero inclusive and columns exclusive. If you don't do that, again, C and C++ will not check. You can trample over arbitrary bits of memory and either crash or get wrong values silently. So how is the layout done? The layout is done in what's called a row major order. Internally, the C++ compiler plus the runtime system makes no distinction whether you are allocating a 3 by 5 2D matrix or you're allocating a 15 size 1D added. It has no idea it doesn't care. The only thing it gives you is a small syntactic glue or some small piece of machinery, which remembers the number of columns in the matrix, namely 5. And when you access Ij or RxCx, it first translates the two indices into a one dimensional cell number. Remember, the cell number in this array is legally between 0 and 14. There are 15 cells. And so legal cell numbers are between 0 and 14. And cell number of RxCx is defined as Rx into calls plus Cx. So remember, calls is equal to 5 here. So let's see what the cell numbers are. The first number before the comma is the cell number for each cell. So here is the 3 by 5 matrix. Row indices Rx can range legally between 0 and 2. Column indices can legally go between 0 and 4. This is cell number 0. That's cell number 1. This is cell number 2, 3, et cetera, up to 14. If this is an int array, then every cell consumes four contiguous bytes. The first cell occupies bytes numbered 0 through 3 of this array block. Remember, the array block could be allocated anywhere in memory. You have no control on that. So we're writing down the relative offset, starting at the beginning of the array, how many bytes and which bytes are allocated to that cell. So the first cell takes bytes 0 through 3. The second, namely 1th cell, takes byte positions 4 through 7. The second, the 2th cell, takes byte positions 8 through 11, and so on. The last cell here, number 14, takes bytes located at positions 56 through 59. So overall, you have bytes with relative offsets 0 through 59, that is 60 bytes. And that's what you'd expect, because there are 15 elements each taking 4 bytes. So that's an int array. If you had a double array instead, the cell offset, the cell numbers would remain the same. But each cell would now take 8 bytes. And so cell 14 would finish at position, yes, 8 times 15. So let's see. So here is the C++ code, native matrix. Now there's a bit of junk here. So we need to go through this slowly. Maybe I should only isolate the lines that we need to read first. So rows 3, columns 5, don't look at this line, none of those. And then Rx equal to 0, Cx equal to 0, you access an element like imatRxCx. So what extra weird stuff am I doing here? I'm trying to tease out the cell number and the cell address. Much of this deals with pointer arithmetic and pointer conventions that we'll only go into after midterms. So it's not really in the exams. You don't need to really understand. You don't need to see what it prints. I'll explain a little bit of what's going on, but not you don't need to understand it in depth. So here is, say, a double matrix called imat with rows and columns. So 15 cell, double matrix, taking 120 bytes. P base is the address in RAM of the first byte of the first cell, 0th. So when I say imat00, that's the very upper left cell. I'm trying to ask for the first byte address of that. So I say imat, then I take an address of it using the ampersand operator. Ampersand applied between two things is bitwise and ampersand applied to some variable or portion of an array is an address operator. It says, give me the address in RAM where that is located. And what's the type of an address? Well, at a very low level, an address is an integer. It's pointing to a byte. But because we'd like to code in a higher level, C++ understands that because imat was a double matrix, taking an address of an element in that matrix gives you a pointer, not to a byte, but to a double. I can also take that address and convert it into an unsigned integer. Now inside, those numbers will be exactly the same. There's no difference. If the array was actually allocated starting at byte number 2 million, then internally both P base and I base are 2 million. It's just that P base is understood as something that points to a double. I base is understood as something that points to a byte. The values are the same. Now we start the row loop. We start the column loop. And then here is the array element imat rxcx. I again take an address. This is a pointer to the element. That is a double pointer. It points to a double. How do you say something points to a double? It's a double star. That's the syntax in C++. So P base is not a double. P base points to something which is a double. PLM is not a double. PLM points to something which is a double. So once again, as rxcx changes, imat rxcx starts from some memory location. For example, in this integer example, if rx is equal to 0 and cx is equal to 2 and the base address of the array is 2 million, then this cell starts at address 2 million 8. So again, both PLM and ILM are actually 2 million and 8. But one of them is interpreted as a pointer to a double. The other one is interpreted as a pointer to a byte, the basic unit of addressing in RAM. Now I compute two interesting things. One is the difference in the pointer values. PLM minus P base. The other is the difference between the byte offsets. Those two are going to be now different. Why is that? So forget about 2D arrays. Let's just look at even 1D arrays. There's no difference internally. So let's say I have int array. So each element takes four bytes to store. Let's say here is element number index 43. Or to make things a little simpler, let's say index number 40. Now we know that this is stored in four bytes. The first one is byte at relative offset 160 plus 160 with regard to the beginning of the array. So if this address is a, then this is a plus 160, a plus 161, a plus 162, a plus 163. So suppose this is a. So P base is equal to a. But so is i base. Remember, P base and i base are internally the same. They store the address to this guy. Now PLM and ILM are actually both 160, a plus 160. Now if I take PLM minus P base versus I take ILM minus i base, the second one is easier to deal with first. That's just equal to 160. This just says there are 160 bytes between this place and that place. But when I take PLM minus P base, it's 160 bytes. But how many ints is it? It's 40 ints. So what would happen is that when I cast this into an integer like I'm doing there, unsigned integer, this will turn into a 40. Because PLM and P base point to integers. And it knows that each integer is four bytes worth. So the difference between two pointers is the byte address difference divided by the number of bytes taken to store each element. Whereas when I'm taking integer addresses into byte array, then the difference is just the number of bytes between them. How many people are comfortable with this? So the summary is that each element of an array consumes some fixed number of bytes, four, eight, something like that. And if I take two typed pointers like PLM and P base, their difference as an int is the number of elements logically in the array. Whereas if I convert them in these addresses into integers, then the difference is the number of bytes between those two positions. And one is a multiple of the other. It's the number of bytes required to store each element. So now let's run this code and see what is printed. I'm going to print the rx and cx. I'm going to print p offset. So in other words, p offset should become the cell number. And i offset should be the number of bytes that I have to travel from the beginning of the array to get to that element. So I'm turning p base into an unsigned integer, which is just the byte address. When you cast any pointer into an unsigned, you make it an address of the starting byte. Because I said that I don't want to deal with this in too much detail, because it will become confusing. But the summary you need to remember from this code is that I'm going to print rx, row, cx, column. I'm going to also write down the cell number. I implemented cell number by this pointer jugglery. Cell number should be given by p offset. And the byte starting that cell is given by i offset. That's the only thing you need to remember. If you forget the other things or didn't understand the other things, don't worry. I'm basically just printing the table out. Cell number and starting byte offset. And I'm going to comment this out for the moment. So here's the starting address of the array. This is that same thing cast into an integer. But don't worry about that. Element 0, 0 maps to cell number 0, which starts at relative byte 0, namely at that address, that plus 0. The second cell, which is 0, 1, is cell number 1, which starts at byte 8. Because the first cell has taken byte 0 through 7. This is a double matrix, remember. So 0, 8, 16. So this grows 1 by 1 cell number. That grows in jumps of 8. The last cell is the 14 cell. There are 15 cells total. And the last cell starts at 1, 1, 2 and takes 8 bytes. So finishes at 1, 1, 9. So bytes 0 through 1, 1, 9, 120 bytes take up the whole 2D matrix. So if you don't understand the pointer stuff, don't worry about it. The gist is that 2 indices map to a cell number, which maps to a starting byte. And the number of bytes taken by the element. So the next important thing I'm going to show is that you can take illegal indices with impunity and no one cares. So remember, our matrix was 3 by 5. I'm going to now take an illegal index, which is 1, 7. So pictorially, what is that? So I had a 3 by 5 matrix, 0, 1, 2, 0, 1, 2, 3, 4. And suddenly I'm taking 1, 7. So 1. So there's this non-existent cell with column 5, column 6, column 7. So this is what I'm asking for, which doesn't even exist inside the matrix. But what is cell number? If I wanted to blindly compute the cell number of 1, 7, that would be equal to, remember what the formula was? Columns 5 into 1 plus 7 calls into Rx plus Cx. So this would be equal to 12. Now observe, if I wrote down the cell number, 4. This is 8, 9, 10. Sorry, is this something wrong? Yeah, this is 7. 1, 2, 3, 4, 5, 6, 7, 8, sorry, 9, 10, 11, 12, 13, 14. So this is the cell, which is at that cell number. So note that 12 is correctly known as 2, 2. Cell number 12 is actually 2, 2, which is 5 into 2 plus 2, which is also 12. So legally 2, 2 maps to 12. But illegally 1, 7 also maps to 12. And C or C++ will not check that individually your Rx and Cx are within range. In fact, you could think of giving negative Rx or Cx, and the calls times Rx plus Cx may turn out fine. The runtime system doesn't care. In the interest of ultra high efficiency there, not preserve the rows or columns you declared, just like in 1D adays, no one remembers what the size of your array was. It's up to you. Similarly, remembering rows and columns and checking for correct index is up to you. If this calculation turns out to be somewhere in the array or even outside, C and C++ will happily serve you the value or override that cell. So you need to be really careful in calculating indices. When we do more and more sophisticated algorithms with arrays or matrices, we will compute indices in nontrivial ways. And if you make a mistake, you can easily go outside the legal array boundaries and do something nonsensical. So let's see what happens to the code. So the legal pointer, supposedly, goes to imat17. And again, this is printing the illegal pointed offset, which is the cell number. This is printing the illegal byte offset, like before. So suppose I compile it again. When I print it out, that illegal value is cell 12 by 96, just like here it's cell 12 by 96. So you pass an illegal set of pair of indices. You may not even detect it at the lower byte level. The value will get to you or you will correct the value.