Ada Programming/Libraries/Ada.Containers.Vectors
This language feature is only available from Ada 2005 on.
Ada.Containers.Vectors is a unit of the Predefined Language Environment since Ada 2005.
This generic package supplies a container (Vector) which can store any definite subtype in a consecutive list. This makes an Ada.Containers.Vectors.Vector similar to an array — however a Vector can change size after it has been declared, which an array can't do. For that reason, vectors are also known as dynamic arrays or resizable arrays.
Introduction
[edit | edit source]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 package works.
But first: What is a vector? Here's what the reference manual has 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
[edit | edit source]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
[edit | edit source]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
[edit | edit source]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
[edit | edit source]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
[edit | edit source]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.Set_Length
[edit | edit source]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 vector's 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 are 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.To_Vector
[edit | edit source]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.
Inserting data and/or empty elements
[edit | edit source]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
[edit | edit source]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.Prepend
[edit | edit source]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.Insert
[edit | edit source]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:
- They make generic algorithms possible
- 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_Space
[edit | edit source]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.
Reading and querying the vector
[edit | edit source]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
[edit | edit source]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.Query_Element
[edit | edit source]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
.
Alteration - how to "poke" the elements
[edit | edit source]Changing the data in a vector is a common task. Here are two methods to accomplish this.
Vectors.Update_Element
[edit | edit source]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.Replace_Element
[edit | edit source]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.
Deleting and clearing
[edit | edit source]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
[edit | edit source]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.Delete
[edit | edit source]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_First
[edit | edit source]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_Last
[edit | edit source]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
.
Moving, swapping and reversing
[edit | edit source]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
[edit | edit source]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.Reverse_Elements
[edit | edit source]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.Swap
[edit | edit source]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.
Information about a vector
[edit | edit source]A fair deal of information about a given vector can be obtained using the following methods.
Vectors.Capacity
[edit | edit source]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.Is_Empty
[edit | edit source]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.Length
[edit | edit source]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
.
Iterating the vector
[edit | edit source]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
[edit | edit source]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.Reverse_Iterate
[edit | edit source]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:
<source lang="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.
First and last positions in the vector
[edit | edit source]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
[edit | edit source]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.Last_Index
[edit | edit source]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
.
Let's 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 (ISBN 0321340787), John Barnes has this to say about it:
Note that the irritating subtypeExtended_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.First
[edit | edit source]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.Last
[edit | edit source]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.
Next and previous with a cursor
[edit | edit source]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
[edit | edit source]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.Previous
[edit | edit source]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.
Finding things
[edit | edit source]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
[edit | edit source]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:
Pos
is of typeExtended_Index
. This is necessary to cope with theNo_Index
value.- Using the
Index
parameter ofFind_Index
enables us to start the search at a specified index. - 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
[edit | edit source]On this example, 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 example.
Here's the specification for Find
:
function Find
(Container : Vector;
Item : Element_Type;
Position : Cursor := No_Element) return Cursor;
Vectors.Reverse_Find_Index
[edit | edit source]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
[edit | edit source]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.Contains
[edit | edit source]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:
. 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.Has_Element
[edit | edit source]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.
From index to cursor, and vice versa
[edit | edit source]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
[edit | edit source]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_Index
[edit | edit source]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.Generic_Sorting
[edit | edit source]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
[edit | edit source]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.
Generic_Sorting.Is_Sorted
[edit | edit source]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.
Generic_Sorting.Merge
[edit | edit source]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.)
Concatenate using the & operator
[edit | edit source]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
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 "&".
Equality
[edit | edit source]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.
Specification
[edit | edit source]-- Standard Ada library specification -- Copyright (c) 2003-2018 Maxim Reznik <reznikmm@gmail.com> -- Copyright (c) 2004-2016 AXE Consultants -- Copyright (c) 2004, 2005, 2006 Ada-Europe -- Copyright (c) 2000 The MITRE Corporation, Inc. -- Copyright (c) 1992, 1993, 1994, 1995 Intermetrics, Inc. -- SPDX-License-Identifier: BSD-3-Clause and LicenseRef-AdaReferenceManual -- -------------------------------------------------------------------------generic
type
Index_Typeis
range
<>;type
Element_Typeis
private
;with
function
"=" (Left :in
Element_Type; Right :in
Element_Type)return
Booleanis
<>;package
Ada.Containers.Vectorsis
pragma
Preelaborate (Vectors);subtype
Extended_Indexis
Index_Type'Baserange
Index_Type'First - 1 .. Index_Type'Min (Index_Type'Base'Last - 1, Index_Type'Last) + 1; No_Index :constant
Extended_Index := Extended_Index'First;type
Vectoris
tagged
private
;pragma
Preelaborable_Initialization (Vector);type
Cursoris
private
;pragma
Preelaborable_Initialization (Cursor); Empty_Vector :constant
Vector; No_Element :constant
Cursor;function
"=" (Left :in
Vector; Right :in
Vector)return
Boolean;function
To_Vector (Length :in
Count_Type)return
Vector;function
To_Vector (New_Item :in
Element_Type; Length :in
Count_Type)return
Vector;function
"&" (Left :in
Vector; Right :in
Vector)return
Vector;function
"&" (Left :in
Vector; Right :in
Element_Type)return
Vector;function
"&" (Left :in
Element_Type; Right :in
Vector)return
Vector;function
"&" (Left :in
Element_Type; Right :in
Element_Type)return
Vector;function
Capacity (Container :in
Vector)return
Count_Type;procedure
Reserve_Capacity (Container :in
out
Vector; Capacity :in
Count_Type);function
Length (Container :in
Vector)return
Count_Type;procedure
Set_Length (Container :in
out
Vector; Length :in
Count_Type);function
Is_Empty (Container :in
Vector)return
Boolean;procedure
Clear (Container :in
out
Vector);function
To_Cursor (Container : Vector; Index : Extended_Index)return
Cursor;function
To_Index (Position :in
Cursor)return
Extended_Index;function
Element (Container :in
Vector; Index :in
Index_Type)return
Element_Type;function
Element (Position :in
Cursor)return
Element_Type;procedure
Replace_Element (Container :in
out
Vector; Index :in
Index_Type; New_Item :in
Element_Type);procedure
Replace_Element (Container :in
out
Vector; Position :in
Cursor; New_item :in
Element_Type);procedure
Query_Element (Container :in
Vector; Index :in
Index_Type; Process :not
null
access
procedure
(Element :in
Element_Type));procedure
Query_Element (Position :in
Cursor; Process :not
null
access
procedure
(Element :in
Element_Type));procedure
Update_Element (Container :in
out
Vector; Index :in
Index_Type; Process :not
null
access
procedure
(Element :in
out
Element_Type));procedure
Update_Element (Container :in
out
Vector; Position :in
Cursor; Process :not
null
access
procedure
(Element :in
out
Element_Type));procedure
Move (Target :in
out
Vector; Source :in
out
Vector);procedure
Insert (Container :in
out
Vector; Before :in
Extended_Index; New_Item :in
Vector);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Vector);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Vector; Position :out
Cursor);procedure
Insert (Container :in
out
Vector; Before :in
Extended_Index; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Element_Type; Position :out
Cursor; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Extended_Index; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; Position :out
Cursor; Count :in
Count_Type := 1);procedure
Prepend (Container :in
out
Vector; New_Item :in
Vector);procedure
Prepend (Container :in
out
Vector; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Append (Container :in
out
Vector; New_Item :in
Vector);procedure
Append (Container :in
out
Vector; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Insert_Space (Container :in
out
Vector; Before :in
Extended_Index; Count :in
Count_Type := 1);procedure
Insert_Space (Container :in
out
Vector; Before :in
Cursor; Position :out
Cursor; Count :in
Count_Type := 1);procedure
Delete (Container :in
out
Vector; Index :in
Extended_Index; Count :in
Count_Type := 1);procedure
Delete (Container :in
out
Vector; Position :in
out
Cursor; Count :in
Count_Type := 1);procedure
Delete_First (Container :in
out
Vector; Count :in
Count_Type := 1);procedure
Delete_Last (Container :in
out
Vector; Count :in
Count_Type := 1);procedure
Reverse_Elements (Container :in
out
Vector);procedure
Swap (Container :in
out
Vector; I :in
Index_Type; J :in
Index_Type);procedure
Swap (Container :in
out
Vector; I :in
Cursor; J :in
Cursor);function
First_Index (Container :in
Vector)return
Index_Type;function
First (Container :in
Vector)return
Cursor;function
First_Element (Container :in
Vector)return
Element_Type;function
Last_Index (Container :in
Vector)return
Extended_Index;function
Last (Container :in
Vector)return
Cursor;function
Last_Element (Container :in
Vector)return
Element_Type;function
Next (Position :in
Cursor)return
Cursor;procedure
Next (Position :in
out
Cursor);function
Previous (Position :in
Cursor)return
Cursor;procedure
Previous (Position :in
out
Cursor);function
Find_Index (Container :in
Vector; Item :in
Element_Type; Index :in
Index_Type := Index_Type'First)return
Extended_Index;function
Find (Container :in
Vector; Item :in
Element_Type; Position :in
Cursor := No_Element)return
Cursor;function
Reverse_Find_Index (Container :in
Vector; Item :in
Element_Type; Index :in
Index_Type := Index_Type'Last)return
Extended_Index;function
Reverse_Find (Container :in
Vector; Item :in
Element_Type; Position :in
Cursor := No_Element)return
Cursor;function
Contains (Container :in
Vector; Item :in
Element_Type)return
Boolean;function
Has_Element (Position :in
Cursor)return
Boolean;procedure
Iterate (Container :in
Vector; Process :not
null
access
procedure
(Position :in
Cursor));procedure
Reverse_Iterate (Container :in
Vector; Process :not
null
access
procedure
(Position :in
Cursor));generic
with
function
"<" (Left :in
Element_Type; Right :in
Element_Type)return
Booleanis
<>;package
Generic_Sortingis
function
Is_Sorted (Container :in
Vector)return
Boolean;procedure
Sort (Container :in
out
Vector);procedure
Merge (Target :in
out
Vector; Source :in
out
Vector);end
Generic_Sorting;private
type
Vectoris
tagged
null
record
; Empty_Vector :constant
Vector := (null
record
);type
Cursoris
null
record
; No_Element :constant
Cursor := (null
record
);end
Ada.Containers.Vectors;
See also
[edit | edit source]Wikibook
[edit | edit source]- Ada Programming
- Ada Programming/Libraries
- Ada Programming/Libraries/Ada
- Ada Programming/Libraries/Ada.Containers
External examples
[edit source]- Search for examples of
Ada.Containers.Vectors
in: Rosetta Code, GitHub (gists), any Alire crate or this Wikibook. - Search for posts related to
Ada.Containers.Vectors
in: Stack Overflow, comp.lang.ada or any Ada related page.
Ada 2005 Rationale
[edit | edit source]Ada Reference Manual
[edit | edit source]Ada 2005
[edit | edit source]Ada 2012
[edit | edit source]Open-Source Implementations
[edit | edit source]FSF GNAT
- Specification: a-convec.ads
- Body: a-convec.adb
drake
- Specification: containers/a-convec.ads
- Body: containers/a-convec.adb