2

In Ada programming, What is a best practice way to create a 2D array of a size that is specified by the user?

I'm teaching myself Ada for work (after many years of C programming), and I'm having difficulty understanding how to create an array of a size that is only known at runtime...

My simple program needs to do the following.

  1. Ask the user to type in two numbers, for the width (X) and height (Y) of a 2 dimensional character array.
  2. Use these values as the upper bounds of the array (0..X-1, 0..Y-1).
  3. Initialise the array with zeroes.

After quite a bit of googling I think I'm almost there, if I hard code the array to a fixed size then the program works fine, but I'd like to get the array sized as per the user's desire.

My full program is as follows, it doesn't compile, but I hope it is enough to demonstrate what I'm trying to do.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Characters; use Ada.Characters;

procedure test is
  type Two_Dimensional_Char_Array is array (Integer range <>, Integer range <>) of character;
  --grid : Two_Dimensional_Char_Array (0..59, 0..29) := (others => (others => ' '));
  grid : Two_Dimensional_Char_Array;

  procedure Draw_Grid(scr : Two_Dimensional_Char_Array) is
    X, Y : Integer := 0;
  begin
    Put("Width? ");
    Get(X);

    Put("Height? ");
    Get(Y);

    declare
      grid : Two_Dimensional_Char_Array(0..X-1, 0..Y-1);
    begin
      grid := (others => (others => 0));
    end;

    Put("+");
    for X in scr'First(1)..scr'Last(1) loop
      Put("-");
    end loop;
    Put_Line("+");

    for Y in scr'First(2)..scr'Last(2) loop
      Put("|");
      for X in scr'First(1)..scr'Last(1) loop
        Put(scr(X, Y));
      end loop;
      Put_Line("|");
    end loop;

    Put("+");
    for X in scr'First(1)..scr'Last(1) loop
      Put("-");
    end loop;
    Put_Line("+");
  end Draw_Grid;

begin
  grid(0,0) := 'a';
  grid(0,1) := 'b';
  grid(1,1) := 'c';
  grid(20,10) := 'd';
  grid(59,29) := 'X';
  Draw_Grid(grid);
end test;
Wossname
  • 139
  • 1
  • 6
  • It is possible to have dynamically sized grids, but it is unclear what you are trying to do: do you want to have a 60x60 matrix and draw only a subset of it? Basically, I don't understand why you are asking for dimensions in `Draw_Grid`. – coredump Jul 01 '15 at 08:57
  • Note that in your code, you first initialize some elements, and then you ask for dimensions, which is quite strange. – coredump Jul 01 '15 at 08:59
  • I want it to be any size the user asks for, the 60x60 part you refer to is actually commented out. – Wossname Jul 01 '15 at 09:04
  • This code is just me playing around with the language while I'm learning Ada's features. – Wossname Jul 01 '15 at 09:08

2 Answers2

4

The best practice is to use "constructor" function.

While the type can be unconstrained, the actual object has to posses some compile time bounds on size (unless you allocate it on heap).

You should create additional function for creating this array. Here is how I modified your code and works just fine (do note that I changed indices to be 1 based, as I hate 0 based array indices).

Additionally in your sample code you tried to initialize your Grid with Integers... (despite the fact that Grid is 2D array of Character)

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure test is
  type Two_Dimensional_Char_Array is
    array (Integer range <>, Integer range <>) of Character;

  -- "constructor" function.
  function Create_Grid return Two_Dimensional_Char_Array
  is
    X, Y: Natural := 0;
  begin
    Put ("Width? ");
    Get (X);

    Put ("Height? ");
    Get (Y);

    declare
      Grid: constant Two_Dimensional_Char_Array (1 .. X, 1 .. Y) :=
        (others => (others => ' '));
    begin
      return Grid;
    end;
  end Create_Grid;

  procedure Draw_Grid(scr : Two_Dimensional_Char_Array) is
  begin
    Put ("+");
    for X in scr'Range (1) loop
      Put ("-");
    end loop;
    Put_Line ("+");

    for Y in scr'Range (2) loop
      Put ("|");
      for X in scr'Range (1) loop
        Put (scr (X, Y));
      end loop;
      Put_Line ("|");
    end loop;

    Put ("+");
    for X in scr'Range (1) loop
      Put ("-");
    end loop;
    Put_Line ("+");
  end Draw_Grid;

  -- Create grid at runtime using function.
  Grid: Two_Dimensional_Char_Array := Create_Grid;

begin
  Grid (1,1)    := 'a';
  Grid (1,2)    := 'b';
  Grid (2,2)    := 'c';
  Grid (21,11)  := 'd';
  Grid (60,30)  := 'X';
  Draw_Grid (Grid);
end test;

In addition there is nice attribute in Ada: 'Range - used in for loops when you are iterating over entire range (which is just a shortcut for 'First .. 'Last)

PS: Why are you, C programmers, so averse of using spaces in code? It makes it hard to read [as in: scr'First(1)..scr'Last(1)].

darkestkhan
  • 156
  • 2
  • Ah, thank you, that answers it. I wasn't a million miles off :) – Wossname Jul 01 '15 at 09:24
  • @Wossname The fact that you were so close to solution is why I basically did it for you :) Can't count how many don't even get half as far as you did... – darkestkhan Jul 01 '15 at 09:30
  • By the way, I agree with you about readability and neatness of code formatting, I'm just being sloppy because I'm unaccustomed to writing Ada code. Forgive me. I do disagree about zero-based arrays though :) – Wossname Jul 01 '15 at 09:32
  • @Wossname This is a nice answer, but why are Ada programmers are so judgmental about other languages ;-) ? – coredump Jul 01 '15 at 09:35
  • 2
    We programmers are a picky species. I don't have a problem with it really. Ada is a language that requires careful design and consistency. In fact that's pretty much the reason it exists as far as I can tell, the pursuit of high quality code seems to be the core tenet of Ada more than any other language I can think of. C in particular is too forgiving of silly mistakes in my opinion, no matter how hard you try to write good, clear code. I'm really warming to Ada because it actively tries to assist you toward that goal. I'm seriously considering switching to Ada for all my future needs. – Wossname Jul 01 '15 at 09:41
  • @Wossname About code formatting and style, use the [-gnaty](https://gcc.gnu.org/onlinedocs/gnat_ugn/Style-Checking.html) option for GNAT (for example). I understand perfectly that Ada tries to provide high-quality code, and I like the language. However, this is unfortunately not the first time I see an Ada programmer gratuitiously attack C (their favorite target), and C *programmers* (you know, a bunch of *hackers* who do not *get* Ada and write *low-quality code*). Well, I don't even know why darkestkhan assumed OP was a C programmer in the first place. Just saying. – coredump Jul 01 '15 at 09:59
  • @coredump Maybe cause OP wrote that he has many years of C experience? [you know, first sentence of question] And my question is not that judgmental - it is just an observation - can't count how many times I saw, (usually) C programmers saving single key strokes on not typing spaces. – darkestkhan Jul 01 '15 at 10:06
  • 1
    @darkestkhan Shame on me, it is indeed written. I don't think people try to save keystrokes, they just do not necessarly know about existing idioms (and your example is good at showing how to write Ada code). – coredump Jul 01 '15 at 11:53
  • 1
    @Wossname Check out the [Ada Style Guide](https://en.wikibooks.org/wiki/Ada_Style_Guide). – Patrick Jul 01 '15 at 13:38
  • @coredump Thanks, showing how to write Ada code is one of two things I tried to achieve with this answer (another thing was actually answering question) – darkestkhan Jul 01 '15 at 20:28
1

Here is a revised version:

 with Ada.Text_IO; use Ada.Text_IO;
 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
 with Ada.Characters; use Ada.Characters;

 procedure test is
    type Two_Dimensional_Char_Array is
      --  Natural range seems more appropriate
      array (Natural range <>, Natural range <>) of Character;

   procedure Draw_Grid(grid : Two_Dimensional_Char_Array) is
   begin
      Put("+");
      for X in grid'First(1)..grid'Last(1) loop
         Put("-");
      end loop;
      Put_Line("+");

      for Y in grid'First(2)..grid'Last(2) loop
         Put("|");
         for X in grid'First(1)..grid'Last(1) loop
            Put(grid(X, Y));
         end loop;
         Put_Line("|");
      end loop;

      Put("+");
      for X in grid'First(1)..grid'Last(1) loop
         Put("-");
      end loop;
      Put_Line("+");
   end Draw_Grid;

   X, Y : Natural;

 begin
    Put("Width? ");
    Get(X);

    Put("Height? ");
    Get(Y);

    declare
       grid : Two_Dimensional_Char_Array(0..X-1, 0..Y-1) := (others => (others => ' '));
    begin
       --  grid is only visible in this block

       grid(0,0) := 'a';
       grid(0,1) := 'b';
       grid(1,1) := 'c';
       grid(20,10) := 'd';
       grid(59,29) := 'X';
       Draw_Grid(grid);
    end;
 end test;
  • Draw_Grid only draws the grid. It does not need to know the actual sizes, because the code you wrote uses 'First and 'Last attributes, which is good. Maybe it would be possible to shorten that a bit with grid'Range, but I did not try.

  • I change the indice type to Natural, because you don't expect to have negative indices (that could be possible, but 0..X-1 suggests you don't want that).

  • X, Y are declared in the scope of the main procedure, and initialized first by prompting the user.

  • Then, grid is declared in a sub-block (it won't be accessible outside of this scope), with the given dimensions.
  • Finally, populate the grid and draw it.

Note that a runtime Constraint_Error is raised when the dimensions are not large enough: you assume you can set a value at (59,29).

coredump
  • 5,895
  • 1
  • 21
  • 28