Ada Programming/Containers

What follows is a simple demo of some of the container types. It does not cover everything, but should get you started.

First Example: Maps
The program below prints greetings to the world in a number of human languages. The greetings are stored in a table, or hashed map. The map associates every greeting (a value) with a language code (a key). That is, you can use language codes as keys to find greeting values in the table.

The elements in the map are constant strings of international characters, or really, pointers to such constant strings. A package Regional is used to set up both the language IDs and an instance of.

;  Ada.Containers; Regional Language_ID (DE, EL, EN, ES, FR, NL); Hello_Text   Wide_String; ID_Hashed (id: Language_ID) Hash_Type; Phrases  Ada.Containers.Hashed_Maps (Key_Type => Language_ID,      Element_Type => Hello_Text,       Hash => ID_Hashed,       Equivalent_Keys => "="); Regional;

Here is the program, details will be explained later.

Regional; Regional; ; Ada; Hello_World_Extended greetings: Phrases.Map; Phrases.Insert(greetings,                  Key => EN,                   New_Item =>  Wide_String'("Hello, World!")); greetings.Insert(DE, Wide_String'("Hallo, Welt!")); greetings.Insert(NL, Wide_String'("Hallo, Wereld!")); greetings.Insert(ES, Wide_String'("¡Hola mundo!")); greetings.Insert(FR, Wide_String'("Bonjour, Monde!")); greetings.Insert(EL, Wide_String'("Γεια σου κόσμε")); Phrases; speaker: Cursor := First(greetings); Has_Element(speaker) Wide_Text_IO.Put_Line( Element(speaker). ); Next(speaker); ;   ;  Hello_World_Extended;

The first of the Insert statements is written in an Ada 95 style:

Phrases.Insert(greetings,                 Key => EN,                  New_Item =>  Wide_String'("Hello, World!"));

The next insertions use so called distinguished receiver notation which you can use in Ada 2005. (It's O-O parlance. While the Insert call involves all of: a Container object (greetings), a Key object (EN), and a New_Item object ( Wide_String'("Hello, World!")), the Container object is distinguished from the others in that the Insert call provides it (and only it) with the other objects. In this case the Container object will be modified by the call, using arguments named Key and New_Item for the modification.)

greetings.Insert(ES, Wide_String'("¡Hola mundo!"));

After the table is set up, the program goes on to print all the greetings contained in the table. It does so employing a cursor that runs along the elements in the table in some order. The typical scheme is to obtain a cursor, here using First, and then to iterate the following calls:


 * 1) Has_Element, for checking whether the cursor is at an element
 * 2) Element, to get the element and
 * 3) Next, to move the cursor to another element

When there is no more element left, the cursor will have the special value No_Element. Actually, this is an iteration scheme that can be used with all containers in child packages of.

A slight variation: picking an element
The next program shows how to pick a value from the map, given a key. Actually, you will provide the key. The program is like the previous one, except that it doesn't just print all the elements in the map, but picks one based on a Language_ID value that it reads from standard input.

Regional; Regional; Ada.Wide_Text_IO; Ada; Hello_World_Pick ... as before ... Phrases; Lang_IO  Wide_Text_IO.Enumeration_IO(Language_ID); lang: Language_ID; Lang_IO.Get(lang); Wide_Text_IO.Put_Line( greetings.Element(lang). ); ; Hello_World_Pick;

This time the Element function consumes a Key (lang) not a Cursor. Actually, it consumes two values, the other value being <tt>greetings</tt>, in distinguished receiver notation.

Second Example: Vectors and Maps
Let's take bean counting literally. Red beans, green beans, and white beans. (Yes, white beans really do exist.) Your job will be to collect a number of beans, weigh them, and then determine the average weight of red, green, and white beans, respectively. Here is one approach.

Again, we need a package, this time for storing vegetable related information. Introducing the <tt>Beans</tt> package (the Grams type doesn't belong in a vegetable package, but it's there to keep things simple):

Beans Bean_Color R G W     Grams   001  7 Bean kind Bean_Color weight Grams Bean_Count Positive  1  1_000 Bean_Vecs  AdaContainersVectors Element_Type => Bean Index_Type => Bean_Count Beans

The <tt>Vectors</tt> instance offers a data structure similar to an array that can change its size at run time. It is called <tt>Vector</tt>. Each bean that is read will be appended to a <tt>Bean_Vecs.Vector</tt> object.

The following program first calls <tt>read_input</tt> to fill a buffer with beans. Next, it calls a function that computes the average weight of beans having the same color. This function:

Beans   Beans average_weight buffer Bean_VecsVector desired_color Bean_Color Grams

Then the average value is printed for beans of each color and the program stops.

Beans average_weight bean_counting Beans Ada buffer Bean_VecsVector read_inputbuf  Bean_VecsVector read_inputbuffer kind Bean_Color Wide_Text_IOPut_Line Bean_ColorWide_Imagekind " ø =" GramsWide_Image average_weightbuffer kind bean_counting

All container operations take place in function <tt>average_weight</tt>. To find the mean weight of beans of the same color, the function is looking at all beans in order. If a bean has the right color, <tt>average_weight</tt> adds its weight to the total weight, and increases the number of beans counted by 1.

The computation visits all beans. The iteration that is necessary for going from one bean to the next and then performing the above steps is best left to the <tt>Iterate</tt> procedure which is part of all container packages. To do so, wrap the above steps inside some procedure and pass this procedure to <tt>Iterate</tt>. The effect is that <tt>Iterate</tt> calls your procedure for each element in the vector, passing a cursor value to your procedure, one for each element.

Having the container machinery do the iteration can also be faster than moving and checking the cursor yourself, as was done in the <tt>Hello_World_Extended</tt> example.

Beans  BeansBean_Vecs average_weight buffer Bean_VecsVector desired_color Bean_Color Grams total Grams := 00 number Natural := 0 accumulatec Cursor Elementckind = desired_color number := number 1 total := total Elementcweight accumulate Iteratebuffer accumulateAccess number 0 total number 00 average_weight

This approach is straightforward. However, imagine larger vectors. <tt>average_weight</tt> will visit all elements repeatedly for each color. If there are M colors and N beans, <tt>average_weight</tt> will be called M * N times, and with each new color, N more calls are necessary. A possible alternative is to collect all information about a bean once it is visited. However, this will likely need more variables, and you will have to find a way to return more than one result (one average for each color), etc. Try it!

A different approach might be better. One is to copy beans of different colors to separate vector objects. (Remembering Cinderella.) Then <tt>average_weight</tt> must visit each element only one time. The following procedure does this, using a new type from <tt>Beans</tt>, called <tt>Bean_Pots</tt>.

...    Bean_Pots  Bean_Color  Bean_VecsVector ...

Note how this plain array associates colors with Vectors. The procedure for getting the beans into the right bowls uses the bean color as array index for finding the right bowl (vector).

gather_into_potsbuffer Bean_VecsVector pots  Bean_Pots Bean_Vecs put_into_right_potc Cursor AppendpotsElementckind Elementc put_into_right_pot Iteratebuffer put_into_right_potAccess gather_into_pots

Everything is in place now.

Beans average_weight gather_into_pots AdaWide_Text_IO bean_counting Beans Ada buffer Bean_VecsVector bowls Bean_Pots read_inputbuf  Bean_VecsVector read_inputbuffer gather_into_potsbuffer bowls color Bean_Color Wide_Text_IOPut_Line Bean_ColorWide_Imagecolor & " ø =" & GramsWide_Imageaverage_weightbowlscolor color bean_counting

As a side effect of having chosen one vector per color, we can determine the number of beans in each vector by calling the <tt>Length</tt> function. But <tt>average_weight</tt>, too, computes the number of elements in the vector. Hence, a summing function might replace <tt>average_weight</tt> here.

All In Just One Map!
The following program first calls <tt>read_input</tt> to fill a buffer with beans. Then, information about these beans is stored in a table, mapping bean properties to numbers of occurrence. The processing that starts at <tt>Iterate</tt> uses chained procedure calls typical of the iteration mechanism.

The Beans package in this example instantiates another generic library unit,. Where the require a hashing function, require a comparison function. We provide one, <tt>"<"</tt>, which sorts beans first by color, then by weight. It will automatically be associated with the corresponding generic formal function, as its name, <tt>"<"</tt>, matches that of the generic formal function, <tt>"<"</tt>.

...    "<"a b Bean  Boolean Bean_Statistics AdaContainersOrdered_Maps Element_Type => Natural Key_Type => Bean ...

Where the previous examples have ed subprograms, this variation on <tt>bean_counting</tt> packs them all as local subprograms.

Beans AdaWide_Text_IO bean_counting Beans Ada buffer Bean_VecsVector stats_cw Bean_StatisticsMap read_inputbuf  Bean_VecsVector add_bean_infospecimen Bean add_bean_infospecimen Bean one_moreb Bean n   Natural n := n 1 one_more c Bean_StatisticsCursor inserted Boolean stats_cwInsertspecimen 0 c inserted Bean_StatisticsUpdate_Elementc one_moreAccess add_bean_info read_inputbuffer Bean_Vecs count_beanc Cursor add_bean_infoElementc count_bean Iteratebuffer count_beanAccess Bean_Statistics q_sum Grams q_count Natural q_statslo hi Cursor q_statslo hi Cursor k Cursor := lo         q_count := 0 q_sum := 00 k = hi            q_count := q_count  Elementk q_sum := q_sum Keykweight  Elementk Nextk q_stats assert Is_Emptystats_cw "container is empty" lower upper Cursor := Firststats_cw Wide_Text_IOPut_Line"Summary:" color Bean_Color lower := upper color = Bean_ColorLast upper := No_Element upper := Ceilingstats_cw BeanBean_ColorSucccolor 00 q_statslower upper q_count 0 Wide_Text_IOPut_Line Bean_ColorWide_Imagecolor & " group:" & " ø =" & GramsWide_Imageq_sum  q_count & ", # =" & NaturalWide_Imageq_count & ", Σ =" & GramsWide_Imageq_sum bean_counting

Like in the greetings example, you can pick values from the table. This time the values tell the number of occurrences of beans with certain properties. The <tt>stats_cw</tt> table is ordered by key, that is by bean properties. Given particular properties, you can use the <tt>Floor</tt> and <tt>Ceiling</tt> functions to approximate the bean in the table that most closely matches the desired properties.

It is now easy to print a histogram showing the frequency with which each kind of bean has occurred. If N is the number of beans of a kind, then print N characters on a line, or draw a graphical bar of length N, etc. A histogram showing the number of beans per color can be drawn after computing the sum of beans of this color, using groups like in the previous example. You can delete beans of a color from the table using the same technique.

Finally, think of marshalling the beans in order starting at the least frequently occurring kind. That is, construct a vector appending first beans that have occurred just once, followed by beans that have occurred twice, if any, and so on. Starting from the table is possible, but be sure to have a look at the sorting functions of.

Wikibook

 * Ada Programming
 * Ada Programming/Libraries/Ada.Containers

Ada 2005 Reference Manual


|Containers