initial_address + (element#) * size_of_base_typeFor example, if we had an array of integers (each two bytes long) that started at address 15, the location in memory (the address) of element 10 is
15 + (10) * 2 = 35Languages like Pascal number the elements from 1 so the above has to be changed to use element#-1. This can be extended to work for languages (like Pascal) that allow ranges of array indices. You can convert to 1..N by subtracting the beginning of the range from the index, this gives a value from 0.... Subtracting the beginning of the range from the end gives the last element relative to 0. The expression for this is:
initial_address + (element# - initial_element#) * size_of_base_typeLet's do the same calculation as above, only the array is declared from 5 to 38. Now the location of element 10 is
15 + (10 - 5) * 2 = 25
initial_address + (row# - initial_row#) * size_of_rowThen find the column within that row
row_address + (element# - initial_element#) * size_of_base_typeArrays are characterized by fast random access. Any element on the array can be found as fast as any other. They are however, limited to the size originally allocated and all the elements must be the same size. It is also difficult to insert or delete elements
To find something in an array, we have to look at each element in
the array and see if it is the one we want. Here I will first go over some
different means to repeat things. That is, there are several techniques
for looping in a program. If we have an array of 5 elements called
foo
that looks like this:
|
|
|
|
|
|
|
|
|
|
There are three parts to the for statement. The first part is the initialization. Here it sets i to 0. The second part is the termination check. The loop ends when this expression is false. Here, if i is greater than or equal to 5, the loop ends. Lastly is the increment. Here, we add 1 to i each time to do the loop again. In this loop, i will get the values 0,1,2,3,4 and each time it will check the value against the letter 'c'. This loop is equivalent to the 5 statements above.for(i=0;i<5;i++) {
if(foo[i] == 'c') print "found it"
}
The while loop is best used when the loop conditions aren't a range
of numbers.
i=0
while(i<5) {
if(foo[i] == 'c') print "found it"
i=i+1
}
This is pretty much the same as the other loop. But it can
be written as
i=0
while(i<5 and foo[i] != 'c') i=i+1
if(foo[i] == 'c') print "found it"
The until loop is like the while loop except that the check is doen
at the bottom of the loop so the loop always runs at least once.
i=0
do {
if(foo[i] == 'c') print "found it"
} until (i >= 5)
These are all linear searches. That is, we look at each element
in turn until we find what we are looking for. Also, all these loops look
at all elements, even after the find the one we are looking for. This is
good if there are multiple matches but some times we want to quit when
we find it. So we use the break statement. This causes the loop
to end immediatly.
for(i=0;i<5;i++) {
if(foo[i] == 'c') {
print "found it"
break
}
}
But notice that the data in the array is ordered. That is, it is
in alphabetical order. We can take advantage of that and make the searching
faster. We can do a binary search. This method involves breaking
the problem in half repeatedly until we find the one we are looking for.
First a little about functions. A function or subroutine is
a small program that is usually part of a larger program. It is best if
the function is written to do just one thing. Often a function returns
a value. In the main program, the function is executed when it is called.
Functions also can take arguments.
The definition of a function looks like
foo(x,y) {
if(x>y) return(x)
return(y)
}
foo is the function name. The x and y are called the formal parameters.
The return statement is used to send a value back to the main program.
The function is called from the main program by using its name.
For example
x=foo(2,3)
When it used like this, the program that makes up the function is executed.
If it executed a return statement, then the value in the return statement
is assigned to the variable x. The numbers 2 and 3 are called the
actual parameters of the function.Within the function, that formal
parameter x is set to 2 and the parameter y is set to 3. Then the code
is executed. In this case, 2 is not greater than 3. So the return(y) line
is executed. In this case, that is the same as return(3). Then the 3 is
copied to the variable x.
Now lets look at a small function that calculates the factorial of a
number. The factorial is the multiplication of all the integers starting
with the number and going down to 1. So, the the factorial of 4 is 4 *
3 * 2 * 1 = 24. This is written as 4!. Another way to think of the problem
is to notice that n! = n*(n-1)!. Also that 1! = 1.
One method to calculate this is
fact(n) {
if(n <= 1) return(1) // 1!
x=fact(n-1) // (n-1)!
return(n*x) // n* (n-1)!
}
To understand how this works, you need to know how the system processes
function calls. When the function starts, a block of memory is allocated
called a stack frame. This is where we store the values of the formal
parameters and any other variables that are used in the function. In this
case, we have x. So this block can be written as a list of
2 values. At the start, this is (4,?). The question mark is because x is
undefined at the start.
So lets step through the execution of z=fact(4).
Code Line/ Notes | stack frame |
fact(4) | (4,?) |
if(n <= 1) return(1) False | (4,?) |
x=fact(n-1) fact(3) new block started | (4,?) |
fact(3) | (4,?) (3,?) |
if(n <= 1) return(1) False | (4,?) (3,?) |
x=fact(n-1) fact(2) new block started | (4,?)(3,?)(2,?) |
if(n <= 1) return(1) False | (4,?)(3,?)(2,?) |
x=fact(n-1) fact(1) new block started | (4,?)(3,?)(2,?)(1,?) |
if(n <= 1) return(1) True | (4,?)(3,?)(2,?)(1,?) |
x=1 | (4,?)(3,?)(2,1) |
return(n*x) return(2*1) | (4,?)(3,?)(2,1) |
x=2 | (4,?)(3,2) |
return(n*x) return(3*2) | (4,?)(3,2) |
x=6 | (4,6) |
return(n*x) return(4*6) | (4,6) |
z=24 |
function bsearch(A, start, end, target)
{
mid = (start + end) / 2
// if there is a fraction, chop it. 2.5 becomes 2
if(A[mid] == target) return(mid)
// we found it the first time
if(A[mid] < target) {
z=bsearch(A,start,mid-1,target)
// look in the left half
return(z)
}
else {
z=bsearch(A,mid+1,end,target)
// look in the right half
return(z)
}
}
The key thing that makes this work is that the array is sorted. If we look at a particular element in the array, say element 2 and we are looking for 'b', we first check if foo[2] is equal to 'b'. It isn't. But foo[2] is 'c'. And since 'b' is less than 'c', we know that if 'b' is in the array at all, it must be to the left of 'c'. So we search the left half of the array from 0 to (2-1) or 1. Then we start over again. This time, start = 0 and end = 1. Mid = (1+0)/2 = 0. foo[0] != 'b'. So we call the bsearch routine again on the right half of the array we are looking at. This time, start = 1, end = 1. So mid = (1+1)/2 = 1. Since foo[1] = 'b', we are done. There are several levels of returns and the calling program gets a 1 back.
This technique is called recursion. It means that the program
calls itself.
1 procedure writestring(str)
// print the contents of str
2 i = 0
3 while (str[i] != 0) do
4 print(str[i]); i = i + 1
5 end while
6 end writestring
1 function length(str)
// returns the number of non-null characters in str
2 i = 0
3 while (str[i] != 0) do
4 i = i + 1
5 end while
6 return(i)
7 end length
1 function findchar(c,str)
// returns the index to the character c in the
// string str or -1 if it is not found
2 i = 0
3 while (str[i] != 0) and (str[i] != c) do i = i + 1 endwhile
4 if (str[i] = 0) then
5 return(-1) // didn't find it
6 else
7 return(i) // found it
8 end findchar
Write a routine using the count idea.
1 procedure writestring(str)
// print the contents of str
3 for(i=1;i<= str[0];i++)
4 print(str[i]);
6 end writestring
1 function length(str)
/* returns the number of characters in str */
2 return(str[0])
3 end length
.