I'm supposed to make a subprogram that has exactly one parameter of type Integer. And this Integer represents a day of the week where 1 is monday, 2 is tuesday etc...
The subprogram should return how many days there's left until Saturday.
This is my approach (I'm not supposed to use arrays):
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Test3 is
function Week (Days : in Integer) return Integer is
begin
if Days = 1 then
return 5;
elsif Days = 2 then
return 4;
elsif Days = 3 then
return 3;
elsif Days = 4 then
return 2;
elsif Days = 5 then
return 1;
elsif Days = 6 then
return 7;
elsif Days = 7 then
return 6;
end if;
end Week;
Days : Integer;
begin
Put("What day is it: ");
Get(Days);
if Days < 0 or Days > 7 then
Put_Line("Wrong Value");
end if;
Put("It's ");
Put(Week(Days), Width => 1);
Put(" days until Saturday.");
end Test3;
Even though my program works I have two problems. Do you see the amount of "elsifs", can this not be solved in any other way? I don't like how my program looks.
Secondly,
How am I supposed to make an if statement where if Days < 0 or Days > 7 will raise an error. As you can see I have attempted to this in my main program but it won't work since I have
"Get(Days);" before this statement and my subprogram won't recognize my if statement? Should I put my if statement in my subprogram instead?
Any help is appreciated.
With respect to raising error, how about
if Days not in 1 .. 7 then
Put_Line("Wrong Value");
raise Contrain_Error with "Days is out of range, " & Days'Image;
else
-- normal processing
end if;
Regarding the question of what to do with invalid Days, that is for you to decide, and then you code the subprogram accordingly. I think the most natural, for Ada, would be to put the check in the subprogram and raise Constraint_Error if it fails.
As Simon Wright said, you must decide and specify (and describe in comments) how the week-days are numbered. Otherwise no-one can check your code.
Once you have clearly decided and described those things, for yourself and for any reader of the program, you can have a closer look at your if-then-else nest and see how systematically the return value depends on the input value. Maybe there is a simple formula that can handle most cases, or perhaps all cases with valid input? That would let you reduce the number of if-then-elses.
It appears you allow day numbers in 1 .. 7. You can state this directly
subtype Day_Number is Integer range 1 .. 7;
function Days_Till_Saturday (Day : in Day_Number) return Day_Number;
You can then omit the check on the input value; passing an invalid value to the function will raise Constraint_Error, which you can handle to report the error.
Regarding the long chain of elsifs, you can use a case statement:
case Day is
when 1 =>
return 5;
but for this problem it might be clearer to use arithmetic:
Result : constant Integer := 6 - Day;
begin
if Result not in Day_Number then
return Result + Day_Number'Last;
end if;
return Result;
Related
I was expecting this program to raise an error when I feed it 3 as a valid Scale value, but no such luck:
with Ada.Text_IO; use Ada.Text_IO;
procedure predicate is
type Scale is new Integer
with Dynamic_Predicate => Scale in 1 | 2 | 4 | 8;
GivesWarning : Scale := 3; -- gives warning
begin
Put_Line ("Hello World");
loop
Put_Line ("Gimme a value");
declare
AnyValue : Integer := Integer'Value (Get_Line);
S : Scale := Scale (AnyValue); -- no check done!
begin
Put_Line ("okay, that works" & S'Image);
end;
end loop;
end predicate;
I found this related question, but there the requirement is to use an enum., and the solution is to define an array from enum -> value.
I want something that gives me at least a warning at compile time, and allows me to check at runtime as well, and that raises an error if I try to put an invalid value in. Then, if I can use SPARK to prove that no invalid values can occur, I could turn off said checks. I was under the impression that this was how Static_ / Dynamic_ predicates work, so the example above took me by surprise.
You need to enable assertions. Either compile with -gnata or set an appropriate Assertion_Policy
pragma Assertion_Policy(Dynamic_Predicate => Check);
In this code, I need help writing a multidimensional array with a range between 2020-01-01 to 2119-12-31.
My code works but as you see there are no arrays in it. How can I write this code with only arrays?
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
Procedure Date is
type date_type is record
Str : string (1..8);
Length : Natural := 0; end record;
A: date_type;
begin
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Get_Line (A.Str, A.Length);
exit when A.Length = 8;
Put_Line ("Wrong input. Try again.");
end loop;
Put_Line (A.Str (1 .. 4) & "-" & A.Str (5 .. 6) & "-" & A.Str (7 .. 8));
end Date;
Perhaps, rather than a multi-dimensional array you should consider using a record such as
type Year_Number is range 1900..3000;
type Month_Number is range 1..12;
type Day_Number is range 1..31;
type Date_Rec is record
Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
end record;
subtype Year_String is string (1..4);
subtype Month_String is string (1..2);
subtype Day_String is string (1..2);
function To_Date (Yr : Year_String; Mnth : Month_String; Dy : Day_String)
return Date_Rec is
Result : Date_Rec;
begin
Result.Year := Year_Number'Value (Yr);
Result.Month := Month_Number'Value (Mnth);
Result.Day := Day_Number'Value (Dy);
return Result;
end To_Date;
You can now pass around instances of Date_
Rec doing whatever you want with the date.
If you go this far then you might want to consider using the Time type described in Ada Language Reference Manual sections 9.6 and 9.6.1.
You haven't asked a reasonable question here because "I want to use arrays" is not a good reason to use an array.
"This problem can be best solved with an array ... but how do I deal with ... ?" would be a reasonable question, but you haven't stated a problem,let alone one that needs an array.
This is important because "using an array" is thinking in the solution domain, like "using a chisel". It's not the way to think about programming in Ada, (or IMO in any language).
Try thinking in the problem domain first : instead of "I want to use a chisel", I think "I want to recess this hinge so the door fits precisely" and a chisel is the neatest way of doing the job.
Then "How can I best validate a date?" would be one reasonable question, or "how can I store events that happen on each day for 100 years?"
The answer to the first question is probably in the Ada.Calendar package. Possibly the Value function in Ada.Calendar.Formatting, with an exception handler to catch incomprehensible strings and make the user try again.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar;
with Ada.Calendar.Formatting;
procedure date is
Date : Ada.Calendar.Time;
Done : Boolean;
begin
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Done := TRUE;
declare
A : String := Get_Line & " 12:00:00";
begin
Date := Ada.Calendar.Formatting.Value(A); -- validate it's a date
Done := Ada.Calendar.Year(Date) >= 2020
and Ada.Calendar.Year(Date) < 2120; -- validate correct range
exception
when Constraint_Error => Done := False; -- Formatting.Value failed
end;
exit when Done;
Put("Try Again : ");
end loop;
end date;
The answer to the second is probably a 1-dimensional array indexed by Day_Count from Ada.Calendar.Arithmetic but let's use the wrong tool : a 3D array indexed by your range of years, Month_Number and Day_Number from the Ada.Calendar base package.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
with Ada.Calendar.Formatting;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
procedure date is
Date : Ada.Calendar.Time;
Done : Boolean := TRUE;
Event_Array : array(2020 .. 2119,
Ada.Calendar.Month_Number,
Ada.Calendar.Day_Number) of Unbounded_String;
begin
Event_Array := (others => (others => (others => Null_Unbounded_String)));
Event_Array(2020,11,3) := To_Unbounded_String("nothing much");
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Done := TRUE;
declare
A : String := Get_Line & " 12:00:00";
begin
Date := Ada.Calendar.Formatting.Value(A);
Done := Ada.Calendar.Year(Date) >= 2020
and Ada.Calendar.Year(Date) < 2120;
exception
when Constraint_Error => Done := False;
end;
exit when Done;
Put("Try Again : ");
end loop;
Put_Line("Today " & Ada.Calendar.Formatting.Image(Date) & " : " &
To_String(Event_Array(Year(Date), Month(Date), Day(Date))) & " happened");
end date;
Test with the string 2020-11-03
My question is pretty simple, I have input that looks like this...
0 0 0 1 1 1 -1 -1 -1 1
And I need to store these values into an array but I can't figure it out. This is what I have so far...
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type arr is array(1..10) of Integer;
Data : arr;
begin
for I in 1..arr'Length loop
Data(I) := Integer'Value(Get_Line);
end loop;
end Main;
I know this wrong and it's pretty obvious why this isn't working. I'm trying to store multiple values into a single integer, I need a way to iterate over the input or load all the values at once. How would you do this in Ada?
You can use Get_Line to get the whole line as a string and then Ada.Integer_Text_IO to parse the string:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Hello is
Line : String := Get_Line;
Value : Integer;
Last : Positive := 1;
begin
while Last < Line'Last loop
Get(Line(Last..Line'Last),Value,Last);
Put_Line(Value'Image); -- Save the value to an array here instead
Last := Last + 1; -- Needed to move to the next part of the string
end loop;
end Hello;
After that, you can load the values into an array in the loop or however you like.
Example output:
$gnatmake -o hello *.adb
gcc -c hello.adb
gnatbind -x hello.ali
gnatlink hello.ali -o hello
$hello
0
0
0
1
1
1
-1
-1
-1
1
EDIT: Adding a recursive option that is more general. This will read a line from STDIN and recursively concatenate the values into an array. It uses the secondary stack to do so in GNAT.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_Io;
procedure Hello is
-- Need an array return type
type Integer_Array is array (Positive range <>) of Integer;
-- Recursive function
function Get_Ints return Integer_Array is
Value : Integer;
begin
-- Read from STDIN using Integer_Text_IO;
Get(Value);
-- Concatinate recursively
return Integer_Array'(1 => Value) & Get_Ints;
exception
-- I found different exceptions with different versions
-- of GNAT, so using "others" to cover all versions
when others =>
-- Using Ada2012 syntax here. If not using Ada2012
-- then just declare the Empty variable somewhere
-- and then return it here
return Empty : Integer_Array(1..0);
end Get_Ints;
Result : Integer_Array := Get_Ints;
begin
Put_Line("Hello, world!");
Put_Line(Integer'Image(Result'Length));
for E of Result loop
Put(Integer'Image(E) & " ");
end loop;
end Hello;
If you know that you have 10 elements to read, it can be done a little more simply like this:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Hello is
A: array (1..10) of Integer;
begin
for V of A loop
Get(V);
end loop;
for I in A'Range loop
Put(I, 5);
Put(": ");
Put(A(I), 5);
New_Line;
end loop;
end Hello;
If you don't actually know how many elements to read in advance, please update the question.
Even though this already was answered, I'd like to add a couple improvements to Jere's answer.
It is more Ada-like to terminate the recursion using End_Of_File rather than an exception. Plus, it makes the program clearer.
Also, using tail-call recursion instead of normal recursion allows the compiler to perform some optimization.
function Get_Ints(input : in File_Type) return Integer_Array is
function Get_Ints_Rec(accumulator : in Integer_Array) return Integer_Array is
value : Integer;
begin
if End_Of_File(input) then
return accumulator;
else
begin
Get(input, value);
exception
when Data_Error => -- problem when reading
if not End_Of_Line(input) then
Skip_Line(input);
end if;
return Get_Ints_Rec(acc);
end;
return Get_Ints_Rec(accumulator & (1 => value));
end if;
end Get_Ints_Rec;
acc : constant Integer_Array(1 .. 0) := (others => 0);
begin
return Get_Ints_Rec(acc);
end Get_Ints;
I'm about 5 hours into learning Ada. I made a simple program and I was putting different values into it and experienced behavior I can't explain.
My program works fine with normal inputs like km = 100, litres = 10, result = 10.0.
And obviously bad inputs work: km = "cat" result in "raised ADA.IO_EXCEPTIONS.DATA_ERROR"
But this combination of inputs has me baffled: km = 100..10. The program skips over the litres input and presents a result that works back to a litres value of 0.10. I was expecting a constraint error like when I entered 'cat'.
Can someone please explain this to me and how Ada programmers work around it so that 100..10 results in an error.
Here's my program:
-- This program takes km driven and litres consumed to calculate
-- fuel economy.
with Ada.Text_IO;
with Ada.Float_Text_IO;
procedure Main is
package TIO renames Ada.Text_IO;
package FIO renames Ada.Float_Text_IO;
type Km is new Float range 0.01 .. 10_000.00;
type Litres is new Float range 0.01 .. 10_000.00;
package Km_IO is new TIO.Float_IO (Km);
package Litres_IO is new TIO.Float_IO (Litres);
Entered_Km : Km;
Entered_Litres : Litres;
function Fuel_Economy (
Entered_Km : in Km;
Entered_Litres : in Litres
) return Float is
-- returns fuel economy normalized to litres per 100 km
begin
return Float(Entered_Litres) / Float(Entered_Km) * 100.0;
end Fuel_Economy;
begin
TIO.Put_Line ("This program calculates fuel economy.");
TIO.Put_Line ("Enter km:");
Km_IO.Get (Entered_Km);
TIO.Put_Line ("Enter litres:");
Litres_IO.Get (Entered_Litres);
TIO.Put ("Your fuel economy is ");
FIO.Put (Item => Fuel_Economy (Entered_Km, Entered_Litres),
Fore => 1,
Aft => 2,
Exp => 0
);
TIO.Put_Line (" litres per 100 km.");
end Main;
Exact output:
This program calculates fuel economy.
Enter km:
100..10
Enter litres:
Your fuel economy is 0.10 litres per 100 km.
[2018-05-06 14:08:16] process terminated successfully, elapsed time: 04.01s
I'm using GPS 2017 on windows 7.
Many thanks.
Typically experienced Ada users read the entire line into a String (using function Get_Line) and then extract the numeric value(s) from the String. Since programs like this typically loop until the user enters a valid value, reading into a String avoids a common problem with direct numeric I/O in which invalid input results in an infinite loop:
Get_Km : loop
Handle_Invalid : begin
Put_Line (Item => "Enter km");
Get (Item => Entered_Km);
exit Get_Km;
exception -- Handle_Invalid
when others =>
Put_Line ("Invalid input");
end Handle_Invalid;
end loop Get_Km;
The invalid input remains in the buffer and is repeatedly processed by Get.
Wright demonstrated a variation of this, but usually anything left on the line after the value(s) is discarded, so that input can be redirected from a file with trailing comments:
13.3 -- km traveled
One solution would be to discard the rest of the line after having read the (leading) number:
TIO.Put_Line ("Enter km:");
Km_IO.Get (Entered_Km);
TIO.Skip_Line;
Another, less straightforward, would be to check that there wasn’t anything left over:
TIO.Put_Line ("Enter km:");
Get_Km:
loop
declare
Input : constant String := TIO.Get_Line;
Last : Natural;
begin
Km_IO.Get (From => Input, Item => Entered_Km, Last => Last);
exit Get_Km when Input (Last + 1 .. Input'Length)
= (Last + 1 .. Input'Length => ' ');
TIO.Put_Line ("... just the number, please; '"
& Input (Last + 1 .. Input'Length)
& "' was left over");
exception
when others =>
TIO.Put_Line ("... a number, please");
end;
end loop Get_Km;
According to the Ada Language Reference Manual A.10.9(15-16) your run-time library is behaving correctly, as these two formats are allowed syntax for Ada.Text_IO.Float_IO.Get:
[+|–]numeral.[exponent]
[+|–].numeral[exponent]
I'm trying to run this code, but something is going wrong with the line:
Soldiers (Number_Of_Soldiers) := Soldier_Type'(Name=>new String'(Line(1..Length)), Alive=>True);
Can someone help me, please?
Thank you so much!
--Josephus Problem
with Ada.Text_IO,Ada.Integer_Text_IO;
use Ada;
procedure Josephus is
type String_Pointer is access String;
type Soldier_Type is record
Name : String_Pointer;
Alive : Boolean;
end record;
Max_Number_Of_Soldiers: constant := 10;
Number_Of_Soldiers : Integer range 0..Max_Number_Of_Soldiers := 0;
-- start with 0 to facilitate modular arithmetic
Soldiers: array (0..Max_Number_Of_Soldiers-1) of Soldier_Type;
procedure Next (Index: in out Integer; Interval: Positive) is
begin
for I in 1..Interval loop
loop
Index := (Index + 1) mod Number_Of_Soldiers;
exit when Soldiers(Index).Alive;
end loop;
end loop;
end Next;
Interval : Integer;
Man : Integer := Soldiers'First;
begin
-- get interval from the standard input
Integer_Text_IO.Get (Interval);
Text_IO.Skip_Line;
Text_IO.Put ("Skip every ");
Integer_Text_IO.Put (Interval, Width=>1);
Text_IO.Put_Line (" soldiers.");
-- get names (one per line) from the standard input
declare
Line: String (1..10);
Length: Integer;
begin
while not (Text_IO.End_Of_File) loop
Text_IO.Get_Line (Line, Length);
Soldiers (Number_Of_Soldiers) := Soldier_Type'(Name=>new String'(Line(1..Length)), Alive=>True);
Number_Of_Soldiers := Number_Of_Soldiers + 1;
end loop;
end;
for I in 1..Number_Of_Soldiers-1 loop
Soldiers(Man).Alive := False;
Text_IO.Put (Soldiers(Man).Name.all);
Text_IO.Put_Line (" commits suicide.");
Next (Man, Interval);
end loop;
Text_IO.Put (Soldiers(Man).Name.all);
Text_IO.Put_Line (" is the last.");
end Josephus;
I think your problem is with the line
Max_Number_Of_Soldiers: constant := 10;
Obviously the number specified needs to be more than the maximum possible number of entries in your input!
The problem of unbounded input data sets is one reason to look at using Ada.Containers.Vectors instead of arrays.