## Tuesday, August 21, 2012

### AdaTutor - Records and Arrays (2)

#### Multidimensional Arrays

Ada arrays may have any number of dimensions, and the subscripts may be of different discrete types.&nsp; For example, assuming Rainbow_Color, Month_Type, and Date have already been defined, we can write

```    X : array(Integer       range -10 .. -1,
Rainbow_Color range Orange .. Blue,
Month_Type    range Feb .. Jun     ) of Date;
```

Here the first subscript is of type Integer and has 10 possible values, the second subscript is of type Rainbow_Color and has four possible values, and the third subscript has type Month_Type with five possible values.  Thus we have a three-dimensional array of 10 * 4 * 5 = 200 Dates.  One element of the array might be X(-5, Green, Apr); one field of that element might be X(-5, Green, Apr).Year.  The array in this example probably has no use, other than demonstrating that multiple subscripts need not have the same type.

If one subscript of a multidimensional array type is constrained, they must all be constrained.  We can't write

```  -- illegal
type Rectangle is array(1 .. 5, Integer range <>) of Float;
```

Multidimensional arrays are initialized or assigned with nested aggregates.  We can create a two-dimensional array of Floats, initializing all 50 elements to 1.0, with Mat : array(0 .. 9, 1 .. 5) of Float := (others => (others => 1.0));.

Here X is a 10-by-10 array of Integers.  All elements are 0, except X(4, 5), which is 1. We qualify the aggregate because others follows named notation:

```    type Square10 is array (1 .. 10, 1 .. 10) of Integer;
X : Square10 := Square10'(4      =>  (5 => 1, others => 0),
others =>  (others => 0));
```

We initialize or assign an array of arrays with nested aggregates, the same as a multidimensional array.  However, we reference a single element differently:

```    type Square10 is array(1 .. 10, 1 .. 10) of Integer;
type Row10    is array(1 .. 10) of Integer;
type Mat10    is array(1 .. 10) of Row10;
-- S is a two-dimensional array.
S : Square10 := (others => (others => 0));
-- M is an array of arrays.
M : Mat10    := (others => (others => 0));
...
S(4, 5) := 1; -- a single element of a two-dimensional array
M(4)(5) := 1; -- a single element of an array of arrays
```

The "short circuit" forms can prevent us from using array subscripts that are out of range.  For example, if we write

```    A : array(1 .. 10) of Float;
I : Integer;
...
if I in A'Range and then A(I) = 0.0 then
-----;
-----;  (block of code)
-----;
end if;
```
then we know the program won't try to evaluate A(I) when I is outside the range 1 to 10.

#### Question

Whihc one of these is illeagel?
1. ```    subtype Day_Subtype is Integer range 1 .. 31;
type Group is array(Day_Subtype) of Float;
Gr : Group;
...
Gr(3) := 0.0;

```
2. ```    type List is array(1 .. 10) of Integer;
type Page is array(1 .. 20) of List;
Pg : Page;
...
Pg(5)(10) := 0;
```
3. ```    type Row is array (Integer range <>) of Integer;
R1 : Row;
...
R1(1) := 0;
```

#### Strings

There's a very important array type declaration built into the Ada language.  As with types Boolean and Character, and subtypes Positive and Natural, this definition comes with Ada and shouldn't be repeated in our programs:

```    type String is array(Positive range <>) of Character;
```
(In Ada 95, type Wide_String is similarly defined as an array of Wide_Character.)  Thus we can declare, for example, S : String(1 .. 5);.  We can't simply write S : String; because we can't declare unconstrained array objects.  (We can declare S : constant String := "Hello"; and in Ada 95, we may write S : String := "Hello"; because the compiler will translate this to S : String(1 .. 5) := "Hello";).  Note that String isn't a special type in Ada; it's just an array of Characters.  Everything we learned about arrays applies to Strings.  For example, we can assign to S using the same syntax that we use when assigning to an array of any other type.  If we write S : String(1 .. 5); we can write:
```    S := ('H', 'e', 'l', 'l', 'o');
```

However, this notation is clumsy, so Ada allows us to abbreviate an array of Character constants using double quotes.  Thus S := "Hello"; is equivalent to the statement above.  If a quotation mark appears inside the string, it must be doubled.  Thus Ada.Text_IO.Put_Line("a ""big"" man"); will display a "big" man.

It may seem disappointing that Ada Strings have fixed length, and that we can't declare a variable S : String;.  Fortunately, Ada 95 comes with several string-handling packages to simulate variable-length strings; see Annex A of the Ada 95 RM.  The name of the Ada 95 package that provides "Unbounded-Length String Handling" is Ada.Strings.Unbounded, described in Annex A.4.5 of the Ada 95 RM.

Also, later we'll learn how to define our own type Text to get around this restriction and simulate variable-length Strings even in Ada 83.

When arrays are assigned, the lengths must be the same on both sides of the :=, and the types must be the same, but the subscripts needn't be the same.  For example, if we have

```    type Vector is array(Integer range <>) of Float;
V1 : Vector(1 .. 5);
V2 : Vector(2 .. 6) := (others => 0.0);

S1 : String(1 .. 5);
S2 : String(2 .. 6) := (others => ' ');
```
then we can write V1 := V2; and S1 := S2; even though the subscripts are different, because the array lengths are the same and the element types are the same.  But we'll get a Constraint_Error if we write S1 := "Hello there"; or S1 := "Hi"; or V1 := (1.0, 2.0, 3.0);, because these arrays have wrong lengths.  Ada won't automatically truncate Strings or pad with blanks.  Of course, it would be easy to write our own procedure to assign Strings of different lengths, padding or truncating as necessary.

A slice of an array is a portion of an array, and is indicated with a range in the subscript.  A slice is itself an array.  Some languages use the term "substring" to refer to a slice of a String, but in Ada we can take a slice of any kind of array, not just an array of Characters.  So instead of "substring," Ada uses the more general term "slice."  For example, if we have

```   A : array(1 .. 10) of Integer := (1, 2, 3, 4, 5,
6, 7, 8, 9, 10);
```
then A(1 .. 3) is the array (1, 2, 3) and A(6 .. 9) is the array (6, 7, 8, 9).  Similarly, if we have S : String(1 .. 11) := "Hello there"; then S(8 .. 11) is "here" and S(4 .. 5) is "lo".  We can also write S(1 .. 10) := S(2 .. 11); and A(1 .. 3) := A(4 .. 6); since the lengths are the same on both sides.

If the value preceding .. is greater than the value following it, we have a null range.  A slice with a null range has a length of zero, and is called a null slice.  In the case of a null slice, the subscript is not checked for Constraint_Error.  Thus, even if N is 0 we could write S(1 .. N); which would produce the null string "".  This is legal, even though Ada defines "type String is array(Positive range <>) of Character;".  Assigning a null slice to a null slice does no harm and generates no error; it does nothing.  Also, if S is a null array, then S'Length is 0, and S'First and S'Last don't exist.  Using 'First or 'Last with a null array will raise a Constraint_Error.

Beginners sometimes confuse a Character with a String of length 1.  If we write

```    S : String(1 .. 10);
I : Integer := 5;
```
then S(I) is a Character and S(I .. I) is a String of length 1.  Also, 'X' is a Character while "X" is a String of length 1.  Thus we could write
```    S(I) := 'X';
S(I .. I) := "X";
```
but we'd be mixing types if we were to write S(I) := "X"; or S(I .. I) := 'X';.

Fortunately, Ada.Text_IO has a Put for type Character as well as a Put for type String.  (It also has a Get for each of these types.)  Thus we can write either Put(S(I .. I)); or Put(S(I));.  However, Put_Line and Get_Line exist only for Strings, not for Characters.  We'll learn about Ada.Text_IO in more detail later.

#### Question

1. Hello : String;
2. Digit : String(0 .. 9) := "0123456789";
3. Line : String(1 .. 80) := (others => "*");
4. Hello : String(2 .. 6) := "Hello";
5. Hello : String(1 .. 5) := (1 .. 3 => "Hel", 4 => 'l', 5 => 'o');
6. Prompt : String(1 .. 3) := ">";
7. Hello : String(1 .. 5) := 'Hello';
Which one of the above is legal?

#### Array Operators

The operator & concatenates any two arrays of the same type, including two Strings.  It can also concatenate a single element with an array of that element type, or two single elements into an array of length two.  For example, every use of & below is legal:

```    C, D : Character := '*';
S2   : String(1 .. 2);
S3   : String(1 .. 3) := (others => ' ');
S5   : String(1 .. 5);

type Vector is array(Integer range <>) of Float;
F, G : Float := 1.2;
V2   : Vector(1 .. 2);
V3   : Vector(1 .. 3) := (others => 0.0);
V5   : Vector(1 .. 5);
...
S2 := C & D;  S5 := S2 & S3;  S3 := C & S2;  S3 := S2 & C;
V2 := F & G;  V5 := V2 & V3;  V3 := F & V2;  V3 := V2 & F;
```

The operators and, or, xor, and not, defined for Booleans, are also defined for one-dimensional arrays of Boolean.  They operate element by element on the arrays.  Thus, we can simulate sets in Ada.  For example, if we write

```    type Set_Of_Chars is array(Character) of Boolean;
S1 : Set_Of_Chars := Set_Of_Chars'('*' | '#' => True,
others => False);
S2 : Set_Of_Chars := Set_Of_Chars'('*' | '?' => True,
others => False);
```
then S1 or S2 is Set_Of_Chars'('*' | '#' | '?' => True, others => False), and S1 and S2 is Set_Of_Chars'('*' => True, others => False).

The operators = and /= can compare two records or two arrays of the same type.  Records are equal if all of their corresponding fields are equal, arrays, if all of their corresponding elements are equal.  Arrays of different lengths are always unequal.  The four remaining relational operators can compare two arrays of the same type.  They're compared element by element until a difference is found.  For example, if we have

```    S : String(1 .. 6) := "to all";
T : String(1 .. 7) := "to Bill";
```
then S > T because 'a' > 'B' in Ada's definition of type Character.