Introduction

One of the major additions to Ada 2005 is the container library. This library enables the Ada developer to manipulate data structures such as doubly linked lists, maps, sets and vectors. This page will show how the Ada.Containers.Vectors library work.

But first: What is a vector? Here's what the reference manual have to say about it:

The language-defined generic package Containers.Vectors provides private types Vector and Cursor, and a set of operations for each type. A vector container allows insertion and deletion at any position, but it is specifically optimized for insertion and deletion at the high end (the end with the higher index) of the container. A vector container also provides random access to its elements.

A vector container behaves conceptually as an array that expands as necessary as items are inserted. The length of a vector is the number of elements that the vector contains. The capacity of a vector is the maximum number of elements that can be inserted into the vector prior to it being automatically expanded.

Elements in a vector container can be referred to by an index value of a generic formal type. The first element of a vector always has its index value equal to the lower bound of the formal type.

A vector container may contain empty elements. Empty elements do not have a specified value.

Basically it's a one-dimensional array, and in many ways it behaves just like an array, allowing both random and sequential access to the elements of the vector. The main difference, is that a regular array is bound in size, whereas a vector is not, insofar as there are enough resources available to contain the vector. A vector will expand automatically when new elements are added.

The flexibility of vectors is a wonderful thing, but it does come at a cost: A vector is not as fast and lightweight as a regular array. Vectors are by no means slow and/or resource-hungry, but they are both slower and heavier than a regular array. On the other hand, if you can live with the higher resource demands, vectors do give you a great set of very intuitive tools to manage your lists of data.

Using Ada.Containers.Vectors

Learning how to use the Ada.Containers.Vectors library is, in my humble opinion, best done with actual, working code. I'm not a big fan of pseudo-code, so all examples on this page will compile and work.

The examples all center around a small “Quotes by Linus Torvalds” application, where we have a bunch of quotes in a regular, flat text file. The file (quotes.txt) will be used throughout all the examples, and it looks like this:

I have an ego the size of a small planet.
My name is Linus Torvalds and I am your god.
Do you pine for the days when men were men and wrote their own device drivers?
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
An infinite number of monkeys typing into GNU emacs would never make a good program.
Is "I hope you all die a painful death" too strong?
Most days I wake up thinking I'm the luckiest bastard alive.
I'm always right. This time I'm just even more right than usual.
And what's the internet without the rick-roll? 

In case it isn't obviously clear, each line represents one quote from Linus.

The idea is that quotes are added and removed from this file on a whim, and as such the program doesn't know how many quotes there are before we've read them all, making vectors the perfect data structure to both hold and manipulate all the quotes.

We're not actually going to build the entire application. We'll just focus on the part where the quotes are read from the quotes.txt file, and then added to a vector.

Before we start on the actual code, lets take a peek at the beginning of the Ada.Containers.Vectors specification:

generic
   type Index_Type is range <>;
   type Element_Type is private;

Note the types Index_Type and Element_Type. The former is what defines the index of the vector and the latter is the type of elements the vector contains. With that in mind, lets have a look at the basic Quotes program itself:

with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;      use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Quotes is
   package IO renames Ada.Text_IO;
   package SUIO renames Ada.Text_IO.Unbounded_IO;
   package Quote_Container is new Vectors (Natural, Unbounded_String);
   use Quote_Container;
   Quotes   : Vector;
   Input    : IO.File_Type;
begin
   IO.Open (File => Input,
            Mode => IO.In_File,
            Name => "quotes.txt");
   while not IO.End_Of_File (File => Input) loop
      Quotes.Append (New_Item => SUIO.Get_Line (File => Input));
   end loop;
   IO.Close (Input);
end Quotes;

If we focus on the vector related parts of the program, we see that on line 4 the Ada.Containers.Vectors library unit is added to the program. This step is vital to being able to use the vectors at all, just as Ada.Text_IO is necessary for basic string IO. If this concept is unfamiliar to you, you should probably read up on the basics of Ada and then return here again.

With the vectors library readily available, we turn our attention to line 9, where we instantiate the Vectors generic with the parameters Natural and Unbounded_String. The result is a vector where the index starts at 0 (remember that Natural is a subtype of Integer with the range 0 .. Integer'Last) and the elements are of type Unbounded_String. On line 11 we declare the Quotes variable as type Vector. We don't need the full declaration, Quote_Container.Vector, because of the use Quote_Container clause at line 10.

We now have a working vector, Quotes, so the next natural step is adding some data to it. This is done on line 18, where each quote is appended to the vector, using the Append procedure. Finally we close the file, and exit the program.

This little Quotes program is the foundation on which this entire article is built. Nearly all of the examples below are simply expanding on this program. But before we move ahead with the various procedures and functions of the vectors library, lets take short look at the vectors package for indefinite types.

Ada.Containers.Indefinite_Vectors

The sibling library Ada.Containers.Indefinite_Vectors is available for indefinite types. It works in much the same way as Ada.Containers.Vectors, except for a few omitted Insert procedures. Here's an example on how it works:

with Ada.Text_IO;
with Ada.Containers.Indefinite_Vectors; use Ada.Containers;
 
procedure Quotes is
   package IO renames Ada.Text_IO;
   package Quote_Container is new Indefinite_Vectors (Natural, String);
   use Quote_Container;
   Quotes    : Vector;
   Input     : IO.File_Type;
begin
   IO.Open (File => Input,
            Mode => IO.In_File,
            Name => "quotes.txt");
   while not IO.End_Of_File (File => Input) loop
      Quotes.Append (New_Item => IO.Get_Line (File => Input));
   end loop;
   IO.Close (Input);
 
   IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
   IO.Put_Line (Item => "Quote no. 2: " & Quotes.Element (Index => 1));
end Quotes;

Compile and execute this code, and you get:

No. of quotes: 10
Quote no. 2: My name is Linus Torvalds and I am your god.

It is important to note, that the indefinite vectors are slower than the constrained vectors, so avoid them if performance is important.

Because the definite and indefinite containers are so alike, the rest of this article will concern itself only with the definite version of the vectors library.

Procedures and functions

There's a lot of procedures and functions in the Ada.Containers.Vectors package. Luckily they are all very aptly named, so figuring which procedure or function you need for a specific situation is usually pretty simple. To further help in identifying what exactly procedure X or function Y does, I've divided them into several groups, according to their main functionality.

Some procedures/functions might fit in more than one category. In those cases, I've placed them in the category in which I feel they are the most commonly used.

Setting a vectors size/capacity

In the Ada.Containers.Vectors package there are tools available to create vectors of a specified size/capacity. The primary goal of these methods is to enable the programmer to assign the amount of resources needed, thereby getting rid of the performance overhead of calling Reserve_Capacity internally whenever a new element is added to the vector.

Vectors.Reserve_Capacity

Here's the specification for Reserve_Capacity:

procedure Reserve_Capacity
     (Container : in out Vector;
      Capacity  : Count_Type);

The Ada Reference Manual explains Reserve_Capacity best:

Reserve_Capacity allocates new internal data structures such that the length of the resulting vector can become at least the value Capacity without requiring an additional call to Reserve_Capacity, and is large enough to hold the current length of Container. Reserve_Capacity then copies the elements into the new data structures and deallocates the old data structures. Any exception raised during allocation is propagated and Container is not modified.

There are really only a few cases where using Reserve_Capacity makes any sense, and some of those rare cases are:

  • Prepare resources for a vector of a certain size
  • Release excessive resources

Of course there might be several other reasons, but those two are probably the most common, with the first one being the absolute winner. So, lets go ahead and add the following code to the body of the Quotes program, after the call to IO.Close (Input); on line 20:

IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
Quotes.Reserve_Capacity (Capacity => 100);
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);

Compile and execute Quotes and you get:

Capacity: 16
Capacity: 100

Lets try and expand a bit on the Quotes program, and see how we can manipulate the vector during execution of the program. Place this before the call to IO.Open:

Quotes.Reserve_Capacity (Capacity => 100);
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);

And this after the call to IO.Close:

IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);
Quotes.Reserve_Capacity (Capacity => Quotes.Length);   
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);

And the output:

Capacity: 100
Length: 0
Capacity: 100
Length: 10
Capacity: 10
Length: 10

If you're working with big vectors, it might be worth it to check if a few well-placed Reserve_Capacity calls can speed up your code and/or save you some resources, but as with all optimization: Don't do it prematurely and test it rigorously.

Vectors.Reserve_Capacity Example Source

Vectors.Set_Length

Here's the specification for Length:

procedure Set_Length
  (Container : in out Vector;
   Length    : Count_Type);

One might wonder what the point of a procedure like Set_Length is, since the vector will just grow as new elements are appended/prepended/inserted, so why? In order to answer this question, it's important to understand that a vectors length can never exceed its capacity.

The capacity of a vector is the number of internal data structures set aside for the vector. This might be a lot higher than the vector's actual length, but it can never be less (see Vectors.Capacity). So when a new element is added to a vector, first the capacity of the vector is expanded using Reserve_Capacity (if necessary of course) and then the new element is added. So adding 100 new elements to a vector, effectively calls Reserve_Capacity 100 times. Surely this is a waste of resources, and it's exactly that waste we can avoid by using Set_Length. If we know we'll be adding a bunch of new elements, we can accumulate all those calls to Reserve_Capacity into one call to Set_Length. This is much more efficient.

Another use for Set_Length is to shorten a vector, as we'll see in the following example:

IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
Quotes.Set_Length (Length => 20);
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
Quotes.Set_Length (Length => 5);
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);

And the output:

Length of vector: 10
Length of vector: 20
Length of vector: 5

To show the benefits of using Set_Length, I've done two small programs. The first adds 10_000_000 new elements using Append. The second program does the same, but it does so using Set_Length and Replace_Element.

with Ada.Text_IO;
with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Quotes is
   package IO renames Ada.Text_IO;
   package Number_Container is new Vectors (Natural, Natural);
   Numbers   : Number_Container.Vector;
begin
   IO.Put_Line ("Length:" & Numbers.Length'Img);
   for i in 0 .. 9999999 loop
      Numbers.Append (New_Item => i);
   end loop;
   IO.Put_Line ("Length:" & Numbers.Length'Img);
end Quotes;

Here's how this looks when timed on my computer:

Length: 0
Length: 10000000
real    0m0.438s
user    0m0.336s
sys     0m0.096s

Now, let us rewrite this little gem with the aid of Set_Length and re-time it:

with Ada.Text_IO;
with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Quotes is
   package IO renames Ada.Text_IO;
   package Number_Container is new Vectors (Natural, Natural);
   Numbers   : Number_Container.Vector;
begin
   IO.Put_Line ("Length:" & Numbers.Length'Img);
   Numbers.Set_Length (Length => 10000000);
   for i in 0 .. 9999999 loop
      Numbers.Replace_Element (Index    => i,
                               New_Item => i);
   end loop;
   IO.Put_Line ("Length:" & Numbers.Length'Img);
end Quotes;

And here's the numbers for this version:

Length: 0
Length: 10000000
real    0m0.109s
user    0m0.064s
sys     0m0.044s

This is a massive improvement in execution time, so be sure to use Set_Length if you need to add lots of new elements to a vector.

The above results are an average of 10 runs.

Vectors.Set_Length Example Source

Vectors.To_Vector

The specification for To_Vector looks like this:

function To_Vector (Length : Count_Type) return Vector;
 
function To_Vector
  (New_Item : Element_Type;
   Length   : Count_Type) return Vector;

It should be very obvious what the two To_Vector functions do. I'm going to lump the examples for both functions together, as they are so alike. Add this to the body of the Quotes program:

IO.Put (Item => "1st. element: ");
SUIO.Put (Item => Quotes.Element (Index => 0));
IO.New_Line;
IO.Put (Item => "10th. element: ");
SUIO.Put_Line (Item => Quotes.Element (Index => 9));
 
Quotes := To_Vector (Length => 10);
 
IO.Put (Item => "1st. element: ");
SUIO.Put (Item => Quotes.Element (Index => 0));
IO.New_Line;
IO.Put (Item => "10th. element: ");
SUIO.Put_Line (Item => Quotes.Element (Index => 9));
 
Quotes := To_Vector (New_Item => To_Unbounded_String 
                     ("Put new quote here"),
                     Length   => 10);
 
IO.Put (Item => "1st. element: ");
SUIO.Put (Item => Quotes.Element (Index => 0));
IO.New_Line;
IO.Put (Item => "10th. element: ");
SUIO.Put_Line (Item => Quotes.Element (Index => 9));

And the output is:

1st. element: I have an ego the size of a small planet.
10th. element: And what's the internet without the rick-roll?
1st. element: 
10th. element: 
1st. element: Put new quote here
10th. element: Put new quote here

So what have we learned? Well, the first To_Vector function creates a new vector with Length empty elements, and the second To_Vector functions creates a new vector with Length elements that all have the value New_Item.

As we'll learn later, it is actually a bounded error to read an empty element, so the second To_Vector is probably the safest to use, as you force a valid value into each element.

Vectors.To_Vector Example Source

Inserting data and/or empty elements

Inserting data into a vector is a crucial part of working with vectors. Empty vectors are usually not that interesting, so lets see how we can add some data to them.

Vectors.Append

The specification for Append looks like this:

procedure Append
  (Container : in out Vector;
   New_Item  : Element_Type;
   Count     : Count_Type := 1);
 
procedure Append
  (Container : in out Vector;
   New_Item  : Vector);

What Append does, is add elements or other vectors to the end of a vector.

If we add some output functionality to the program, we can see how this works:

SUIO.Put_Line (Item => Quotes.Element (9));

The Element function will be discussed in detail later, but for now it's enough to know that the Element function returns the element (Element_Type) found at the given index (Index_Type), in this case index 10.

Executing the program should result in the following output:

And what's the internet without the rick-roll?

To make it absolutely clear what Append does, lets add these three lines to the body of the program:

--  Output the contents of index no. 9
SUIO.Put_Line (Item => Quotes.Element (9));
 
--  Append a new element to the vector 
Quotes.Append (New_Item => To_Unbounded_String ("Test append"));
 
--  Output the contents of the index no. 10
SUIO.Put_Line (Item => Quotes.Element (10));

Executing the program, we now get this output:

And what's the internet without the rick-roll?
Test append

Looking at the specification for the first Append procedure, you might've noticed the optional Count parameter. The functionality of this parameter closely matches its name, as it enables you to repeat the insertion of the element Count amount of times. Lets see it in action. Try adding this to the body of the Quotes program:

SUIO.Put_Line (Item => Quotes.Element (9));
 
--  Append the 3 identical elements to the vector
Quotes.Append (New_Item => To_Unbounded_String ("Test append"),
               Count => 3);
SUIO.Put_Line (Item => Quotes.Element (10));
SUIO.Put_Line (Item => Quotes.Element (11));
SUIO.Put_Line (Item => Quotes.Element (12));

And the output:

And what's the internet without the rick-roll?
Test append
Test append
Test append

In the above example, we've used a Count value of three, and as expected the element “Test append” is appended to the vector three times.

The second Append procedure in the Vectors package appends a vector to another vector. To see how this works, we'll need to create a new vector, which requires an addition to the declarative part of the program. Add the following declaration:

My_Quotes : Vector;

What we've just done is declare a new vector, My_Quotes. This is the vector we will append to the Quotes vector.

Now add this to the body of the program:

My_Quotes.Append (New_Item => To_Unbounded_String ("My 1st. quote"));
My_Quotes.Append (New_Item => To_Unbounded_String ("My 2nd. quote"));
My_Quotes.Append (New_Item => To_Unbounded_String ("My 3rd. quote"));
 
SUIO.Put_Line (Item => Quotes.Element (9));
SUIO.Put_Line (Item => My_Quotes.Element (0));
 
--  Append the My_Quotes vector to the Quotes vector
Quotes.Append (New_Item => My_Quotes);
SUIO.Put_Line (Item => Quotes.Element (12));

The output of executing this program is:

And what's the internet without the rick-roll?
My 1st. quote
My 3rd. quote

Notice that we're using the same method as if we had appended a regular element, except that there's no Count parameter used in the Append procedure that appends vectors.

There's not much else to say about the Append procedure, so lets move on to its sibling: Prepend.

Vectors.Append Example Source

Vectors.Prepend

The specification for Prepend looks like this:

procedure Prepend
  (Container : in out Vector;
  New_Item  : Vector);
 
procedure Prepend
  (Container : in out Vector;
   New_Item  : Element_Type;
   Count     : Count_Type := 1);

Prepend is very similar to Append, the only difference being that data is inserted at the beginning of the vector instead of at the end. To see it in action, add the following to the body of the basic Quotes program:

SUIO.Put_Line (Item => Quotes.Element (0));
 
--  Prepend a new element to the Quotes vector
Quotes.Prepend (New_Item => To_Unbounded_String ("Prepended!"));
SUIO.Put_Line (Item => Quotes.Element (0));
SUIO.Put_Line (Item => Quotes.Element (1));

The result when running the program is, as expected:

I have an ego the size of a small planet.
Prepended!
I have an ego the size of a small planet.

As you can see, the “Prepended!” text is added to the vector before the ego quote from Linus.

It is also possible with Prepend to join two vectors. The functionality is the same, the only difference being that a vector is being copied instead of a single element.

Vectors.Prepend Example Source

Vectors.Insert

The specification for Insert looks like this:

procedure Insert
  (Container : in out Vector;
   Before    : Extended_Index;
   New_Item  : Vector);
 
procedure Insert
  (Container : in out Vector;
   Before    : Cursor;
   New_Item  : Vector);
 
procedure Insert
  (Container : in out Vector;
   Before    : Cursor;
   New_Item  : Vector;
   Position  : out Cursor);
 
procedure Insert
  (Container : in out Vector;
   Before    : Extended_Index;
   New_Item  : Element_Type;
   Count     : Count_Type := 1);
 
procedure Insert
  (Container : in out Vector;
   Before    : Cursor;
   New_Item  : Element_Type;
   Count     : Count_Type := 1);
 
procedure Insert
  (Container : in out Vector;
   Before    : Cursor;
   New_Item  : Element_Type;
   Position  : out Cursor;
   Count     : Count_Type := 1);
 
procedure Insert
  (Container : in out Vector;
   Before    : Extended_Index;
   Count     : Count_Type := 1);
 
procedure Insert
  (Container : in out Vector;
   Before    : Cursor;
   Position  : out Cursor;
   Count     : Count_Type := 1);

Insert enables us to insert one or more identical elements anywhere in the list. It also introduces us to a new concept: Cursors.

But lets start with the Insert procedures that are using the index to identify individual elements. Since they are very similar to each other, I'll put them all in the same example. Add the following to the body of the Quotes program:

--  Insert an element at the second index of the vector
Quotes.Insert (Before   => 1,
               New_Item => To_Unbounded_String ("New second index!"));
 
--  Insert two identical elements at the fifth index of the vector
Quotes.Insert (Before   => 4,
               New_Item => To_Unbounded_String ("Two new elements @ 4 and 5!"),
               Count    => 2);
 
--  Insert two empty elements before the last index of the vector
Quotes.Insert (Before => Quotes.Last_Index,
               Count  => 2);
 
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (Integer (i)));
end loop;

The output of this program is:

0 I have an ego the size of a small planet.
1 New second index!
2 My name is Linus Torvalds and I am your god.
3 Do you pine for the days when men were men and wrote their own device drivers?
4 Two new elements @ 4 and 5!
5 Two new elements @ 4 and 5!
6 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
7 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
8 An infinite number of monkeys typing into GNU emacs would never make a good program.
9 Is "I hope you all die a painful death" too strong?
10 Most days I wake up thinking I'm the luckiest bastard alive.
11 I'm always right. This time I'm just even more right than usual.
12 
13 
14 And what's the internet without the rick-roll?

Note that the last call of Insert does not insert two empty elements. Instead, two elements of the vectors default value is inserted. As far as I know, this value currently is empty, but I wouldn't rely on it. If you need to insert empty elements, look further down for the Insert_Space procedures.

With Insert you can insert new elements anywhere in the vector, simply by setting the Before parameters appropriately. The vector will expand as necessary and elements following the Before index will be pushed down by the amount of the Count parameter (the default for which is 1).

The remaining Insert procedures all make use of a cursor. With a cursor you address both the vector and the element. In the next example I'll show all the Insert procedures using cursors. In order to use a cursor, we must first declare it, so add the following to the Quotes declaration:

Q_Cursor  : Cursor;

And add the following to the body:

--  Move the cursor to the first position of the Quotes vector.
Q_Cursor := Quotes.First;
 
--  Insert an element at the first position of the vector.
Quotes.Insert (Before   => Q_Cursor,
               New_Item => To_Unbounded_String ("New index 0!"));
 
--  Move the cursor to the 5th. position of the vector
Q_Cursor := Quotes.To_Cursor (Index => 4);
 
--  Insert an element before the current 5th. position in the vector and
--  set the current position of the cursor.
Quotes.Insert (Before   => Q_Cursor,
               New_Item => To_Unbounded_String ("New index 4!"),
               Position => Q_Cursor);
 
--  Move the cursor to the next position
Q_Cursor := Next (Position => Q_Cursor);
 
--  Insert two new elements before the current 6th. position in the vector.
Quotes.Insert (Before   => Q_Cursor,
               New_Item => To_Unbounded_String ("New index 5 and 6!"),
               Count    => 2);
 
--  Move the cursor to the 10th. position of the vector.
Q_Cursor := Quotes.To_Cursor (Index => 9);
 
--  Insert two elements before the 10th. position and set the current 
--  position of the cursor.
Quotes.Insert (Before   => Q_Cursor,
               New_Item => To_Unbounded_String ("New index 9 and 10!"),
               Position => Q_Cursor,
               Count    => 2);
 
--  Move the cursor to the last position of the vector.
Q_Cursor := Quotes.Last;
 
--  Insert two empty elements before the last position and set the position
--  of the cursor.
Quotes.Insert (Before   => Q_Cursor,
               Position => Q_Cursor,
               Count    => 2);
 
--  Move the cursor to the 1st. postion of the vector.
Q_Cursor := Quotes.First;
for i in Quotes.First_Index .. Quotes.Last_Index loop
   --  Output the element of the current position.
   SUIO.Put_Line (Item => To_Index (Position => Q_Cursor)'Img & 
                  Element (Position => Q_Cursor));
 
   --  Move the cursor to the next position in the vector.
   Next (Position => Q_Cursor);
end loop;

The result of running the program is:

0New index 0!
1I have an ego the size of a small planet.
2My name is Linus Torvalds and I am your god.
3Do you pine for the days when men were men and wrote their own device drivers?
4New index 4!
5New index 5 and 6!
6New index 5 and 6!
7If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
8You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
9New index 9 and 10!
10New index 9 and 10!
11An infinite number of monkeys typing into GNU emacs would never make a good program.
12Is "I hope you all die a painful death" too strong?
13Most days I wake up thinking I'm the luckiest bastard alive.
14I'm always right. This time I'm just even more right than usual.
15
16
17And what's the internet without the rick-roll?

When I first wrote this, I had a hard time coming up with good reasons for using cursors with the Ada.Containers.Vectors library - To me, using the index seemed more natural and more “in tune” with a vector, but later on I learned that there are in fact at least two good reasons for having cursors available:

  1. They make generic algorithms possible
  2. If a program is created using cursors, it is easy to switch to one of the other sequence containers

So there are in fact at least two good reasons for using cursors. Of course, if you are computing index values, using cursors doesn't gain you anything. As usual we have to think hard about what we need, and choose the tools accordingly.

Vectors.Insert Example Source

Vectors.Insert_Space

When you need to insert a bunch of “empty” elements, then Insert_Space is just the right tool for the job. The specification for Insert_Space looks like this:

procedure Insert_Space
  (Container : in out Vector;
   Before    : Extended_Index;
   Count     : Count_Type := 1);
 
procedure Insert_Space
  (Container : in out Vector;
   Before    : Cursor;
   Position  : out Cursor;
   Count     : Count_Type := 1);

An important thing to note when working with Insert_Space, is that “empty” elements really don't mean that. Here's what the Reference Manual has to say about it:

Then Insert_Space slides the elements in the range Before .. Last_Index (Container) up by Count positions, and then inserts empty elements in the positions starting at Before.

And later:

Reading the value of an empty element by calling Element, Query_Element, Update_Element, Swap, Is_Sorted, Sort, Merge, ”=”, Find, or Reverse_Find is a bounded error. The implementation may treat the element as having any normal value (see 13.9.1) of the element type, or raise Constraint_Error or Program_Error before modifying the vector.

The important thing to take away from all this, is that “empty” really means “unpredictable value”. It all depends on the compiler, and how it treats the new element, so don't expect Insert_Space to deliver neat and tidy empty elements. It may well just hand you a bunch of copies of already existing elements or some other value, as long as it is valid in the context of the Vector, in our case meaning an Unbounded_String. Also please note that some compilers may raise Constraint_Error or Program_Error if you try to use an Element function on the “empty” element. In the case of GNATMAKE GPL 2008 (20080521), which is what I'm currently using, the empty elements are treated as having any normal value of the element type.

With that out of the way, lets move on and see how Insert_Space behaves in actual code. First add this to the declarative part of the Quotes program:

Q_Cursor  : Cursor;  --  Declare the cursor.

Now we can also use the cursor edition of Insert_Space. Next we add this to the body of the program:

--  Output all the quotes prior to any Insert_Space calls
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
 
IO.New_Line (2);
 
--  Insert two "empty" elements at the beginning of the vector
Quotes.Insert_Space (Before => 0,
                     Count => 2);
 
--  Insert 3 "empty" elements before index 5 in the vector
Quotes.Insert_Space (Before => 5,
                     Count  => 3);
 
--  Insert 3 "empty" elements before the last element, using a cursor.
Q_Cursor := Quotes.To_Cursor (Index => Quotes.Last_Index);
Quotes.Insert_Space (Before   => Q_Cursor,
                     Position => Q_Cursor,
                     Count    => 3);
 
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;

And here's the resulting output:

0 I have an ego the size of a small planet.
1 My name is Linus Torvalds and I am your god.
2 Do you pine for the days when men were men and wrote their own device drivers?
3 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
4 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
5 An infinite number of monkeys typing into GNU emacs would never make a good program.
6 Is "I hope you all die a painful death" too strong?
7 Most days I wake up thinking I'm the luckiest bastard alive.
8 I'm always right. This time I'm just even more right than usual.
9 And what's the internet without the rick-roll?


0 I have an ego the size of a small planet.
1 My name is Linus Torvalds and I am your god.
2 I have an ego the size of a small planet.
3 My name is Linus Torvalds and I am your god.
4 Do you pine for the days when men were men and wrote their own device drivers?
5 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
6 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
7 An infinite number of monkeys typing into GNU emacs would never make a good program.
8 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
9 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
10 An infinite number of monkeys typing into GNU emacs would never make a good program.
11 Is "I hope you all die a painful death" too strong?
12 Most days I wake up thinking I'm the luckiest bastard alive.
13 I'm always right. This time I'm just even more right than usual.
14 
15 
16 
17 And what's the internet without the rick-roll?

The first thing we notice, is that the vector is 18 positions long after the various calls to Insert_Space. This matches perfectly with the three Count parameters: 2 + 3 + 3 = 8 plus the original length of 10. Then we notice that in some places Insert_Space has inserted what appear to be empty elements (positions 14-16), whereas in other places, copies of already existing elements have been inserted (positions 0-1 and 8-9). This shows the “unpredictable value” behavior I mentioned at the beginning.

So when you're using Insert_Space, don't think of it as a procedure that does exactly what its name would seem to imply. Think of it as a procedure that simply expands the length of the vector at a specified index. In other words: Insert_Space should primarily be used to quickly expand the vector with Count elements at the Before position and you should never rely on those elements having any real meaning until you've filled them with some valid data. Thus the access we're doing above in the final loop is not a very bright thing to do. We could've experienced a raised exception due to the call to Element, as noted in the reference manual quote.

We can avoid this “issue” by using the Replace_Element procedure to fill the empty elements with some real data:

--  Output all the quotes prior to any Insert_Space calls
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
 
IO.New_Line (2);
 
--  Insert two "empty" elements at the beginning of the vector
Quotes.Insert_Space (Before => 0,
                     Count => 2);
 
--  We now insert some actual data in the two new elements
Quotes.Replace_Element (Index    => 0,
                        New_Item => To_Unbounded_String ("New index 0"));
Quotes.Replace_Element (Index    => 1,
                        New_Item => To_Unbounded_String ("New index 1"));
 
--  Insert 3 "empty" elements before index 5 in the vector
Quotes.Insert_Space (Before => 5,
                     Count  => 3);
 
--  We now insert some actual data in the three new elements
Quotes.Replace_Element (Index    => 5,
                        New_Item => To_Unbounded_String ("New index 5"));
Quotes.Replace_Element (Index    => 6,
                        New_Item => To_Unbounded_String ("New index 6"));
Quotes.Replace_Element (Index    => 7,
                        New_Item => To_Unbounded_String ("New index 7"));
 
--  Insert 3 "empty" elements before the last element, using a cursor.
Q_Cursor := Quotes.To_Cursor (Index => Quotes.Last_Index);
Quotes.Insert_Space (Before   => Q_Cursor,
                     Position => Q_Cursor,
                     Count    => 3);
 
--  We now insert some actual data in the last three new elements
Quotes.Replace_Element (Index    => 14,
                        New_Item => To_Unbounded_String ("New index 14"));
Quotes.Replace_Element (Index    => 15,
                        New_Item => To_Unbounded_String ("New index 15"));
Quotes.Replace_Element (Index    => 16,
                        New_Item => To_Unbounded_String ("New index 16"));
 
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;

And the output is:

0 I have an ego the size of a small planet.
1 My name is Linus Torvalds and I am your god.
2 Do you pine for the days when men were men and wrote their own device drivers?
3 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
4 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
5 An infinite number of monkeys typing into GNU emacs would never make a good program.
6 Is "I hope you all die a painful death" too strong?
7 Most days I wake up thinking I'm the luckiest bastard alive.
8 I'm always right. This time I'm just even more right than usual.
9 And what's the internet without the rick-roll?


0 New index 0
1 New index 1
2 I have an ego the size of a small planet.
3 My name is Linus Torvalds and I am your god.
4 Do you pine for the days when men were men and wrote their own device drivers?
5 New index 5
6 New index 6
7 New index 7
8 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
9 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
10 An infinite number of monkeys typing into GNU emacs would never make a good program.
11 Is "I hope you all die a painful death" too strong?
12 Most days I wake up thinking I'm the luckiest bastard alive.
13 I'm always right. This time I'm just even more right than usual.
14 New index 14
15 New index 15
16 New index 16
17 And what's the internet without the rick-roll?

With those Replace_Element calls in place, we can safely read the entire vector in the final loop, using the Element function, without having to worry about raising any exceptions.

Vectors.Insert_Space Example Source

Reading and querying the vector

Just as important as adding data to a vector, is being able to read the data, so lets have a look at some of the methods available to do just that.

Vectors.Element

We've already met the Element function while going over the previous procedures and functions, so what it does and how it works should be apparent by now. That won't stop us, however, from giving it the once-over just like the rest of the Vectors package. Here's the specification for the two Element functions:

function Element
  (Container : Vector;
   Index     : Index_Type) return Element_Type;
 
function Element (Position : Cursor) return Element_Type;

We have one Element function where we access the elements using an index, and one where we're using a cursor. Both do the same thing: Return the Element_Type on the given location in the vector. Here's an example using the index:

--  Output first index 0 and then index 7
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
SUIO.Put_Line (Item => Quotes.Element (Index => 7));

And the output of this is:

I have an ego the size of a small planet.
Most days I wake up thinking I'm the luckiest bastard alive.

The cursor example requires an addition to the declarative part of the Quotes program:

Q_Cursor  : Cursor;

After that we add this to the body of the Quotes program:

--  Point the cursor at the first index of the vector
Q_Cursor := Quotes.To_Cursor (Index => Quotes.First_Index);
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
 
--  Point the cursor at index 7 of the vector
Q_Cursor := Quotes.To_Cursor (Index => 7);
SUIO.Put_Line (Item => Element (Position => Q_Cursor));

And the output is:

I have an ego the size of a small planet.
Most days I wake up thinking I'm the luckiest bastard alive.

Element is quite straightforward. Whether you use an index or a cursor is a matter of taste. It seems worth noting that most of the cursor related procedures and functions in the Vectors package internally translate the cursor to an index and then use the index procedure/functions to do the actual heavy lifting. Due to this, I usually avoid the cursor forms. I think an index is much easier to work with and relate to. The primary benefit of using the cursor forms is one of flexibility. If you use cursors, your code will work, with very few changes, with the other sequence based containers, such as doubly linked lists.

Vectors.Element Example Source

Vectors.Query_Element

Here's the specification for Query_Element:

procedure Query_Element
  (Container : Vector;
   Index     : Index_Type;
   Process   : not null access procedure (Element : Element_Type));
 
procedure Query_Element
  (Position : Cursor;
   Process  : not null access procedure (Element : Element_Type));

Query_Element is both similar to and different from the Element function. Where Element returns the Element_Type on the given index, Query_Element hands the Element_Type over to the Process parameter. This parameter accepts a procedure that handles the Element_Type. To show how this work, we'll first need to add a new procedure to the specification of the Quotes program:

--  This is the "callback" procedure
procedure Add_42 (Element : Unbounded_String) is
begin
   SUIO.Put_Line (Item => Element & To_Unbounded_String (" 42"));
end Add_42;

All we do in this procedure is output the element and concatenate 42 to the output.

Also in order to use a cursor, we must declare it first:

Q_Cursor  : Cursor;  --  Declare the cursor.

The next bit of code should be added to the body of the Quotes program. We'll lump both the index and the cursor versions of Query_Element into the same example:

--  Output the first index
Quotes.Query_Element (Index   => 0,
                      Process => Add_42'Access);
 
--  Point the cursor at index 7 of the vector and output the element
Q_Cursor := Quotes.To_Cursor (Index => 7);
Query_Element (Position => Q_Cursor, Process  => Add_42'Access);

Note the 'Access attribute. This returns a pointer to the Add_42 subprogram, and it is absolutely required for the Process parameter. It is not enough to just write Ada_42, as you can also see from the specification, where it says not null access procedure.

The output from this program is:

I have an ego the size of a small planet. 42
Most days I wake up thinking I'm the luckiest bastard alive. 42

Whether to use Element or Query_Element is a matter of performance vs. clutter. Query_Element is faster than Element, but the mess of using a call-back procedure is, perhaps, not worth the cluttering of the source code, unless performance is crucial. The larger the elements are, the more performance will suffer when using Element.

Vectors.Query_Element Example Source

Alteration - how to "poke" the elements

Changing the data in a vector is a common task. Here are two methods to accomplish this.

Vectors.Update_Element

If you need to update elements in a vector, the Update_Element procedures will come in handy. They work in much the same way as Query_Element, i.e. you must create a callback procedure to handle whatever it is you're trying to do to the elements in the vector. Here's the specification for Update_Element:

procedure Update_Element
  (Container : in out Vector;
   Index     : Index_Type;
   Process   : not null access procedure (Element : in out Element_Type));
 
procedure Update_Element
  (Container : in out Vector;
   Position  : Cursor;
   Process   : not null access procedure (Element : in out Element_Type));

Notice how these two procedures look almost exactly like the Query_Element procedures. The most crucial difference is the Process parameter, where the parameter mode is now in out, as opposed to in for the Query_Element procedures. Because they are so alike we'll declare the same cursor and the callback procedure from the Query_Element example. We will only change the parameter mode:

procedure Add_42 (Element : in out Unbounded_String) is
begin
   Element := Element & To_Unbounded_String (" 42");
end Add_42;
Q_Cursor  : Cursor;

And just as with Query_Element, I'll show both the index and the cursor example in the same listing:

--  Update the first index
Quotes.Update_Element (Index   => 0,
                       Process => Add_42'Access);
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
 
--  Point the cursor at the index 7 of the vector
Q_Cursor := Quotes.To_Cursor (Index => 7);
Quotes.Update_Element (Position => Q_Cursor,
                       Process  => Add_42'Access);
SUIO.Put_Line (Item => Quotes.Element (Index => 7));

The output from this program is:

I have an ego the size of a small planet. 42
Most days I wake up thinking I'm the luckiest bastard alive. 42

Update_Element is not the only way to alter the contents of an element in a vector. There are other ways to concatenate “42” to our Linus quotes, and one of those methods is Replace_Element, which we will look at next.

Vectors.Update_Element Example Source

Vectors.Replace_Element

Where the previously discussed Update_Element procedure updates an element in situ, Replace_Element assigns a new Element_Type to a given index or position, regardless of the content already there. Here's the specification for Replace_Element:

procedure Replace_Element
  (Container : in out Vector;
   Index     : Index_Type;
   New_Item  : Element_Type);
 
procedure Replace_Element
  (Container : in out Vector;
   Position  : Cursor;
   New_Item  : Element_Type);

Using Replace_Element is very similar to the Insert procedures, except you're now working on an index that already contains a value instead of creating a new index. Leave the Q_Cursor declaration from Update_Element and add the following to the body of the Quotes program:

--  Replace the first index
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
Quotes.Replace_Element (Index    => 0,
                        New_Item => To_Unbounded_String ("New index 0"));
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
 
--  Replace the index 7 using a cursor
SUIO.Put_Line (Item => Quotes.Element (Index => 7));
Q_Cursor := Quotes.To_Cursor (Index => 7);
Quotes.Replace_Element (Position => Q_Cursor,
                        New_Item => To_Unbounded_String ("New index 7"));
SUIO.Put_Line (Item => Quotes.Element (Index => 7));

And the output from this program is:

I have an ego the size of a small planet.
New index 0
Most days I wake up thinking I'm the luckiest bastard alive.
New index 7

When to use Replace_Element and when to use Update_Element is highly dependent on what it is you're trying to accomplish. Just remember that Update_Element updates the element on the given index in situ, whereas Replace_Element completely replaces the element on the given index, and just as with Element and Query_Element, Update_Element is faster than Replace_Element, but it clutters the source code with a call-back procedure. So as with so many other things, it's a trade-off between clean code and performance, so be sure to test things and choose the best fit for the project.

Vectors.Replace_Element Example Source

Deleting and clearing

It is, of course, possible to delete elements from a vector and clear a vector completely. I bet the attentive reader will already gave guessed the names of these procedures. There are some surprises, however.

Vectors.Clear

Here's the specification for Clear:

procedure Clear (Container : in out Vector);

Clear removes all the elements from a vector, but does not change the capacity of the vector. So if you want to reset a vector, while retaining the vectors capacity, then Clear is the way to go. Try adding this to the body of the Quotes program:

IO.Put_Line (Item => "Capacity before clear:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "No. of quotes before clear:" & Quotes.Length'Img);
Quotes.Clear;
IO.Put_Line (Item => "Capacity after clear:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "No. of quotes after clear:" & Quotes.Length'Img);

And the output is:

Capacity before clear: 16
No. of quotes before clear: 10
Capacity after clear: 16
No. of quotes after clear: 0

Note that your capacity numbers might differ from the above. The important thing to note is that the capacity is the same, before and after calling Clear. What is changed is the length value for the contents of the vector.

Unfortunately therefore, you cannot trust Clear to actually delete data from the vector. What this means is that if you resize the now “empty” vector you will probably find your data again. To see how that works, add this to the program:

Quotes.Set_Length (Length => 10);
SUIO.Put_Line (Item => Quotes.First_Element);

The output is not as expected, certainly not as desired:

Capacity before clear: 16
No. of quotes before clear: 10
Capacity after clear: 16
No. of quotes after clear: 0
I have an ego the size of a small planet.

Vectors.Clear Example Source

Vectors.Delete

Here's the specification for Delete:

procedure Delete
  (Container : in out Vector;
   Index     : Extended_Index;
   Count     : Count_Type := 1);
 
procedure Delete
  (Container : in out Vector;
   Position  : in out Cursor;
   Count     : Count_Type := 1);

These two procedures delete elements from the vector. As you can see, there's a version using an index and one using a cursor. I'll show both in the same example, as they are very similar in use.

The Count parameter tells Delete how many elements to remove from the vector. If there are fewer elements than Count, all the elements are removed from the vector. If you try to delete an index that does not exist, a CONSTRAINT_ERROR exception is raised.

We need a cursor for the Delete example, so add this to the declarative part of the Quotes program:

Q_Cursor  : Cursor;

And here's the body code for the Delete example:

--  Delete quote no. 2 and 3
Quotes.Delete (Index => 1,
               Count => 2);
 
--  Delete quote no. 5, 6 and 7
Q_Cursor := Quotes.To_Cursor (Index => 4);
Quotes.Delete (Position => Q_Cursor,
               Count    => 3);
 
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;

And this is the output generated by the above:

0 I have an ego the size of a small planet.
1 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
2 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
3 An infinite number of monkeys typing into GNU emacs would never make a good program.
4 And what's the internet without the rick-roll?

If the Count parameter is 0, then nothing is done.

Vectors.Delete Example Source

Vectors.Delete_First

Here's the specification for Delete_First:

procedure Delete_First
  (Container : in out Vector;
   Count     : Count_Type := 1);

It should be quite obvious what the Delete_First procedure does. It deletes the first Count elements from the vector. To see an example, add this to the body of the Quotes program::

IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Delete_First;
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Delete_First (Count => 5);
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.First_Element);

The output from the above program is:

No. of quotes: 10
I have an ego the size of a small planet.
No. of quotes: 9
My name is Linus Torvalds and I am your god.
No. of quotes: 4
Is "I hope you all die a painful death" too strong?

Delete_First completely removes the element(s) from the vector - it doesn't just delete the contents and leave you with one or more empty elements. If you try to delete more elements than the vector contains, a CONSTRAINT_ERROR is raised, after the entire vector has been deleted. If Count is 0, then Delete_First does nothing.

Vectors.Delete_First Example Source

Vectors.Delete_Last

Here's the specification for Delete_Last:

procedure Delete_Last
  (Container : in out Vector;
   Count     : Count_Type := 1);

The observant reader will notice that this procedure is very similar to the above mentioned Delete_First procedure and because they are so similar, the following will be an almost letter-by-letter copy of the Delete_First text.

The Delete_Last procedure does deletes the last Count elements from the vector. To see an example, add this to the body of the Quotes program::

IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.Last_Element);
Quotes.Delete_Last;
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.Last_Element);
Quotes.Delete_Last (Count => 5);
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.Last_Element);

The output from the above program is:

No. of quotes: 10
And what's the internet without the rick-roll?
No. of quotes: 9
I'm always right. This time I'm just even more right than usual.
No. of quotes: 4
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.

Delete_Last completely removed the element(s) from the vector. It also doesn't just delete the contents and leave you with one or more empty elements and the constraints on Count are the same as for Delete_First.

Vectors.Delete_Last Example Source

Moving, swapping and reversing

Sometimes it is necessary to move an entire vector to a new vector, to swap values in a vector or perhaps reorder the vector in reverse direction. The following methods will help with that.

Vectors.Move

Here's the specification for Move:

procedure Move (Target : in out Vector; source : in out Vector);

Moving data from one vector to another can be done using the Move procedure. Add this to the declarative part of the Quotes program:

My_Quotes : Vector;

And this to the body:

IO.Put_Line (Item => "Quotes length:" & Quotes.Length'Img);
IO.Put_Line (Item => "My_Quotes length:" & My_Quotes.Length'Img);
My_Quotes.Move (source => Quotes);
IO.Put_Line (Item => "Quotes new length:" & Quotes.Length'Img);
IO.Put_Line (Item => "My_Quotes new length:" & My_Quotes.Length'Img);

The output is:

Quotes length: 10
My_Quotes length: 0
Quotes new length: 0
My_Quotes new length: 10

Vectors.Move Example Source

Vectors.Reverse_Elements

Here's the specification for Reverse_Elements:

procedure Reverse_Elements (Container : in out Vector);

The Reverse_Elements procedure is yet another one of those procedures that does exactly what its name says: It reorders the elements in the vector in reverse order. Lets try it:

SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Reverse_Elements;
SUIO.Put_Line (Item => Quotes.First_Element);

And the output is:

I have an ego the size of a small planet.
And what's the internet without the rick-roll?

And that's more or less all there is to say about Reverse_Elements.

Vectors.Reverse_Elements Example Source

Vectors.Swap

Here's the specification for Swap:

procedure Swap (Container : in out Vector; I, J : Index_Type);
 
procedure Swap (Container : in out Vector; I, J : Cursor);

The two Swap procedures exchange the values at positions I and J with each other, meaning the value at position I is moved to position J and vice versa. It's the same functionality for both the Index_Type procedure and for the Cursor procedure, so I'll show both in the same example. First we must add two new variables to the declarative part of the Quotes program:

A_Cursor  : Cursor;
B_Cursor  : Cursor;

And the add this to the body:

SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Swap (I => 0,
             J => 9);
SUIO.Put_Line (Item => Quotes.First_Element);
 
A_Cursor := Quotes.To_Cursor (Index => 0);
B_Cursor := Quotes.To_Cursor (Index => 9);
 
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Swap (I => A_Cursor,
             J => B_Cursor);
SUIO.Put_Line (Item => Quotes.First_Element);

The output from this program is:

I have an ego the size of a small planet.
And what's the internet without the rick-roll?
And what's the internet without the rick-roll?
I have an ego the size of a small planet.

Note that a Constraint_Error is raised if one of the indexes that is being swapped is out of range.

Using Swap, it is very easy to add a Bubble Sort to the Quotes program. Please note that I'm just using a Bubble Sort as an example - you should never use something like this to sort your data, as it is very slow and inefficient.

Vectors.Swap Example Source

Information about a vector

A fair deal of information about a given vector can be obtained using the following methods.

Vectors.Capacity

Capacity returns the current capacity of a vector. It's important to understand, that the capacity is not the same as the size of the vector; instead you should think of it as the size to which a vector can grow before new data structures must be allocated to the vector.

Here's the specification for Capacity:

function Capacity (Container : Vector) return Count_Type;

Lets see how it works. Add this to the body of the Quotes program:

IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
Quotes.Clear;
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);

And the output:

Capacity: 16
Capacity: 16

The capacity of the Quotes vector is 16, even though we know there are only 10 quotes in it. Even if we clear the Quotes vector, the capacity stays at 16. Lets expand a bit on the Quotes program to see how the vectors package handles capacity. I'll post the full source here:

with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;      use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Quotes is
   package IO renames Ada.Text_IO;
   package SUIO renames Ada.Text_IO.Unbounded_IO;
   package Quote_Container is new Vectors (Natural, Unbounded_String);
   use Quote_Container;
   Quotes    : Vector;
   Input     : IO.File_Type;
begin
   IO.Put_Line (Item => "Capacity (pre loop):" & Quotes.Capacity'Img);
   IO.Put_Line (Item => "Length (pre loop):" & Quotes.Length'Img);
   IO.Open (File => Input,
            Mode => IO.In_File,
            Name => "quotes.txt");
   while not IO.End_Of_File (File => Input) loop
      Quotes.Append (New_Item => SUIO.Get_Line (File => Input));
      IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
      IO.Put_Line (Item => "Length:" & Quotes.Length'Img);
   end loop;
   IO.Close (Input);
 
   IO.Put_Line (Item => "Capacity (post loop):" & Quotes.Capacity'Img);
   IO.Put_Line (Item => "Length (post loop):" & Quotes.Length'Img);   
end Quotes;

And the output:

Capacity (pre loop): 0
Length (pre loop): 0
Capacity: 1
Length: 1
Capacity: 2
Length: 2
Capacity: 4
Length: 3
Capacity: 4
Length: 4
Capacity: 8
Length: 5
Capacity: 8
Length: 6
Capacity: 8
Length: 7
Capacity: 8
Length: 8
Capacity: 16
Length: 9
Capacity: 16
Length: 10
Capacity (post loop): 16
Length (post loop): 10

Notice how length and capacity don't necessarily match. The capacity of a vector will always be the same or higher than the length. Capacity seems to expand as a power of 2 when we leave it to the system to adjust it, while the length is a linear count of the data actually added.

Vectors.Capacity Example Source

Vectors.Is_Empty

The specification for Is_Empty looks like this:

function Is_Empty (Container : Vector) return Boolean;

With the Is_Empty function we can test if a vector is empty or not. Add this to the Quotes body, to see how it works:

if Quotes.Is_Empty then
   IO.Put_Line (Item => "Empty!");
else
   IO.Put_Line (Item => "Not empty!");
end if;
 
Quotes.Clear;
 
if Quotes.Is_Empty then
   IO.Put_Line (Item => "Empty!");
else
   IO.Put_Line (Item => "Not empty!");
end if;

And the output:

Not empty!
Empty!

Note that empty in this context means a vector with no elements, not a vector with a bunch of empty elements. What the Is_Empty function checks for is the length of the vector. If it's > 0, then Is_Empty returns FALSE and if the length equals 0, then Is_Empty returns TRUE.

Vectors.Is_Empty Example Source

Vectors.Length

Here's the specification for Length:

function Length (Container : Vector) return Count_Type;

We've already encountered the Length function in previous examples, but in case you didn't pay any attention to them, here's the short explanation of what Length does: It returns the length (i.e., the count of elements) of a vector. I bet that was a surprise!

Lets see an example. Add this to the body of the Quotes program:

IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
Quotes.Clear;
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);

And the output:

10
0

And that concludes our look at Length.

Vectors.Length Example Source

Iterating the vector

Iterating a vector can be done using simple loops, but why not use the built-in methods instead? These are not as flexible as a customized loop, but if a simple “array-walk” is all we need, then these methods are the way to go.

Vectors.Iterate

As easy as it is to iterate a vector using either the index or a cursor, there's an even simpler way: The Iterate procedure.

Here's the specification for Iterate:

procedure Iterate
  (Container : Vector;
   Process   : not null access procedure (Position : Cursor));

Iterate enables you to apply the Process procedure to each element in the vector. As with the Query_Element and Update_Element procedures, we must create a procedure to handle the individual elements. For this example we'll go for a small procedure that reverses each quote and then outputs the resulting gibberish to the screen. Add this to the declarative part of the Quotes program:

procedure Reverse_Quote (A_Cursor : Cursor) is
   Org_Quote : constant Unbounded_String := Element (Position => A_Cursor);
   New_Backwards_Quote : Unbounded_String;
begin
   for i in reverse 1 .. Length (Source => Org_Quote) loop
      Append (Source   => New_Backwards_Quote,
              New_Item => Element (Source => Org_Quote,
                                   Index  => i));
   end loop;
   SUIO.Put_Line (Item => New_Backwards_Quote);
end Reverse_Quote;

And next add this to the body:

Quotes.Iterate (Process   => Reverse_Quote'Access);

The output from this program is:

.tenalp llams a fo ezis eht oge na evah I
.dog ruoy ma I dna sdlavroT suniL si eman yM
?srevird ecived nwo rieht etorw dna nem erew nem nehw syad eht rof enip uoy oD
.margorp ruoy xif dluohs dna ,yawyna dewercs er'uoy ,noitatnedni fo slevel 3 naht erom deen uoy fI
.won morf skeew 2 did uoy tahw dnatsrednu ot ekil d'uoy ebyam tub ,tnaillirb er'uoy wonk uoY
.margorp doog a ekam reven dluow scame UNG otni gnipyt syeknom fo rebmun etinifni nA
?gnorts oot "htaed lufniap a eid lla uoy epoh I" sI
.evila dratsab tseikcul eht m'I gnikniht pu ekaw I syad tsoM
.lausu naht thgir erom neve tsuj m'I emit sihT .thgir syawla m'I
?llor-kcir eht tuohtiw tenretni eht s'tahw dnA

Very elegant. Using Iterate for simple manipulations of the elements in a vector is exactly what it's made for. For very complex things it might not be the best solution, as you can't pass any parameter to the Process procedure, which naturally limits it somewhat in use.

Vectors.Iterate Example Source

Vectors.Reverse_Iterate

Here's the specification for Reverse_Iterate:

procedure Reverse_Iterate
  (Container : Vector;
   Process   : not null access procedure (Position : Cursor));

Where Iterate works its way through the vector from first to last, Reverse_Iterate does the opposite. Other than that little difference, the two procedures are exactly the same, so the example I'm going to show on how to use Reverse_Iterate is almost the same as the example for Iterate.

First we add the Reverse_Quote procedure to the declarative part of the Quotes program:

procedure Reverse_Quote (A_Cursor : Cursor) is
   Org_Quote : constant Unbounded_String := Element (Position => A_Cursor);
   Backwards : Unbounded_String;
   Index     : constant Natural := To_Index (Position => A_Cursor);
begin
   for i in reverse 1 .. Length (Source => Org_Quote) loop
      Append (Source   => Backwards,
              New_Item => Element (Source => Org_Quote,
                                   Index  => i));
   end loop;
   IO.Put (Item => Index'Img & " ");
   SUIO.Put (Item => Backwards);
   IO.New_Line;
end Reverse_Quote;
/code>
 
And the we add this line to the body of the program:
 
<code ada>
Quotes.Reverse_Iterate (Process   => Reverse_Quote'Access);

And the output is, as expected:

9 ?llor-kcir eht tuohtiw tenretni eht s'tahw dnA
8 .lausu naht thgir erom neve tsuj m'I emit sihT .thgir syawla m'I
7 .evila dratsab tseikcul eht m'I gnikniht pu ekaw I syad tsoM
6 ?gnorts oot "htaed lufniap a eid lla uoy epoh I" sI
5 .margorp doog a ekam reven dluow scame UNG otni gnipyt syeknom fo rebmun etinifni nA
4 .won morf skeew 2 did uoy tahw dnatsrednu ot ekil d'uoy ebyam tub ,tnaillirb er'uoy wonk uoY
3 .margorp ruoy xif dluohs dna ,yawyna dewercs er'uoy ,noitatnedni fo slevel 3 naht erom deen uoy fI
2 ?srevird ecived nwo rieht etorw dna nem erew nem nehw syad eht rof enip uoy oD
1 .dog ruoy ma I dna sdlavroT suniL si eman yM
0 .tenalp llams a fo ezis eht oge na evah I

Reversed quotes in reversed direction. As with Iterate, you cannot use any parameters with the Process procedure.

Vectors.Reverse_Iterate Example Source

First and last positions in the vector

Whether you're using cursors or the index, sometimes you're going to need to identify the beginning and/or the end of a vector. For this specific job, we have 4 methods available. Lets take a look at them.

Vectors.First_Index

Here's the specification for First_Index:

function First_Index (Container : Vector) return Index_Type;

The benefits of using First_Index (besides clarity) are not readily apparent unless we create a few vectors with different Index_Type parameters. I'll show the full source here, as the changes required are extensive:

with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;      use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Quotes is
   package IO renames Ada.Text_IO;
   package SUIO renames Ada.Text_IO.Unbounded_IO;
   subtype Foo_Range is Natural range 42 .. 52;
   subtype Bar_Range is Integer range -100 .. 100;
   package Quote_Container is new Vectors (Natural, Unbounded_String);
   package Foo_Container is new Vectors (Foo_Range, Unbounded_String);
   package Bar_Container is new Vectors (Bar_Range, Unbounded_String);
   Quotes    : Quote_Container.Vector;
   Foo       : Foo_Container.Vector;
   Bar       : Bar_Container.Vector;
   Input     : IO.File_Type;
   Line      : Unbounded_String;
begin
   IO.Open (File => Input,
            Mode => IO.In_File,
            Name => "quotes.txt");
   while not IO.End_Of_File (File => Input) loop
      Line := SUIO.Get_Line (File => Input);
      Quotes.Append (New_Item => Line);
      Foo.Append (New_Item => Line);
      Bar.Append (New_Item => Line);
   end loop;
   IO.Close (Input);
 
   IO.Put_Line (Item => Quotes.First_Index'Img);
   for i in Quotes.First_Index .. Quotes.Last_Index loop
      IO.Put (Item => i'Img & " ");
      SUIO.Put (Item => Quotes.Element (Index => i));
      IO.New_Line;
   end loop;
   IO.New_Line;
 
   IO.Put_Line (Item => Foo.First_Index'Img);
   for i in Foo.First_Index .. Foo.Last_Index loop
      IO.Put (Item => i'Img & " ");
      SUIO.Put (Item => Foo.Element (Index => i));
      IO.New_Line;
   end loop;
   IO.New_Line;
 
   IO.Put_Line (Item => Bar.First_Index'Img);
   for i in Bar.First_Index .. Bar.Last_Index loop
      IO.Put (Item => i'Img & " ");
      SUIO.Put (Item => Bar.Element (Index => i));
      IO.New_Line;
   end loop;
end Quotes;

The output from this program is:

0
0 I have an ego the size of a small planet.
1 My name is Linus Torvalds and I am your god.
2 Do you pine for the days when men were men and wrote their own device drivers?
3 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
4 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
5 An infinite number of monkeys typing into GNU emacs would never make a good program.
6 Is "I hope you all die a painful death" too strong?
7 Most days I wake up thinking I'm the luckiest bastard alive.
8 I'm always right. This time I'm just even more right than usual.
9 And what's the internet without the rick-roll?

42
42 I have an ego the size of a small planet.
43 My name is Linus Torvalds and I am your god.
44 Do you pine for the days when men were men and wrote their own device drivers?
45 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
46 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
47 An infinite number of monkeys typing into GNU emacs would never make a good program.
48 Is "I hope you all die a painful death" too strong?
49 Most days I wake up thinking I'm the luckiest bastard alive.
50 I'm always right. This time I'm just even more right than usual.
51 And what's the internet without the rick-roll?

-100
-100 I have an ego the size of a small planet.
-99 My name is Linus Torvalds and I am your god.
-98 Do you pine for the days when men were men and wrote their own device drivers?
-97 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
-96 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
-95 An infinite number of monkeys typing into GNU emacs would never make a good program.
-94 Is "I hope you all die a painful death" too strong?
-93 Most days I wake up thinking I'm the luckiest bastard alive.
-92 I'm always right. This time I'm just even more right than usual.
-91 And what's the internet without the rick-roll?

Note how the number preceeding all the quotes is set accordingly to the subtype used as Index_Type for the vector. You should always use the First_Index function instead of the actual numeric value, mainly because it makes it possible to change the Index_Type later on, without having to search all your code for references to a hard-coded numeric value. It is much safer using First_Index.

Vectors.First_Index Example Source

Vectors.Last_Index

Here's the specification for Last_Index:

function Last_Index (Container : Vector) return Extended_Index;

Last_Index is a bit of an oddball, as it returns a value of type Extended_Index, instead of the expected Index_Type. Where the First_Index function returns the first value of the Index_Type (0 for Natural, 1 for Positive and so on), Last_Index returns the No_Index constant if the vector is empty. This No_Index constant is equal to Index_Type'First-1, which of course puts it just outside the range of the Index_Type.

Lets see how it works. Add this to the declarative part of the program:

Empty : Vector;

And this to the body of the program:

IO.Put_Line (Item => Quotes.Last_Index'Img);
IO.Put_Line (Item => Empty.Last_Index'Img);

And the output is:

 9
-1

As you can see, the Last_Index function returns -1 for the Empty vector. We've instantiated this vector with a Natural as the Index_Type, and as you might know, the Natural type has the range 0 .. Integer'Last, and 0 minus 1 equals -1.

To further prove this point, try adding these two if blocks to the body:

if Empty.Last_Index = No_Index then
   IO.Put_Line ("No_Index");
end if;
 
if Empty.Last_Index = Empty.First_Index - 1 then
   IO.Put_Line ("No_Index");
end if;

Now the output is:

 9
-1
No_Index
No_Index

In his excellent book Programming in Ada 2005, John Barnes has this to say about it:

Note that the irritating subtype Extended_Index has to be introduced to cope with end values.

I don't know if I'd call Extended_Index irritating, but it is an oddity that you should be aware of and understand.

Vectors.Last_Index Example Source

Vectors.First

Here's the specification for First:

function First (Container : Vector) return Cursor;

The First function returns a cursor pointing to the first element in a vector. As with First_Index, the primary reason for using First is to avoid having to hard-code the first value of the Index_Type in your program.

Add this to the declarative part of the Quotes program:

Q_Cursor  : Cursor;

And this to the body:

--  This is bad. If we change the Index_Type from Natural to Positive,
--  0 would no longer be the first position in the vector.
Q_Cursor := Quotes.To_Cursor (Index => 0);
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
 
--  This is good. We always get the first position, no matter what 
--  Index_Type we're using.
Q_Cursor := Quotes.First;
SUIO.Put_Line (Item => Element (Position => Q_Cursor));

The output is:

I have an ego the size of a small planet.
I have an ego the size of a small planet.

You should always use either First_Index or First to identify the first position in a vector. You should never ever hard-code the numerical value of the first position, as such a method risks failing if you change the Index_Type for the vector.

Vectors.First Example Source

Vectors.Last

Here's the specification for Last:

function Last (Container : Vector) return Cursor;

Last is much more straightforward than its sibling Last_Index, simply because it doesn't have to handle the “irritating” Extended_Index type. The Last function simply returns a cursor pointing at the last element in a vector, or in case of an empty vector, it returns No_Element. Lets see how it works.

Add this to the declarative part of the Quotes program:

Q_Cursor  : Cursor;

And this to the body:

Q_Cursor := Quotes.Last;
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
 
Quotes.Clear;
 
Q_Cursor := Quotes.Last;
if Q_Cursor /= No_Element then
   SUIO.Put_Line (Item => Element (Position => Q_Cursor));
else
   IO.Put_Line (Item => "No Elements in vector");
end if;

The output from this program is:

And what's the internet without the rick-roll?
No Elements in vector

As you can see, when a vector is empty, the value of the cursor is set to No_Element. If you try to “read” a No_Element cursor, a CONSTRAINT_ERROR is raised.

Vectors.Last Example Source

Next and previous with a cursor

We've already seen how to iterate a vector using Iterate, Reverse_Iterate and regular loops using the index, so I think it's about time we learn how to do it with cursors. For this we have the Next and Previous methods available.

Vectors.Next

Here's the specification for Next:

function Next (Position : Cursor) return Cursor;
 
procedure Next (Position : in out Cursor);

Depending on taste and preference, you can choose between using the Next function or the Next procedure. They both do the same thing: Move the cursor to the next element in the vector. I will show both versions in the following example.

First add this to the declarative part of the program:

A_Cursor  : Cursor;

And the this to the body:

A_Cursor := Quotes.First;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Move to the next element in the vector using the Next procedure.
Next (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Move to the next Element in the vector using the Next function.
A_Cursor := Next (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Move the cursor to the last position, and then call Next again.
--  Note how Next assigns the No_Element value to the cursor.
A_Cursor := Quotes.Last;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
Next (Position => A_Cursor);
if A_Cursor = No_Element then
   IO.Put_Line (Item => "No_Element");
end if;

And the output is:

I have an ego the size of a small planet.
My name is Linus Torvalds and I am your god.
Do you pine for the days when men were men and wrote their own device drivers?
No_Element

Hopefully further explaining is unnecessary.

Vectors.Next Example Source

Vectors.Previous

Here's the specification for Previous:

function Previous (Position : Cursor) return Cursor;
 
procedure Previous (Position : in out Cursor);

Where the Next methods move the cursor forward, the Previous methods move the cursor backwards. I'm confident this is well understood by all, so without further ado, lets see how Previous works in actual code.

Add this to the declarative part:

A_Cursor  : Cursor;

And this to the body:

A_Cursor := Quotes.Last;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Move to the previous element in the vector using the Previous procedure.
Previous (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Move to the previous Element in the vector using the Previous function.
A_Cursor := Previous (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Move the cursor to the first position, and then call Previous again.
--  Note how Previous assigns the No_Element value to the cursor.
A_Cursor := Quotes.First;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
Previous (Position => A_Cursor);
if A_Cursor = No_Element then
   IO.Put_Line (Item => "No_Element");
end if;

And the output:

And what's the internet without the rick-roll?
I'm always right. This time I'm just even more right than usual.
Most days I wake up thinking I'm the luckiest bastard alive.
I have an ego the size of a small planet.
No_Element

There should be no surprises here.

Vectors.Previous Example Source

Finding things

Finding out if a specific element is present in a vector or if a given position contains a value are fairly common tasks when working with vectors, hence a handful of valuable tools have been made available to us.

Vectors.Find_Index

Here's the specification for Find_Index:

function Find_Index
  (Container : Vector;
   Item      : Element_Type;
   Index     : Index_Type := Index_Type'First) return Extended_Index;

The Find_Index function is actually just a wrapper around a simple loop, where each element in the vector is compared to Item. There's nothing “magical” about it. It wont be any faster than a homegrown loop, but it will save you a few lines of code. Find_Index searches the vector from Index and forward. Index defaults to Index_Type'First, in this case 0.

To see how it works, we must first add a few variables to the declarative part of our program:

Needle    : Unbounded_String;
Pos       : Extended_Index;

And then add this to the body:

--  First search. This will fail because we have no FooBar quote.
Needle := To_Unbounded_String ("FooBar");
Pos := Quotes.Find_Index (Item  => Needle);
 
if Pos = No_Index then
   IO.Put_Line ("No_Index");
else
   IO.Put_Line (Pos'Img);
end if;
 
--  Second search. This will succeed, finding the quote at index 7.
Needle := To_Unbounded_String 
  ("Most days I wake up thinking I'm the luckiest bastard alive.");
Pos := Quotes.Find_Index (Item  => Needle);
 
if Pos = No_Index then
   IO.Put_Line ("No_Index");
else
   IO.Put_Line (Pos'Img);
end if;
 
--  Third search. This will fail, because we start the search at index 8.
Needle := To_Unbounded_String 
  ("Most days I wake up thinking I'm the luckiest bastard alive.");
Pos := Quotes.Find_Index (Item  => Needle,
                          Index => 8);
 
if Pos = No_Index then
   IO.Put_Line ("No_Index");
else
   IO.Put_Line (Pos'Img);
end if;

And finally the output:

No_Index
 7
No_Index

There are three interesting things going on in this example:

  1. Pos is of type Extended_Index. This is necessary to cope with the No_Index value.
  2. Using the Index parameter of Find_Index enables us to start the search at a specified index.
  3. The No_Index value is returned if no matching element is found.

Besides those three things, there's really not a whole lot more to say about Find_Index.

Vectors.Find_Index Example Source

Vectors.Find

On the Vectors.Find Example Source page, you will find the Find_Index example from above, modified to work with Find. These two functions are so alike in usage and functionality, that it would be a complete waste of space to do any further explaining. So all you get is the specification and the Vectors.Find Example Source.

Here's the specification for Find:

function Find
  (Container : Vector;
   Item      : Element_Type;
   Position  : Cursor := No_Element) return Cursor;

Vectors.Find Example Source

Vectors.Reverse_Find_Index

Reverse_Find_Index is similar to Find_Index, except it searches from Index and backwards until it reaches the beginning of the vector or finds a match. On the Example Source page you will find the Find_Index example modified to reflect this.

The specification for Reverse_Find_Index is astonishingly similar to Find_Index:

function Reverse_Find_Index
  (Container : Vector;
   Item      : Element_Type;
   Index     : Index_Type := Index_Type'Last) return Extended_Index;

Vectors.Reverse_Find_Index Example Source

Vectors.Reverse_Find

Reverse_Find is similar to Find, except it searches backwards from Position until it reaches the beginning of the vector or finds a match. On the Example Source page you will find the Find example modified to reflect this.

The specification for Reverse_Find is astonishingly similar to Find:

function Reverse_Find
  (Container : Vector;
   Item      : Element_Type;
   Position  : Cursor := No_Element) return Cursor;

Vectors.Reverse_Find Example Source

Vectors.Contains

Here's the specification for Contains:

function Contains
  (Container : Vector;
   Item      : Element_Type) return Boolean;

With Contains you basically get Find_Index with a Boolean return value instead of an Extended_Index, and actually the Contains function is just a thin wrapper around the Find_Index function. If you're working with very large vectors and you know that the element you're looking for is situated near the end of the vector, from a performance point of view, you're probably better off using a homegrown solution. I'll show you one such later.

But first lets try our hand at a little Contains example. Add this to the declaration:

Needle    : Unbounded_String;

And the body:

Needle := To_Unbounded_String ("FooBar");
if Quotes.Contains (Item => Needle) then
   IO.Put_Line (Item => "Found!");   
else
   IO.Put_Line (Item => "Not found!");   
end if;
 
Needle := To_Unbounded_String 
  ("Most days I wake up thinking I'm the luckiest bastard alive.");
if Quotes.Contains (Item => Needle) then
   IO.Put_Line (Item => "Found!");   
else
   IO.Put_Line (Item => "Not found!");   
end if;

And finally the output:

Not found!
Found!

As stated earlier, Contains searches the vector from start to end, but that might not be the best solution. Sometimes you know a piece of data is placed near the end of the vector, if it's there at all. In such a case, a Contains function that searches in reverse is what we're looking for. One such would be easy to do: Vectors.Reverse_Contains Example Source. With Reverse_ editions available for the two Find functions, I find it odd that there isn't a Reverse_Contains function available. Luckily, as the preceding source shows, it is very easy to add it yourself.

Vectors.Contains Example Source

Vectors.Has_Element

Here's the specification for Has_Element:

function Has_Element (Position : Cursor) return Boolean;

Has_Element enables you to check if a cursor identifies an element, or as the ARM say:

Returns True if Position designates an element, and returns False otherwise.

So Has_Element enables you to check if a cursor is still pointing at a valid element in a vector. Remember that a cursor designates both a container and a position in the container. This means that a cursor can point to a position in a vector that no longer exists. Lets see how it's used. Add this to the declaration:

A_Cursor  : Cursor;

And now add this to the body:

--  First check. Cursor is not yet pointing at any specific index
if not Has_Element (Position => A_Cursor) then
   IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
   IO.Put_Line (Item => "No Element!");
end if;
 
--  Point cursor at the last index
A_Cursor := Quotes.To_Cursor (Index => Quotes.Last_Index);
if Has_Element (Position => A_Cursor) then
   IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
   IO.Put_Line (Item => "Element!");
end if;
 
--  Delete the first position. Now the cursor is pointing at a non-
--  existant position, represented by the numeric value -1
Quotes.Delete_First;
if not Has_Element (Position => A_Cursor) then
   IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
   IO.Put_Line (Item => "No Element!");
end if;
 
--  Move the cursor on position backwards, from 9 to 8.
Previous (Position => A_Cursor);
 
--  Now check if the current position designates an element
if Has_Element (Position => A_Cursor) then
   IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
   IO.Put_Line (Item => "Element!");
end if;

And the output is:

-1 No Element!
 9 Element!
-1 No Element!
8 Element!

I've honestly never had any use for Has_Element while working with vectors, but that's probably because I hardly ever use cursors with vectors. With Ada.Containers.Doubly_Linked_Lists it is a different story though, as the only means of navigating a doubly linked list is by using cursors.

Vectors.Has_Element Example Source

From index to cursor, and vice versa

Sometimes it's necessary to convert an index to a cursor or vice-versa. This is done using the aptly named To_Cursor and To_Index functions.

Vectors.To_Cursor

Here's the specification for To_Cursor:

function To_Cursor
  (Container : Vector;
   Index     : Extended_Index) return Cursor;

I will show you how the above function works, even though it should be painfully obvious. First add this to the declarative part of the program:

A_Cursor  : Cursor;

And this to the body:

--  Point the cursor to index 1
A_Cursor := Quotes.To_Cursor (Index => 1);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Point the cursor to index 5
A_Cursor := Quotes.To_Cursor (Index => 5);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
 
--  Point the cursor to a non-existant index
A_Cursor := Quotes.To_Cursor (Index => 42);
if A_Cursor = No_Element then
   IO.Put_Line (Item => "No_Element");
end if;

The resulting output from this program is:

My name is Linus Torvalds and I am your god.
An infinite number of monkeys typing into GNU emacs would never make a good program.
No_Element

The important thing to take away from this example, is the No_Element part. If the given Index is out of range, then No_Element is returned.

Vectors.To_Cursor Example Source

Vectors.To_Index

Here's the specification for To_Index:

function To_Index (Position : Cursor) return Extended_Index;

Above we took a quick look at how to convert Index_Type to a cursor; now we'll do the opposite: Going from a cursor to Index_Type. This is done using the To_Index function.

You can keep the declarative part from the To_Cursor example, but replace the body with the following:

--  Point the cursor to the first index and output the index value
A_Cursor := Quotes.To_Cursor (Index => Quotes.First_Index);
IO.Put_Line (Item => To_Index (Position => A_Cursor)'Img);
 
--  Point the cursor to the 5th. index and output the index value
A_Cursor := Quotes.To_Cursor (Index => 5);
IO.Put_Line (Item => To_Index (Position => A_Cursor)'Img);
 
--  Point the cursor to a non-existant index, convert to an index and 
--  check for No_Index
A_Cursor := Quotes.To_Cursor (Index => 42);
if To_Index (Position => A_Cursor) = No_Index then
   IO.Put_Line (Item => "No_Index");
end if;

This is not exactly rocket science, but it is worth noting that No_Element is “transformed” into No_Index when To_Index is called on a cursor which point at No_Element.

Vectors.To_Index Example Source

Vectors.Generic_Sorting

The vector package also contains facilities to sort the vector, in the form of the generic package Generic_Sorting, so there's no need to mess around with home-grown sorting routines. The sorting is done in ascending order. The specification for Generic_Sorting looks like this:

generic
   with function "<" (Left, Right : Element_Type) return Boolean is <>;
package Generic_Sorting is
 
   function Is_Sorted (Container : Vector) return Boolean;
 
   procedure Sort (Container : in out Vector);
 
   procedure Merge (Target : in out Vector; Source : in out Vector);
 
end Generic_Sorting;

In order to use these procedures, we will first need to instantiate the Generic_Sorting package. This is done the obvious way:

package A_Sorter is new Generic_Sorting;

Simply add the above to the declarative part of the Quotes program, and you're good to go. In the following we will look at how to use the generic procedures to sort a vector.

Generic_Sorting.Sort

Here's the specification for Sort:

procedure Sort (Container : in out Vector);

Other than instantiating the Generic_Sorting generic (see Vectors.Generic_Sorting), we needn't specify anything else in the declarative part of the Quotes program. Instead we'll jump straight to the body, where we add these few lines to see how sorting works:

A_Sorter.Sort (Container => Quotes);
 
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => Quotes.Element (Index => i));
end loop;

The output from the program is:

An infinite number of monkeys typing into GNU emacs would never make a good program.
And what's the internet without the rick-roll?
Do you pine for the days when men were men and wrote their own device drivers?
I have an ego the size of a small planet.
I'm always right. This time I'm just even more right than usual.
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
Is "I hope you all die a painful death" too strong?
Most days I wake up thinking I'm the luckiest bastard alive.
My name is Linus Torvalds and I am your god.
You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.

And there you have it: A perfectly sorted quotes.txt file. It just doesn't get any simpler than that.

Vectors.Generic_Sorting.Sort Example Source

Generic_Sorting.Is_Sorted

Here's the specification for Is_Sorted:

function Is_Sorted (Container : Vector) return Boolean;

Figuring out if a vector has or has not been sorted is done using this little gem of a function. It will return Boolean TRUE if sorting has been done, and Boolean FALSE if not. This is how it works:

if not A_Sorter.Is_Sorted (Container => Quotes) then
    IO.Put_Line ("We have not yet sorted the Quotes vector");
end if;
 
A_Sorter.Sort (Container => Quotes);
 
if A_Sorter.Is_Sorted (Container => Quotes) then
   IO.Put_Line ("Now we have sorted the Quotes vector");
end if;

The output from the program is:

We have no yet sorted the Quotes vector
Now we have sorted the Quotes vector

I'm certain the above result came as no surprise to you.

But what happens if a vector is sorted and then a new element is added? Will Is_Sorted still return Boolean TRUE? Add the following code to the above, and lets see:

Quotes.Append (New_Item => To_Unbounded_String ("New stuff!"));
 
if not A_Sorter.Is_Sorted (Container => Quotes) then
   IO.Put_Line ("The vector is no longer sorted");
end if;

Now the output looks like this:

We have no yet sorted the Quotes vector
Now we have sorted the Quotes vector
The vector is no longer sorted

As could be expected, the Is_Sorted function will return Boolean FALSE if changes are made to the vector, but this is not actually done using an internal “flag” of some sorts. No, the FALSE value is found by simply iterating over the entire vector while checking if position i + 1 is lesser than position i. If this is the case, then Is_Sorted return Boolean FALSE.

In case of large vectors, this is not very efficient. Given that, you should of course only use Is_Sorted if it's absolutely necessary.

Vectors.Generic_Sorting.Is_Sorted Example Source

Generic_Sorting.Merge

Here's the specification for Merge:

procedure Merge (Target : in out Vector; Source : in out Vector);

Merge is special. It merges Target with Source and leaves Source empty. What is special is that it doesn't sort the resulting vector, which one might expect, Merge being a part of the Generic_Sorting. If you want Merge to result in a sorted vector, then you must have sorted both Target and Source prior to the Merge call.

Lets see how it works. Add this to the declarative part of the Quotes program:

Foo_Bar   : Vector;
package A_Sorter is new Generic_Sorting;

And this to the body:

Foo_Bar.Append (New_Item => To_Unbounded_String ("Foo"));
Foo_Bar.Append (New_Item => To_Unbounded_String ("Bar"));
 
A_Sorter.Sort (Container => Foo_Bar);
A_Sorter.Sort (Container => Quotes);
 
A_Sorter.Merge (Target => Foo_Bar,
                Source => Quotes);
 
for i in Foo_Bar.First_Index .. Foo_Bar.Last_Index loop
   SUIO.Put_Line (Item => Foo_Bar.Element (Index => i));
end loop;
 
IO.Put_Line (Item => "Length of Quotes:" & Quotes.Length'Img);

The output from this program is:

An infinite number of monkeys typing into GNU emacs would never make a good program.
And what's the internet without the rick-roll?
Bar
Do you pine for the days when men were men and wrote their own device drivers?
Foo
I have an ego the size of a small planet.
I'm always right. This time I'm just even more right than usual.
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
Is "I hope you all die a painful death" too strong?
Most days I wake up thinking I'm the luckiest bastard alive.
My name is Linus Torvalds and I am your god.
You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
Length of Quotes: 0

Note how the contents of the two vectors are merged into the Target vector and also that the Quotes vector is empty after the merge. Lets delete the A_Sorter.Sort (Container ⇒ Foo_Bar); line and see what happens:

An infinite number of monkeys typing into GNU emacs would never make a good program.
And what's the internet without the rick-roll?
Foo
Bar
Do you pine for the days when men were men and wrote their own device drivers?
I have an ego the size of a small planet.
I'm always right. This time I'm just even more right than usual.
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
Is "I hope you all die a painful death" too strong?
Most days I wake up thinking I'm the luckiest bastard alive.
My name is Linus Torvalds and I am your god.
You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
Length of Quotes: 0

As you can see, the Target vector is no longer sorted.

When using Merge it is very important to remember this: Both the Target and Source vectors must be sorted prior to the Merge call, else you'll end up with an un-sorted vector as the result. (If that is all you want, then it's probably faster to use one of the following procedures: Append, Insert, Prepend. Or you could simply use the & function to get the job done.)

Vectors.Generic_Sorting.Merge Example Source

Concatenate using the & operator

As seen earlier, it is possible to join two vectors/vector elements using a number of different techniques, such as append, prepend, insert and merge, but there's also the ”&” operator. The specification for the ”&” functions look like this:

function "&" (Left, Right : Vector) return Vector;
 
function "&" (Left : Vector; Right : Element_Type) return Vector;
 
function "&" (Left : Element_Type; Right : Vector) return Vector;
 
function "&" (Left, Right : Element_Type) return Vector;

You can concatenate one vector to another, an element to a vector, a vector to an element and finally two elements. All 4 functions return a vector.

Lets see how it works. Add this to the declarative part of the program:

Foo       : Vector;
Bar       : Vector;

And this to the body:

--  This is similar to: 
--  Foo.Append (To_Unbounded_String ("Foo 1"));
Foo := Foo & To_Unbounded_String ("Foo 1");
for i in Foo.First_Index .. Foo.Last_Index loop
   SUIO.Put_Line (Item => Foo.Element (Index => i));
end loop;
 
IO.New_Line;
 
--  This is similar to:
--  Quotes.Append (Foo);
Quotes := Quotes & Foo;
for i in Quotes.First_Index .. Quotes.Last_Index loop
   SUIO.Put_Line (Item => Quotes.Element (Index => i));
end loop;   
 
IO.New_Line;
 
--  This is similar to:
--  Bar.Append (To_Unbounded_String ("Bar 1"));
--  Bar.Append (To_Unbounded_String ("Bar 2"));
Bar := To_Unbounded_String ("Bar 1") & To_Unbounded_String ("Bar 2");
for i in Bar.First_Index .. Bar.Last_Index loop
   SUIO.Put_Line (Item => Bar.Element (Index => i));
end loop;
 
IO.New_Line;
 
--  This is similar to:
--  Foo.Prepend (To_Unbounded_String ("Foo 0"));
Foo := To_Unbounded_String ("Foo 0") & Foo;
for i in Foo.First_Index .. Foo.Last_Index loop
   SUIO.Put_Line (Item => Foo.Element (Index => i));
end loop;

And the output is:

Foo 1

I have an ego the size of a small planet.
My name is Linus Torvalds and I am your god.
Do you pine for the days when men were men and wrote their own device drivers?
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
An infinite number of monkeys typing into GNU emacs would never make a good program.
Is "I hope you all die a painful death" too strong?
Most days I wake up thinking I'm the luckiest bastard alive.
I'm always right. This time I'm just even more right than usual.
And what's the internet without the rick-roll?
Foo 1

Bar 1
Bar 2

Foo 0
Foo 1

While the ”&” operator works exactly as expected, there is one caveat, which John Barnes explains in his Ada 2005 book:

The result is the same but using ”&” is less efficient because of the extra copying involved.

Lets try a small example, where we compare append and the & operator. First I'll show the ”&” example in full:

with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Somenumbers is
   package Container is new Vectors (Natural, Natural);
   use Container;
   Numbers : Vector;
begin
   for i in 0 .. 10000 loop
      Numbers := Numbers & i;
   end loop; 
 
   Numbers := Numbers & Numbers;
 
   for i in 0 .. 10000 loop
      Numbers := i & Numbers;
   end loop;
end Somenumbers;

Timing this on my computer, yields these results (average of 10 runs):

real    0m1.183s
user    0m1.028s
sys     0m0.156s

Now with append and prepend:

with Ada.Containers.Vectors;     use Ada.Containers;
 
procedure Somenumbers is
   package Container is new Vectors (Natural, Natural);
   use Container;
   Numbers : Vector;
begin
   for i in 0 .. 10000 loop
      Numbers.Append (New_Item => i);
   end loop; 
 
   Numbers.Append (New_Item => Numbers);
 
   for i in 0 .. 10000 loop
      Numbers.Prepend (New_Item => i);
   end loop;
end Somenumbers;

Timing this yields these results (average of 10 runs):

real    0m0.247s
user    0m0.244s
sys     0m0.002s

If you care about performance, it is probably best to avoid using the ”&” functions. As the simple benchmark shows, there are faster ways to concatenate vectors and/or vector elements than using ”&”.

The & operator Example Source

Equality

Checking for equality between two vectors is done using the ”=” function. Two vectors are only considered equal if both vectors are equal in length and the elements of each index are completely the same.

To see how it works, we must add this to the declarative part of the program:

Foo : Vector;

And this to the body:

if Quotes = Foo then
   IO.Put_Line (Item => "Quotes and Foo are equal");
else
   IO.Put_Line (Item => "Quotes and Foo are NOT equal");
end if; 
 
Foo.Append (New_Item => Quotes);
 
if Quotes = Foo then
   IO.Put_Line (Item => "Quotes and Foo are equal");
else
   IO.Put_Line (Item => "Quotes and Foo are NOT equal");
end if; 

And the result is:

Quotes and Foo are NOT equal
Quotes and Foo are equal

This is of course as expected.

Equality Example Source


Navigation