Zero-based indexing considered harmful

Apart from goto statements, there aren’t many things that cultivate such heated discussion as the type of array indexing a language has. This article originally had the title “Array indexing: 0, 1 or arbitrary?”, but I thought I might spice it up by changing it to its current rendition. But the article isn’t about hitting on 0-based indexing. Typically languages take one of three options, allowing for 0-based, 1-based, or arbitrarily based array indices. Languages such as Python, C, C++, and Java have 0-based indexing, whereas Julia and Matlab are in the 1-based index club, and a whole slew of languages like Fortran and Ada prefer to allow the programming to make the decision, in cases even allowing negative indices. Is any one of them better than the other?

Some people can’t perceive of there being anything other than 0-based indices. For C it somewhat makes sense, as 0-based indices were are artifact of the fact the language uses pointers to store things, supposedly. More specifically, the expression array[x] in C refers to a memory location x elements away from the origin element, so the index is used as an offset. The first element is therefore array[0] to denote the fact that it is 0 elements away. But it likely wasn’t C that started the trend. C inherited its array semantics from its predecessor B (which called them vectors), which in turn inherited them from BCPL. Notably, BCPL supported 0-based indexing, and was itself derived from CPL (1963), which seemed to use arbitrary-based indexing [1]. Interestingly, in B [1], a vector v[10] allocated a vector v, of 11 consecutive words, v[0]..v[10], with the extra word being a pointer to the first element of the array.

The beast of two indexes

The truth of the matter is that before people started thinking about pointers and memory management, before the existence of Unix and C, many people were using either one-based or arbitrary-based indexing. That is before someone decided that array indexes should start at zero. It is therefore BCPL, created in 1967 by Martin Richards which seems to be the source of pure 0-based indexing. The BCPL was a typeless language close to the low end of machine does not make this all that surprising. Basically BCPL variables represented pointers to one of more consecutive words of memory [3]. So if b is a pointer, then b+1 points to the word after the one b points to, and hence b+0 points to the same value as b.

This is not surprising because as an array of 100 32-bit integers (4 bytes) elements is stored as 100 words at memory addresses: 1000, 1004, 1008,…, 1396. The element with index b, then can be calculated as 1000+(b×4). So it completely makes sense from the perspective of memory inside the machine. Languages that use 1-based indexing or arbitrary indexing just use some mechanism to offset the index. BCPL was compiled on an IBM 7094, one of the first machines with a time-sharing OS. Offset calculations for arrays were often done by the compiler, and hence 0-based indexing likely evolved to reduce the amount of compile-time required. This article by Mike Hoye outlines things a little more, but basically IBM’s use of computer time at MIT to calculate yacht handicapping meant that compiled programs had to be efficient, possibly contributing to the use of 0-based indexing.

Around the mid-1960s three essential approaches to indexing existed.

  • Zero-based indexing
  • One-based indexing – used some CPU instruction, or extra word to manage the offset.
  • Arbitrary indexing – range from X to Y, where X and Y can be positive or negative.

In reality, no one method of indexing is better than the other. There are situation where each method shines, and fails. In many languages a multidimensional array is stored sequentially. For example a 10×10 array, V[10,10], would be stored sequentially in memory from position v[0] to position v[99]. For languages that are row-major, v[0] to v[9] represent the first row, v[10] to v[19] represent the second row, etc. For column-major languages, v[0] to v[9] represent the first column, etc. So to find element (6,4) in the array using 0-based indexing means looking for element V[5,3]. This can be easily calculated using the equation 10r+c (where r=rows, c=columns), so 10×5+3 = v[53]. 0-based indexing works because if the element required is V[0,8], then memory position is v[8]. The same formula does not work for 1-based indexing, where V[1,9] would result in memory position v[19]. The formula would have to change to 10(r-1)+(c-1).

There is a reason why some languages choose 1-based indexes. From a scientific viewpoint, it may just be more logical. 1-based indexing in languages like Matlab and Julia may be because 1-based indexing is the convention for matrix notation. In languages using arbitrary-based indices, it may be the desire to provide the programmer with the ability to choose. Some problems lend themselves to use arbitrary indices. Take for example the Eight Queens Problem. In Niklaus Wirth’s classic take on the problem [4] he uses four arrays, two of which use arbitrary indices, b[2..16], and c[-7..7]. Why? Because as he explains the choice of indices was based on how the indices of b and c would be calculated. For the ⬋ diagonal of b, all the elements of each diagonal have the same sum of their coordinates 2..16. For the ⬊ diagonal of c, the coordinate differences are constant, -7..7. To have a language, Pascal which allowed this made for a natural implementation of the algorithm.

Consider the following piece of code written in C, which calculates the histogram of an image (which is 8-bit, i.e. 256 values from 0-255):

int img[1000][1000], hist[256];
...
for (int x=0; x<1000; x=x+1)
   for (int y=0; y<1000; y=y+1)
      hist[img[x][y]] = hist[img[x][y]] + 1;

Some argue doing this in a language such as Fortran is harder, but it isn’t really. Here’s the Fortran equivalent, which because it allows arbitrary indices, means the image can be 1-based, and the histogram 0-based.

integer, dimension(1000,1000) :: img
integer, dimension(0:255) :: hist
...
do x = 1,nrows
   do y = 1,ncols
      hist(img(x,y)) = hist(img(x,y)) + 1  

Supporters of 0-based indexing often say it’s more natural – in terms of memory addresses yes, in terms of human factors not so much. If an egg carton is empty, and somebody is asked “How many eggs are in the carton”, most likely the answer will be “none”, although some people may answer 0. If there are 12 eggs in a carton, the first egg is not egg zero, because it makes no logical sense – there are not 0-11 eggs in a carton, there are 1-12. Zero implies emptiness, nothing, nil, zilch, nix. Zero represents whether or not there are eggs in the carton. To have it represent the first element of an array then makes no logical sense from a human factors viewpoint. If there is one egg on the carton, it is not the 0th egg, it is the first. If there are no eggs in the carton, only the carton exists (this quickly turns into a discussion on the philosophy of zero).

Yes, there are instances where 0 exists in our lives. 24-hour clocks, and stopwatches start from 00:00:00, yet don’t stay there very long, and if something takes 0 seconds it didn’t really occur. Some people say rulers start at zero, but that argument is silly, because 0 is the edge of the ruler, and not really a measurement. A line of no length is not a line. Some people even put forth the argument of centuries – that the 1800’s is called the 19th century, however that is logical because it is the 19th century, i.e. the years 1-100 were the first century, 101-200 were the second century etc. Zero doesn’t even have a place (and there is no year 0). There are lots of these examples of numeric things in our lives, but none really relate to indexing, they just relate to zero.

There are many people who vehemently opposed to the idea of there being anything but zero-based indexing, and go to great lengths to justify their cause. They are often the same people who believe there is only one true language (whatever that may be). The reality is, who really cares what type of array indexing is used? Either that or go with an index-nonspecific language.

0-based indexingC, C++, Common Lisp, Haskell, Java, Oberon-2, Perl, Python, Ruby, Swift
1-based indexingAWK, Cobol, Fortran (def), Julia (def), Lua, Matlab, Algol-60, Algol-68
arbitrary indexingAda, Pascal, Fortran, Julia, Modula-2
Languages and indexing (def=the default setting)

  1. Barron, D.W., Buxton, J.N., Hartley, D.F., Nixon, E., Strachey, C., “The main features of CPL”, The Computer Journal, 6(2) pp.134-143 (1963)
  2. Kernighan, B.W., “A Tutorial Introduction to the Language B”, Bell Laboratories (1973).
  3. Richards, M., “The BCPL Reference Manual”, MIT, Memorandum-M-352 (1967).
  4. Wirth, N., Algorithms + Data Structures = Programs, Prentice-Hall (1976).

2D strings in Fortran

So elsewhere we have talked about how in Fortran, strings are different to character arrays, so what about 2D strings? Strings that hold multiple words, such as in a dictionary. Here we are effectively looking at an array of strings. In early versions of Fortran, they were created in the following manner:

     CHARACTER*20 DICT(12)
     DATA DICT/'bantha','dewback','ronto','womprat','woodoo','worrt',
   M           'sarlacc','rancor','kraytdragon','tauntaun','wampa','galoomp'/

Here DICT was an array of 20 strings, each of which could be 12 characters in length. This works fine, except the DATA statement for specifying initial values is now somewhat obsolete. Now a 2D string can be created in the following manner in F95:

character(len=20),dimension(12) :: dict

This create a character array named dict in which there are 12 strings each which can contain a maximum of 20 characters. So values can be assigned to dict in the following manner:

dict = [character(20) :: 'bantha','dewback','ronto','womprat', &
                         'woodoo','worrt','sarlacc','rancor', &
                         'kraytdragon','tauntaun','wampa','galoomp']

Note the use of a type specification in the constructor – character(20). If this is omitted, then each string must be of the same size. For example, if the above constructor were to look like:

dict = ['bantha','dewback','ronto','womprat', &
        'woodoo','worrt','sarlacc','rancor', &
        'kraytdragon','tauntaun','wampa','galoomp']

Then when compiled, the following error would appear:

dict = ['bantha','dewback','ronto','womprat', &
                1
Error: Different CHARACTER lengths (6/7) in array constructor at (1)

However the error would not occur if the constructor looked like this (each string is padded with blanks to the size of the maximum string in the group:

dict = ['bantha     ','dewback    ','ronto      ','womprat    ', &
        'woodoo     ','worrt      ','sarlacc    ','rancor     ', &
        'kraytdragon','tauntaun   ','wampa      ','galoomp    ']

Cognitive load and arrays

Arrays always seem challenging for novice programmers because their aren’t too many parallels in real life. The closest we come are eggs in a carton (2×6), or wine bottles in a case (3×4), and these are two dimensional arrays – one dimensional arrays seem more elusive. Pez in a dispenser act more like a stack. So how does one convey the idea of a container which holds only one type of item? The closest one gets to an illustrative example is something like a divided storage boxes, but even then, it illustrates a container with a lid, which arrays don’t have. Arrays are essentially containers without walls, or lids, or physical constraints (leaving memory constraints aside).So while arrays are challenging to conceptualize, they can also be challenging to implement. I have talked previously about the use of 0-based indexing. Whilst it might have seemed natural to Dijkstra, the average person learning to program does not understand why a container has portions that are marked as “division 0”. First let’s consider arrays in C. Here is an integer array with 100 elements in C:

int a[100];

The problem with this implementation is that there is no identification of the concept of an array. This is largely because the specification is only done with the square brackets. There is also no indication that indices start at zero (although in C, this is the norm). C likely has the highest cognitive load because there is no intuitive information for the novice. Consider as an alternative, how other programming languages specify arrays.

Pascal    a : array[1..100] of integer;
Ada       a : array (1..100) of integer;
Fortran   integer, dimension(1:100) :: a
Julia     a = array(Int64, 1, 100)

In all cases, the type of the array is clearly specified. In the case of Pascal, Julia, and Ada, the term array is used to indicate that it is a container.  In all cases the range of array is clearly indicated, and in addition, this often also indicates the indices which can be used. In the case of Fortran, there is no explicit use of the term array, however it does use the adjective dimension to specify the range of values.  Also, not all languages are limited to starting at index 0, like C. Pascal for instance allows for both positive and negative indices to be used, allowing the language to conform to an algorithm, rather than the algorithm conform to the language.

In all the cases above it is easier for the novice programmer to understand that a is an array. Specifying the range of indices for the array is also easier for the novice. Take for example the Ada array. Creating a loop to process this array, giving each element a value is simple:

for i in 1..100 loop
   a(i) := i;
end loop;

This is due in part to the loop being quite easy to understand. Consider the same loop in C:

for (i=0; i<100; i=i+1)
   a[i] = i;

This loop is just not as intuitive for the novice programmer, partially because it uses the indices 0 to 99. What is likely to happen is that the novice programmer might write a loop in one of the following forms:

for (i=0; i=100; i=i+1)
for (i=0; i==100; i=i+1)
for (i=0; i!=100; i=i+1)

The first will result in an infinite loop, the second in no iterations of the loop, and the last one will work, but is very unintuitive. Here there is a lot of scope for the novice to make errors, whereas it is much more difficult using the Ada loop. Consider now arrays in Python, often touted to be a very easy to learn language. Firstly, as Python has no natural arrays (only lists), an array must be created using Numpy. Here is the same specification as for the languages above, an integer array with 100 values.

a = numpy.arange(0, 100, dtype=np.integer)

First there is nothing which intuitively screams “array”. Next, and probably most problematic for the novice programmer is the fact that the range must be specified as 0..100+1, because specifying “0..99” would result in an array with 99 elements.

Of course there are also issues with using arrays, and the structure of the arrays. The use of square brackets, [ ],  may be more intuitive than simple parentheses. So languages like Pascal, Julia and C are likely better than Fortran and Ada here because it is easy to confuse the use of array parentheses, with those used in function calls, or expressions. When one extends the arrays to multiple dimensions, how this is specified also contributes to cognitive load. C does this using separate brackets, e.g. a[i][j], which is less intuitive than Fortrans use of the integrated index, a(i,j). Finally there are issues with what can be done with arrays. In some languages such as Fortran, setting the entire array to a particular value is easy. For example, in Fortran, the following code, creates a 20×20 integer array (theArray), sets the whole array to the value 10, and then sets the central 8×8 region to the value 5:

integer, dimension(20,20) :: theArray
theArray = 10
theArray(7:14,7:14) = 5

This is achieved through whole-array operations, and array slicing, which are convenient features of many programming languages. In C, unless you want to set the whole array to zero, there is no easy way of performing this task, and array slicing is not allowed. This means the code in C might look something like this:

int i, j, theArray[20][20];
for (i=0; i<20; i=i+1)
   for (j=0; j<20; j=j+1)
      theArray[i][j] = 10;
for (i=6; i<=13; i=i+1)
   for (j=6; j<=13; j=j+1)
      theArray[i][j] = 5;

Which one of Fortran or C is easier for the novice programmer to understand and implement?

In conclusion, it seems like languages such as C, and Python increase the cognitive load for novice programmers, partially because the use of 0-based indexing is not intuitive, and partially because of the ease in making errors when it comes to specifying loops to manipulate the array. Other languages provide a better basis for understanding arrays. This and the added features of languages such as Fortran and Julia which allow array slicing mean that the novice programmer can concentrate more on the problem solving aspects of their algorithm, and less on the syntax of the language.

 

 

 

Where did C arrays come from?

One of C’s predecessors was B, designed and implemented by Ken Thompson and Dennis Ritchie. B is essentially a simplified version of C, descended from BCPL. In the “Users’ Reference to B”, B was touted as a language good for “recursive, non-numeric, machine independent applications, such as system and language work” [1]. It was a typeless language, i.e. it had no type, or rather it had one type – the machine word, which is very similar to C’s int type.

Now arrays were problematic, because it is impossible to pack a whole array into a single word. B overcame this by defining an array to a pointer to a block of storage – the pointer could easily be stored in a word. This also makes it trivial to pass the array to a function, as only the pointer to the storage needs to be passed, not the whole array. Arrays were termed vectors in B.

From [1] – “a vector is a primary expression followed by any expression in [ ] brackets.” and “The primary expression can be thought of as a pointer to the base of the vector, while the bracketed expression can be thought of as the offset in the vector”.  The invertible notation used in C also had its beginnings in B: “Since E1[E2] is identical to *(E1+E2), and addition is commutative, the base of the vector and the offset in the vector can swap positions”.

There were some differences. In B, a vector was declared in the following way:

auto vec[10];

However this actually allocates 11 consecutive words, from vec[0] to vec[10]. A tutorial on B [2] makes the point that B lacks the Fortran-like ambiguity between subscripts and function calls, which both use ( ). B also introduced the unary operators * and & for manipulation of addresses. ‘&’ is the address operator so &x is the address of x, assuming it has one. ‘*’ is the indirection operator; *x means “use the content of x as an address.”

Strings were just a vector of characters, terminated in this case by what Kernighan calls a “mysterious character” known as *e (ascii EOT).

Interestingly enough, one document on B [3] makes it quite clear that “B does NOT check whether an index has gone past the end of a vector.”, going on to say “In B, it is the programmer’s responsibility to make sure that indices don’t run off the end of a vector. ” Seem familiar? (B also allowed negative indices in certain circumstances it seems, a practice I’m glad C dispensed with).

[1] Thompson, K., “Users’ Reference to B”, Bell Telephone Laboratories, (1972)
[2] Kernighan, B.W., “A Tutorial Introduction to the Language B” (1996)
[3] The B Tutorial Guide

 

Coding Ada: pointers (the extreme basics)

I’m including a brief section on pointers for those interested. firstly, pointers are not such pointers in Ada, they are known as access types.

  • All pointers are initialized to null.
  • Implicit dereferencing is used.
  • There are four kinds of access types in Ada: pool access types – general access types – anonymous access types – access to subprogram types.

Consider the following pool access type. A pool access type handles accesses to objects which were created on some specific heap (or storage pool as it is called in Ada). In the example below, a record named position contains two integers (for the x and y coordinates of the position). This is then used to create a “pointer” posPtr using the access keyword. Objects in a storage pool are created with the keyword new. The object can be accessed as the complete record using .all, or as components using the standard dot notation. In the example below, for example p1.all.x, or through implicit dereferencing (forget all) as p1.x.

with ada.Text_IO; use ada.Text_IO;
with ada.Integer_Text_IO; use ada.Integer_Text_IO;
with ada.Unchecked_Deallocation;

procedure pointerAda is
   type position is record
      x, y: integer := 0;
   end record;
   type posPtr is access position;
 
   procedure dispose is new Ada.Unchecked_Deallocation
             (Object => position, Name => posPtr);
 
   s1: position;
   p1: posPtr;
 
begin
   s1 := (12, 24);
   p1 := new position;
   p1.all := (2, 4);
 
   put(s1.x); put(" "); put(s1.y);
   new_line;
   put(p1.x); put(" "); put(p1.y);
 
   dispose(p1);
 
end pointerAda;

Note that Ada does not necessarily do garbage collection. Therefore in order to free the memory assigned to  an object on the heap, you need the generic unit Ada.Unchecked_Deallocation.  Using this we can create a subroutine dispose() which will destroy an object of type position.

 

 

Coding Ada: strings (ii) – unbounded

If variable length strings are needed, then an unbounded string should be used, specifying the appropriate packages:

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;

Unbounded strings are not arrays of characters, and are stored on the heap (and automatically deallocated). For example, word is declared as an unbounded string, and can have any size:

word : unbounded_string;

The unbound string can be input using get_line().

get_line(word);

unbounded string ELEMENTS

Due to the fact that unbounded strings are not really arrays, it is more challenging to access them. Consider the following piece of code, which just prints out each of the elements of string word:

 for i in 1..length(word) loop
    put(word(i));
 end loop;

Compiling this will likely result in an error of the form: “array type required in indexed component“. This is because an unbounded string can’t be indexed in the same way a fixed string can. In order to make this work, it requires the use of the “function” element(), where the parameters are the name of the unbounded string (word), and the element to access (i):

 for i in 1..length(word) loop
    put(element(word,i) & " ");
 end loop;

The same with modifying an element. For this you need to use the function replace_element(), for example:

replace_element(word,1,'v');

This will replace the first element of the string word with the character “v“.

Functions

As seen in the above example, the length of an unbounded string can be determined using the function length().

Conversions

It is not possible to just assign strings to unbound strings, they must be converted using functions:

  • to_unbouded_string – convert from a fixed string to an unbounded string
  • to_string – convert from an unbounded string to a fixed string.

 

Coding Ada: strings (i) – fixed

Strings are tricky in Ada. There I said it.

Strings in Ada have their own type.  Let’s say we want to declare a string with with 10 characters in it (this is an explicit, uninitialized string):

str : string(1..10);

Now Ada strings are fixed length, which means its length will never change. It also means a string variable can only be assigned string that match its length. If you then want to use get(str) to read the string from standard input, it will expect to read in 10 characters, and it will wait until all 10 have been read. So if you were to input:

sith
sith
sith

The string stored would be sithsithsi. You can’t store strings less than 10 characters in str. Strings can be declared in a number of ways:

s1 : string := "Lord Vader";
s2 : string(1..10);
s3 : string(1..10) := "Lord Vader";

Here are some characteristics of arrays:

  • Array assignment requires arrays of same length on both sides.
  • Strings of different lengths can be compared.
  • Slices of strings, e.g. s1(2..4) can be compared.
  • A string of length 1 is not the same as a character.
  • For I/O, get_line() and put_line() are defined for strings.

To read in strings, use get_line(). It’s basic syntax is get_line(S,L), where the output parameters are: S, the input string, and L, the number of characters that were input.

Note that strings do lack flexibility. If you were to read in a file of words (e.g. dictionary), where the length of the words varies, it is difficult to do this using a normal array of strings, because of the need to fill up the words. To do this we need to use unbounded strings. In essence, Ada has three types of strings:

  • fixed – the strings as discussed above
  • bounded – the current size and the string are combined in one type. The size of a string can change, but it must always be less than some predefined maximum.
  • unbounded – the size can change as required.

 

Coding Ada: declare blocks

Declare blocks allow for variables to be declared anywhere in a program.

For example, consider the following code to create a dynamically sized array:

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure declareBlock is
   size : integer;
begin
   get(size);
   declare
      nb : array (1..size) of integer;
   begin
      for i in nb'range loop
         get(nb(i));
      end loop;
      for i in nb'range loop
         put(nb(i));
      end loop;
   end;
end declareBlock;

The declare block uses the size obtained from the user to create an integer array. Note that declarations are local to the block (which means nb is destroyed at the end of the declare block.

declare
   -- declarations local to the declare block
begin
   -- statements using the local declarations
end; -- declarations go out of scope here

It is easy to use declare blocks to create unconstrained strings:

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure string2 is
   n : integer;
begin
   get(n);
   declare
      str : string(1..n);
   begin
      get(str);
      put(str);
   end;
end string2;

 

Coding Ada: arrays (ii)

Now back to bubblesort(). As you can see from the code below, a 10,000 element integer array has been created as a type – numberArray. An instance of this type is then created in the array unsortedA. Creating a type allows us to easily specify the array as a parameter in a subroutine.  Apart from this there is nothing intrinsically special about this algorithm. I will leave it to the reader to read through it and absorb the power of Ada.

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure bubblesort is

   type numberArray is array(1..10000) of integer;
   unsortedA : numberArray;
   n : integer;

procedure bubblesort_i(nE : in integer; sArray : in out numberArray) is
   swapped : boolean;
   temp : integer;
begin
   for j in reverse 1..n-1 loop
      swapped := false;
      for i in 1..j loop
         if sArray(i) > sArray(i+1) then
            temp := sArray(i);
            sArray(i) := sArray(i+1);
            sArray(i+1) := temp;
            swapped := true;
         end if;
      end loop;
      if not swapped then
         exit;
      end if;
   end loop;
end bubblesort_i;

begin
   put_line("Number of numbers? ");
   get(n);
   for i in 1..n loop
      get(unsortedA(i));
   end loop;
   bubblesort_i(n,unsortedA);
   for i in 1..n loop
      put(unsortedA(i));
   end loop;
end bubblesort;

 

 

Coding Ada: arrays (i)

Now we turn our attention to the classic data structure found in most languages – the array. In this example, we are going to perform a simple sorting of a series of numbers using a bubblesort() (yeah I know super inefficient, but easy to code).

Arrays in Ada are different to how they are structured in languages like C. Here is a simple array in Ada:

a : array (1..1000) of integer;

Unlike many languages, Ada actually uses the keyword array to denote the declaration of an array. It then specifies the index range for the array in parentheses, in this case 1000 elements having indexes 1 to 1000. Following this is the indication of the datatype. Note two very important things with Ada arrays here:

  1. Ada arrays uses parentheses to denote elements, i.e. ( and ). Not square brackets like in *other* languages. 🙂
  2. Ada does not restrict the starting index of arrays to 0. It uses any arbitrary starting index specified by the user.

Initializing Arrays

So initializing this array to zero can be done using a for loop:

for i in 1..1000 loop
   a(i) := 0;
end loop;

or using an array aggregate:

a := (1..1000 => 0);

multidimensional Arrays

Arrays with more than one dimension are also easy to create:

b : array (1..4, 1..5) of float;

This means b is a two-dimensional array (as opposed to an array of an array). Elements are accessed using b(i,j). Multidimensional arrays can also be given an initial value:

aa: array (1..4, 1..5) of float := ((0.0,0.0,0.0,0.0,0.0),
                                    (0.0,0.0,0.0,0.0,0.0),
                                    (0.0,0.0,0.0,0.0,0.0),
                                    (0.0,0.0,0.0,0.0,0.0));

Note that a two-dimensional array, and an array of one dimensional arrays work slightly differently from a syntax point-of-view. For example

type grid is array(1..10,1..10) of integer;
x: grid;
type vector is array(1..10) of integer;
y: array(1..10) of vector;

The first is a 2D array, and is indexed as x(i,j). The second is an array of arrays and is indexed as y(i)(j). The latter declaration allows specific rows to be treated as separate arrays.

Array types

The arrays we have created so far are anonymous arrays, but it is also possible to create arrays types.

type vector is array (1..1000) of integer;

Now it is possible to create a variable using the type vector.

a: vector;

Now it is possible to perform whole-array operations like assignment, *if* the arrays are derived from the same type.

abstract Array types

It is also possible to create abstract array types where no array bounds are created.

type vector is array (integer range <>) of integer;

Then a variable array can be created with bounds specified:

v: vector(1..10);

PAssing arrays to subroutines

Passing arrays is easiest when a type is created, especially from a parameter declaration perspective. Array objects may be passed as subprogram parameters and returned as function results. Consider the following example (minus the packages):

procedure arrayPass is

   type v2 is array (1..10) of integer;
   m, n: v2;

   function addthem(a,b: in v2) return v2 is c: v2;
   begin
      for i in 1..10 loop
         c(i) := a(i) + b(i);
      end loop;
      return c;
   end addthem;

begin
   m := (1,2,3,4,5,6,7,8,9,10);
   n := addthem(m,(1,1,2,3,5,8,13,21,34,55));

   for i in 1..10 loop
      put(n(i));
   end loop;
end arrayPass;

The function addthem() uses an array type, v2, and reads two arrays in, adds them, and returns them as the array c, (passed to the main program as n).