跳转到内容

Ada 编程/容器

来自 Wikibooks,开放世界中的开放书籍

Ada. Time-tested, safe and secure.
Ada。经久耐用、安全可靠。

以下是某些容器类型的简单演示。它没有涵盖所有内容,但应该可以帮助您入门。

此语言功能仅从Ada 2005开始可用。

第一个示例:映射

[编辑 | 编辑源代码]

下面的程序用多种人类语言向世界问好。问候语存储在一个表格或哈希映射中。该映射将每个问候语(一个值)与语言代码(一个键)相关联。也就是说,您可以使用语言代码作为键在表格中查找问候语值。

映射中的元素是国际字符的常量字符串,或者更确切地说,是指向此类常量字符串的指针。一个包区域用于设置语言 ID 和Ada.Containers.Hashed_Maps的一个实例。

文件:regional.ads (查看纯文本下载页面浏览所有)
with Ada.Containers.Hashed_Maps;  use Ada.Containers;

package Regional is

   type Language_ID is (DE, EL, EN, ES, FR, NL);
   --  a selection from the two-letter codes for human languages

   type Hello_Text is access constant Wide_String;
   --  objects will contain a «hello»-string in some language


   function ID_Hashed (id: Language_ID) return Hash_Type;
   --  you need to provide this to every hashed container

   package Phrases is new Ada.Containers.Hashed_Maps
     (Key_Type => Language_ID,
      Element_Type => Hello_Text,
      Hash => ID_Hashed,
      Equivalent_Keys => "=");

end Regional;

这是程序,稍后将解释详细信息。

文件:hello_world_extended.ads (查看纯文本下载页面浏览所有)
with Regional; use Regional;
with Ada.Wide_Text_IO; use Ada;

procedure Hello_World_Extended is

   --  print greetings in different spoken languages

   greetings: Phrases.Map;
   --  the dictionary of greetings

begin -- Hello_World_Extended

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

   --  or, shorter,
   greetings.Insert(DE, new Wide_String'("Hallo, Welt!"));
   greetings.Insert(NL, new Wide_String'("Hallo, Wereld!"));
   greetings.Insert(ES, new Wide_String'("¡Hola mundo!")); 
   greetings.Insert(FR, new Wide_String'("Bonjour, Monde!"));
   greetings.Insert(EL, new Wide_String'("Γεια σου κόσμε"));

   declare
      use Phrases;

      speaker: Cursor := First(greetings);
   begin
      while Has_Element(speaker) loop
         Wide_Text_IO.Put_Line( Element(speaker).all );
         Next(speaker);
      end loop;
   end;

end Hello_World_Extended;

第一个插入语句以 Ada 95 样式编写

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

接下来的插入使用所谓的区分接收者表示法,您可以在 Ada 2005 中使用。(这是面向对象的术语。虽然 Insert 调用涉及以下所有内容:容器对象(greetings)、键对象(EN)和新项目对象(new Wide_String'("Hello, World!")),但容器对象与其他对象的区别在于,Insert 调用为其(且仅为其)提供其他对象。在本例中,容器对象将通过调用进行修改,使用名为 Key 和 New_Item 的参数进行修改。)

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

设置表格后,程序继续打印表格中包含的所有问候语。它通过一个沿表格中的元素以某种顺序运行的光标来实现此目的。典型方案是获取一个光标,此处使用First,然后迭代以下调用

  1. Has_Element,用于检查光标是否位于元素上
  2. Element,以获取元素和
  3. Next,以将光标移动到另一个元素

当没有更多元素剩余时,光标将具有特殊值No_Element。实际上,这是一种可以与Ada.Containers子包中的所有容器一起使用的迭代方案。

稍作修改:选择元素

[编辑 | 编辑源代码]

下一个程序演示了如何根据键从映射中选择一个值。实际上,您将提供键。该程序与前面的程序类似,只是它不仅打印映射中的所有元素,而且根据从标准输入读取的 Language_ID 值选择一个元素。

文件:hello_world_pick.adb (查看纯文本下载页面浏览所有)
with Regional; use Regional;
with Ada.Wide_Text_IO; use Ada;

procedure Hello_World_Pick is

  ... as before ...

 declare
     use Phrases;
 
     package Lang_IO is new Wide_Text_IO.Enumeration_IO(Language_ID);
     lang: Language_ID;
  begin
     Lang_IO.Get(lang);
     Wide_Text_IO.Put_Line( greetings.Element(lang).all );
  end;
 
end Hello_World_Pick;

这次的Element函数使用一个键 (lang) 而不是一个光标。实际上,它使用两个值,另一个值是greetings,以区分接收者表示法。

第二个示例:向量和映射

[编辑 | 编辑源代码]

让我们从字面上理解豆子计数。红豆、绿豆和白豆。(是的,白豆确实存在。)您的工作是收集一定数量的豆子,称重,然后分别确定红豆、绿豆和白豆的平均重量。这是一种方法。

同样,我们需要一个包,这次用于存储与蔬菜相关的信息。介绍一下Beans包(Grams 类型不属于蔬菜包,但它在那里是为了简化操作)

文件:1/beans.ads (查看纯文本下载页面浏览所有)
with Ada.Containers.Vectors;

package Beans is

   type Bean_Color is (R, G, W);
   --  red, green, and white beans

   type Grams is delta 0.01 digits 7;
   --  enough to weigh things as light as beans but also as heavy as
   --  many of them

   type Bean is
   --  info about a single bean
      record
         kind: Bean_Color;
         weight: Grams;
      end record;

   subtype Bean_Count is Positive range 1 .. 1_000;
   --  numbers of beans to count (how many does Cinderella have to count?)

   package Bean_Vecs is new Ada.Containers.Vectors
     (Element_Type => Bean,
      Index_Type => Bean_Count);

end Beans;

Vectors实例提供了一个类似于数组的数据结构,该数据结构可以在运行时更改其大小。它被称为Vector。读取的每个豆子都将附加到一个Bean_Vecs.Vector对象。

以下程序首先调用read_input以用豆子填充缓冲区。接下来,它调用一个函数,该函数计算具有相同颜色的豆子的平均重量。此函数

with Beans;   use Beans;

function average_weight
  (buffer: Bean_Vecs.Vector; desired_color: Bean_Color) return Grams;
--  scan `buffer` for all beans that have `desired_color`. Compute the
--  mean of their `.weight` components


然后打印每种颜色的豆子的平均值,程序停止。

文件:1/bean_counting.adb (查看纯文本下载页面浏览所有)
with Beans;
with average_weight;
with Ada.Wide_Text_IO;

procedure bean_counting is
   use Beans, Ada;

   buffer: Bean_Vecs.Vector;

   procedure read_input(buf: in out Bean_Vecs.Vector) is separate;
   --  collect information from a series of bean measurements into `buf`


begin --  bean_counting

   read_input(buffer);

   --  now everything is set up for computing some statistical data.
   --  For every bean color in `Bean_Color`, the function `average_weight`
   --  will scan `buffer` once, and accumulate statistical data from
   --  each element encountered.

   for kind in Bean_Color loop
      Wide_Text_IO.Put_Line
        (Bean_Color'Wide_Image(kind) &
         " ø =" & Grams'Wide_Image( average_weight(buffer, kind) ));
   end loop;

end bean_counting;

所有容器操作都在函数average_weight中进行。为了找到相同颜色的豆子的平均重量,该函数正在按顺序查看所有豆子。如果一个豆子具有正确的颜色,average_weight将它的重量添加到总重量中,并将计数的豆子数量增加 1。

计算访问所有豆子。从一个豆子移动到下一个豆子,然后执行上述步骤所需的迭代最好留给Iterate过程,它是所有容器包的一部分。为此,将上述步骤包装在某个过程中,并将此过程传递给Iterate。效果是Iterate为向量中的每个元素调用您的过程,并将光标值传递给您的过程,每个元素一个。

让容器机制执行迭代也可能比自己移动和检查光标更快,就像在Hello_World_Extended示例中所做的那样。

文件:average_weight.adb (查看纯文本下载页面浏览所有)
with Beans;  use Beans.Bean_Vecs;

function average_weight
  (buffer: Bean_Vecs.Vector; desired_color: Bean_Color) return Grams
is
   total: Grams := 0.0;
   --  weight of all beans in `buffer` having `desired_color`

   number: Natural := 0;
   --  number of beans in `buffer` having `desired_color`

   procedure accumulate(c: Cursor) is
      --  if the element at `c` has the `desired_color`, measure it
   begin
      if Element(c).kind = desired_color then
         number := number + 1;
         total := total + Element(c).weight;
      end if;
   end accumulate;

begin --  average_weight

   Iterate(buffer, accumulate'Access);

   if number > 0 then
      return total / number;
   else
      return 0.0;
   end if;

end average_weight;

这种方法很简单。但是,想象一下更大的向量。average_weight将反复访问所有元素以获取每种颜色。如果有 M 种颜色和 N 个豆子,average_weight将被调用 M * N 次,并且每添加一种新颜色,都需要调用 N 次。一种可能的选择是在访问每个豆子时收集有关该豆子的所有信息。但是,这可能需要更多变量,并且您必须找到一种方法来返回多个结果(每种颜色的一个平均值)等。试试看!

可能另一种方法会更好。一种方法是将不同颜色的豆子复制到单独的向量对象中。(记住灰姑娘。)然后average_weight必须只访问每个元素一次。以下过程执行此操作,使用来自Beans的新类型,称为Bean_Pots.

   ...
   type Bean_Pots is array(Bean_Color) of Bean_Vecs.Vector;
   ...

请注意,此普通数组如何将颜色与向量相关联。将豆子放入正确碗中的过程使用豆子颜色作为数组索引来查找正确的碗(向量)。

文件:2/gather_into_pots.adb (查看纯文本下载页面浏览所有)
procedure gather_into_pots(buffer: Bean_Vecs.Vector; pots: in out Bean_Pots) is
   use Bean_Vecs;

   procedure put_into_right_pot(c: Cursor) is
      --  select the proper bowl for the bean at `c` and «append»
      --  the bean to the selected bowl
   begin
      Append(pots(Element(c).kind), Element(c));
   end put_into_right_pot;

begin  --  gather_into_pots
   Iterate(buffer, put_into_right_pot'Access);
end gather_into_pots;


现在一切就绪了。

文件: 2/bean_counting.adb (查看, 纯文本, 下载页面, 浏览所有)
with Beans;
with average_weight;
with gather_into_pots;
with Ada.Wide_Text_IO;

procedure bean_counting is
   use Beans, Ada;

   buffer: Bean_Vecs.Vector;
   bowls: Bean_Pots;

   procedure read_input(buf: in out Bean_Vecs.Vector) is separate;
   --  collect information from a series of bean measurements into `buf`


begin --  bean_counting

   read_input(buffer);

   --  now everything is set up for computing some statistical data.
   --  Gather the beans into the right pot by color.
   --  Then find the average weight of beans in each pot.

   gather_into_pots(buffer, bowls);

   for color in Bean_Color loop
      Wide_Text_IO.Put_Line
        (Bean_Color'Wide_Image(color)
         & " ø ="
         & Grams'Wide_Image(average_weight(bowls(color), color)));
   end loop;

end bean_counting;


由于我们为每种颜色选择了一个向量,因此可以通过调用Length函数来确定每个向量中豆子的数量。但是average_weight也计算向量中的元素数量。因此,一个求和函数可以替换average_weight此处。

只需一张映射表!

[编辑 | 编辑源代码]

以下程序首先调用read_input用于填充一个包含豆子的缓冲区。然后,有关这些豆子的信息存储在一个表中,该表将豆子的属性映射到出现的次数。从Iterate开始的处理使用了Ada.Containers迭代机制中常见的链式过程调用。

此示例中的 Beans 包实例化了另一个泛型库单元,Ada.Containers.Ordered_Maps。其中Ada.Containers.Hashed_Maps 需要散列函数,Ada.Containers.Ordered_Maps 需要比较函数。我们提供了一个函数,"<",它首先按颜色排序豆子,然后按重量排序。由于其名称"<"与泛型形式函数"<".

   ...
   function "<"(a, b: Bean) return Boolean;
   --  order beans, first by color, then by weight

   package Bean_Statistics
     --  instances will map beans of a particular color and weight to the
     --  number of times they have been inserted.
   is new Ada.Containers.Ordered_Maps
     (Element_Type => Natural,
      Key_Type => Bean);
   ...


的名称匹配,因此它将自动与相应的泛型形式函数关联。在前面的示例中,我们使用了bean_counting的变体,将所有子程序打包为局部子程序。

文件: 3/bean_counting.adb (查看, 纯文本, 下载页面, 浏览所有)
with Beans;
with Ada.Wide_Text_IO;

procedure bean_counting is
   use Beans, Ada;

   buffer: Bean_Vecs.Vector;
   stats_cw: Bean_Statistics.Map;
   --  maps beans to numbers of occurrences, grouped by color, ordered by
   --  weight

   procedure read_input(buf: in out Bean_Vecs.Vector) is separate;
   --  collect information from a series of bean measurements into `buf`

   procedure add_bean_info(specimen: in Bean);
   --  insert bean `specimen` as a key into the `stats_cw` table unless
   --  present. In any case, increase the count associated with this key
   --  by 1. That is, count the number of equal beans.

   procedure add_bean_info(specimen: in Bean) is

      procedure one_more(b: in Bean; n: in out Natural) is
       --   increase the count associated with this kind of bean
      begin
         n := n + 1;
      end one_more;

      c : Bean_Statistics.Cursor;
      inserted: Boolean;
   begin
      stats_cw.Insert(specimen, 0, c, inserted);
      Bean_Statistics.Update_Element(c, one_more'Access);
   end add_bean_info;

begin --  bean_counting

   read_input(buffer);

   --  next, for all beans in the vector `buffer` just filled, store
   --  information about each bean in the `stats_cw` table.

   declare
      use Bean_Vecs;

      procedure count_bean(c: Cursor) is
      begin
         add_bean_info(Element(c));
      end count_bean;
   begin
      Iterate(buffer, count_bean'Access);
   end;

   --  now everything is set up for computing some statistical data. The
   --  keys of the map, i.e. beans, are ordered by color and then weight.
   --  The `First`, and `Ceiling` functions will find cursors
   --  denoting the ends of a group.


   declare
      use Bean_Statistics;

      --  statistics is computed groupwise:

      q_sum: Grams;
      q_count: Natural;

      procedure q_stats(lo, hi: Cursor);
      --  `q_stats` will update the `q_sum` and `q_count` globals with
      --  the sum of the key weights and their number, respectively. `lo`
      --  (included) and `hi` (excluded)  mark the interval of keys
      --  to use from the map.

      procedure q_stats(lo, hi: Cursor) is
         k: Cursor := lo;
      begin
         q_count := 0; q_sum := 0.0;
         loop
            exit when k = hi;
            q_count := q_count + Element(k);
            q_sum := q_sum + Key(k).weight * Element(k);
            Next(k);
         end loop;
      end q_stats;


      --  precondition
      pragma assert(not Is_Empty(stats_cw), "container is empty");

      lower, upper: Cursor := First(stats_cw);
      --  denoting the first key of a group, and the first key of a
      --  following group, respectively

   begin

      --  start reporting and trigger the computations

      Wide_Text_IO.Put_Line("Summary:");

      for color in Bean_Color loop
         lower := upper;
         if color = Bean_Color'Last then
            upper := No_Element;
         else
            upper := Ceiling(stats_cw, Bean'(Bean_Color'Succ(color), 0.0));
         end if;

         q_stats(lower, upper);

         if q_count > 0 then
            Wide_Text_IO.Put_Line
              (Bean_Color'Wide_Image(color) & " group:" &
               "  ø =" & Grams'Wide_Image(q_sum / q_count) &
               ", # =" & Natural'Wide_Image(q_count) &
               ", Σ =" & Grams'Wide_Image(q_sum));
         end if;
      end loop;
   end;

end bean_counting;

就像在问候语示例中一样,您可以从表中选择值。这次,这些值表示具有某些属性的豆子的出现次数。
stats_cw表按键排序,即按豆子的属性排序。给定特定的属性,您可以使用FloorCeiling函数来逼近表中最接近所需属性的豆子。

现在很容易打印一个直方图,显示每种豆子出现的频率。如果 N 是某种豆子的数量,则在一行中打印 N 个字符,或绘制长度为 N 的图形条等。在计算这种颜色的豆子总和后,可以使用前面示例中的组绘制显示每种颜色豆子数量的直方图。您可以使用相同的技术从表中删除某种颜色的豆子。

最后,考虑按顺序排列豆子,从出现频率最低的种类开始。也就是说,构造一个向量,首先附加仅出现一次的豆子,然后附加出现两次的豆子(如果有),依此类推。从表开始是可能的,但请务必查看Ada.Containers的排序函数。

另请参阅

[编辑 | 编辑源代码]

维基教科书

[编辑 | 编辑源代码]

Ada 2005 参考手册

[编辑 | 编辑源代码]
华夏公益教科书