Converting to Morse Code in Ada - arrays

I posted the other day here because I was confused behind the syntax of Fortran and got an answer that helped a lot. But now I'm stuck in the same pickle, but with Ada this time.
Below is the gist of my program. I'm trying to read a sentence from the user and convert each character in it into morse code.
with Ada.Text_IO;
with Ada.Strings;
with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded;
with Ada.Command_Line;
use Ada.Text_IO;
procedure MorseCode is
tempSentence : array(1 .. 20) of string;
last : Integer;
size : Integer;
i : Integer;
begin
Put("Enter the size of your sentence: ");
Get(size);
Put("Enter the sentence you would like to convert to morse code: ");
Get_Line(tempSentence, last);
Put_Line(tempSentence);
While_Loop :
while i < size loop
if tempSentence(i .. i) = 'A' or tempSentence(i) = 'a' then
Put(".- ");
elsif tempSentence(i .. i) = 'B' or tempSentence(i) = 'b' then
Put("-... ");
elsif tempSentence(i) = 'C' or tempSentence(i) = 'c' then
Put("-.-. ");
end if;
end loop While_Loop;
end;

I probably shouldn't, but the problems here go far beyond simple syntax errors so here's a few pointers to a more Ada-like approach.
Words in bold are worth reading about in more detail in a good Ada book.
First, stop thinking at such a low level. Strings are arrays of characters, and arrays in Ada offer facilities such as array attributes to make programming simpler - and especially program maintenance.
Arrays are fixed in length when you create them, but that length doesn't have to be determined until then. So you can declare an array and initialise it with a function call - it takes its size from the result of the function call.
Use this facility and eliminate size as a separate thing altogether.
When the declare block ends, the array goes out of scope, so it's freed automatically, and if it's in a loop, the function call can return a different size string, so the array bounds can be different each time - to problem.
We need to loop over all characters in the array, but we don't know its size ... so just ask it, for example via its range attribute.
And rather than writing every test twice, use a function from the Ada.Characters.Handling package to ensure we only hve to deal with lower case.
Also, if statements are a poor choice for this task, a case statement is simpler and shorter - I've shown both below
with Ada.Text_IO;
use Ada.Text_IO;
with Ada.Characters.Handling;
procedure MorseCode is
begin
Put("Enter the sentence you would like to convert to morse code: ");
declare
Sentence : String := Get_Line;
begin
Put_Line(Sentence);
for i in Sentence'range loop
if Ada.Characters.Handling.To_Lower(Sentence(i)) = 'a' then
Put(".- ");
-- elsif etc ... not very nice
end if;
case Ada.Characters.Handling.To_Lower(Sentence(i)) is
when 'b' => Put("-...");
when 'c' => Put("-.-.");
when others => null;
end case;
end loop;
end;
end;
There are also bounded and unbounded strings which you don't need in this simple example. They are a little tedious to use sometimes, you need type conversions between them and plain Strings.

Related

Printing array of integers generates weird output in Ada

I created a simple Ada program that allows a user to populate an array with a maximum of 100 non negative and non zero integers and then prints them out. When I call the function to print out the numbers it prints them out but at the same time it also prints out a bunch of strange and seemingly random numbers. What mistake have I made in my code that is causing the program to output such strange results? This is my first time writing in Ada. For example when I populate the empty array with the numbers [1,2,3,4,5] is prints out this:
1
2
3
4
5
32624
911328835
32624
911328836
32624
67043328
134217726
134217726
2013265921
134217726
134217726
134217726
67043328
909181968
32624
2114692683
89452
914381552
32624
1543503876
2
14
2
14
I am using the gnatmake compiler on ubuntu and when compiling the source code it doesn't give me any error/warning messages.
Here is my source code, I know that I probably don't need to use seperate functions but I implemented them anyways for learning purposes.
with Ada.Containers.Vectors;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
use Ada.Containers;
procedure thing is
type Vector is array (1..100) of Integer;
A: Vector;--array doesn't need to be completely filled up
K: Integer;
--array filling function below
function mkarr return Vector is --asks user to fill empty array with positive and non zero integers
begin
Ada.Text_IO.Put ("enter numbers to fill array, negative or 0 will stop process: ");
for I in 1..100 loop
Ada.Integer_Text_IO.Get(K);
if K>0 then
A(I) := K;
end if;
if K<=0 then
return A;
end if;
end loop;
return A;
end;
--array printing prodcedure below
procedure printarr(A: in out Vector) is
begin
for I in A'Range loop
if A(I)>0 then
Ada.Integer_Text_IO.Put(A(I));
New_Line(1);
end if;
end loop;
end printarr;
B: Vector := mkarr;
--main method below
begin
printarr(A);
end thing;
In mkarr, you use a 0 or negative value to mark the end of the input, but you don't store that value in the array. If whatever garbage is stored in the array after the end of the values you entered happens to be positive, there's no way to tell that it's not a valid value.
In printarr, if you encounter a 0 or negative value you don't print it -- but you continue printing the remaining positive values (which are garbage).
If you store a sentinel value in mkarr, and quit printing in printarr when you encounter the sentinel, the program should work.
Some other notes:
A and K are used only inside mkarr. They should be local to mkarr.
You never use Ada.Containers.Vectors. You can delete the corresponding with and use directives.
For more advanced usage, you can have mkarr return an array containing only the entered data, by making Vector an unconstrained array type and returning a slice. It's still easier to define a fixed-length array inside mkarr. Allowing arbitrarily many inputs is tricker -- but Ada.Containers is probably a good way to do that. (Disclaimer: I haven't looked at Ada.Containers.)
Expanding on Keith’s answer:
You want a variable-length result, so the more Ada-like way is to use an "unconstrained array":
type Vector is array (Positive range <>) of Integer;
This means that you can create instances of the array of any size you like, so long as the bounds are positive: 1 .. 100, 42 .. 43 etc. You can even create a zero-length array by specifying the last bound (called ’Last) to be less than the first (called ’First). In this special circumstance, you’re allowed to use a ’Last outside the range of the index type (in this case, 0, or even -42 if you want to confuse people!).
function mkarr return Vector is
We have to have an actual array to accumulate the values, so set an upper limit of 100.
Result : Vector (1 .. 100);
We need to know how many elements there are.
Last : Natural := Result'First - 1;
More or less as before,
K : Integer;
begin
Ada.Text_IO.Put
("enter numbers to fill array, negative or 0 will stop process: ");
for I in Result'Range loop
We can return an array with at most Result’Length elements.
Ada.Integer_Text_IO.Get (K);
if K > 0 then
We can add one more element.
Last := Last + 1;
Result (Last) := K;
else
There are fewer than 100 elements, so return just that slice.
return Result (Result'First .. Last);
end if;
end loop;
We’ve accumulated 100 results, so return all of them.
return Result;
end mkarr;
As noted here, your example mentions Ada.Containers.Vectors with no further reference to the package. If you pursue such a solution, discussed here, you might first instantiate the generic package Vectors to get a resizable container of integers:
package Integer_Container is new Vectors (Natural, Integer);
Your function to make an array can then declare a Vector named A and initialize its elements and length to specific values:
function Make_Array (Size : Count_Type) return Vector is
A : Vector := To_Vector (New_Item => -1, Length => Size);
…
You can use Iteration in Ada 2012 to simplify your data collection in Make_Array:
for E of A loop
Get (K);
if K > 0 then
E := K;
else
exit;
end if;
end loop;
Similarly, a procedure Print_Array (A : in Vector) might loop like this:
for E of A loop
Put (E, 4);
end loop;
A typical usage might look something like this:
Print_Array (Make_Array (42));
Additional details may be found here and here.

How do i get an access to an element in a string that is in the array in Pascal?

I have a programm that reads an array of letters (it can be any text). Then I need to compare the 1st and the 4th element of each line of code but the programm doesn't allow me to do this. How can I get an access to those elements in order to compare them?
Program acmp387;
uses crt;
var
n, i, answer : integer;
letters : array[1..1000] of string;
Begin
read(n);
for i:=1 to n do
begin
read(letters[i]);
if ord(letters[i][1]) = ord(letters[i][4])
then answer := answer + 1;
end;
writeln(answer);
readkey;
End.
I'm interested in this line:
if ord(letters[i][1]) = ord(letters[i][4])
Your access is OK (if all strings have at least four characters, for strings with 0 to 3 characters there may be an error/message). May be you have a problem to run your program and it does not behave as expected.
Your program will work as expected if you replace the read statements by readln. A read statements makes sense only in limited situations, in interactive programs you will almost always use readln. With these changes and the input
5
abcdef
abcabc
0101010101010101
10011001
123456
you will get the result display 2 (the lines/strings abcabc and 10011001 meet the criterion and will increment answer).

Eiffel algorithm: First Repeated Character

I'm programming in Eiffel for a school Lab, and one of the tasks is to find a bug in a given algorithm. The algorithm returns the first repeated character.
The algorithm works as follows:
word: STRING
first_repeated_character: CHARACTER
local
i: INTEGER
ch: CHARACTER
stop: BOOLEAN
do
from
i := 1
Result := '%U'
until
i > word.count or stop
loop
ch := word[i]
if ch = word[i + 1] then
Result := ch
stop := true
end
i := i + 1
end
end
I spent the last couple of hours trying to find the bug in this, but it always passes all tests.
Any help would be much appreciated. Thanks.
I think the bug is that the code forgets to check if i + 1 is a valid index for word.
What about this code?
first_repeated_character (word: STRING): CHARACTER
-- First repeated character in `word'.
-- If none, returns the null character '%U'.
local
i, n: INTEGER
ch: CHARACTER
do
from
i := 1
n := word.count
until
Result /= '%U' or i > n
loop
ch := word [i]
if i + 1 <= n and then ch = word [i + 1] then
Result := ch
end
i := i + 1
end
end
Whilst trying to find a bug by searching for tests is a legitimate approach (and Eiffel Studio includes AutoTest, which does this systematically for you), it's a brute-force approach.
Here is a more intelligent algorithm which will find the bug in this case:
Examine every feature call in the routine under consideration. List all its preconditions (plus class invariants). Consider if there are any conditions for this routine that will fail to meet any of these preconditions or invariants.
If you find such a set of conditions, then you have probably found a bug (if the preconditions are overly strict, then the routine might work anyway - but in that case you have found another bug). You can then write a test case for these particular conditions to demonstrate the bug (and validate your reasoning).
Using this algorithm, you will locate the bug in this case (just like Jocelyn did).

How to pass array of shortstring to a method

I would like to make a procedure that take array of shortstring as argument
procedure f(const a, b: Array of shortstring);
I would like to call this with arrays of known length and shortstrings of known length e.g.
var
A, B: array[1..2] of string[5];
C, D: array[1..40] of string[12];
begin
f(A,B);
f(C,D);
end;
This result in an compiler error E2008 Incompatible types.
Why is that? Can I write a procedure that can take arrays of shortstring (any length of arrays/strings)?
Why use shortstring?
The shortstings are fields in an existing record. There are alot of these record with thousand of shortstrings. In an effort to migrate data from turbo power B-Tree Filer to SQL databases one step is to convert the record to a dataset, and the back to a record, to confirm all fields are converted correctly both directions. I have been using CompareMem on the records to check this, but it does not provide enough information as to which field a conversion error is in. Thus a small program was created, which from the record definition can generate code to compare the two records. It was for this code generator I needed a function to compare shortstrings. It ended up using CompareMem on the shortstrings.
A ShortString is 0 to 255 characters long. The length of a ShortString can change dynamically, but memory is a statically allocated 256 bytes, the first byte stores the length of the string, and the remaining 255 bytes are available for characters, whilist string[5] declared in this way allocate only as much memory as the type requires (5 byte + 1 byte for length).
you could use type
type
MyString = string[5];
...
procedure f(const a, b: Array of MyString);
...
var
A, B: array[1..2] of MyString;
begin
f(A,B);
end;
In a similar situation I've used the following:
type
TOpenArrayOfOpenString = record
strict private
FSizeOfString: Integer;
FpStart: PChar;
FArrayLength: Integer;
function GetItemPtr(AIndex: Integer): PShortString;
public
constructor Init(var AFirstString: Openstring; AArrayLength: Integer);
function Equals(const AArray: TOpenArrayOfOpenString): Boolean;
property SizeOfString: Integer read FSizeOfString;
property pStart: PChar read FpStart;
property ArrayLength: Integer read FArrayLength;
property ItemPtrs[AIndex: Integer]: PShortString read GetItemPtr; default;
end;
{ TOpenArrayOfOpenString }
constructor TOpenArrayOfOpenString.Init(var AFirstString: Openstring; AArrayLength: Integer);
begin
FSizeOfString := SizeOf(AFirstString);
FpStart := #AFirstString[0]; // incl. length byte!
FArrayLength := AArrayLength;
end;
function TOpenArrayOfOpenString.Equals(const AArray: TOpenArrayOfOpenString): Boolean;
begin
Result := CompareMem(pStart, AArray.pStart, SizeOfString * ArrayLength);
end;
function TOpenArrayOfOpenString.GetItemPtr(AIndex: Integer): PShortString;
begin
Result := PShortString(pStart + AIndex * SizeOfString);
end;
You could use it like this:
procedure f(const a: TOpenArrayOfOpenString);
var
i: Integer;
begin
for i := 0 to Pred(a.ArrayLength) do
Writeln(a[i]^);
end;
procedure Test;
var
A: array[1..2] of string[5];
C: array[1..40] of string[12];
begin
f(TOpenArrayOfOpenString.Init(A[1], Length(A)));
f(TOpenArrayOfOpenString.Init(C[1], Length(C)));
end;
It's not as elegant as a solution built into the language could be and it is a bit hacky as it relies on the fact/hope/... that the strings in the array are laid out contiguously. But it worked for me for some time now.
type
shortStrings =array[1..2] of string[5];
...
a,b : shortString;
..
procedure rock(a,b : shortStrings);
..
You are combining two different kinds of open array.
First, there is the classic Turbo Pascal "string" (also called "openstring" in Delphi IIRC) which is essentially string[255]. As string[255] is a superset of all shortstrings, the open array aspect simply converts all shortstring types to it.
The "array of xx" syntax is the Delphi (4+?) open array. It is an open array of any type, not just strings, and the syntax to call it is f(nonarrayparam,[arrayelement0,arrayelement1]);
Somehow you seem to mix both syntaxes, and even aggrevate it by adding CONST which sollicits pass by reference and excludes conversions.
I think you assume shortstring has an performance advantage. It has, in some cases. Open array is not one of those cases. Not even in TP :-)

How can I assign a value to a Char array?

Say I have the variable:
Var question : array[1..50] of char;
When I do:
question := 't'; //What is the correct way to change the value?
It returns an error:
Incompatible types: 'array[1..50] of Char' and 'Char'
Note: I want to have a max string size of 50 chars, not 50 different chars.
The reason for this question is that I have a record in another unit(This is just a basic example, not what I actually have written above) In that unit I have a Record, which I can't use the string data type in(Or is there a way? please explain if there is). I just need to know how to give an array of chars a value.
While Delphi strings and arrays of char are related, they are not the same.
Delphi overloads assignment of strings and literals (char and string) to array of chars, but only when the lower array bound is zero.
The following code works for me in D2007 and Delphi XE:
var x : array[0..49] of char;
begin
x:='b'; // char literal
x:='bb'; // string literal
end.
If I change the [0 to [1 it fails. This limitation probably simplifies the language helper that takes care of this, and probably the feature is only meant for dealing with converted C structs where arrays always have lower bound 0.
Are you sure that you can't use string data type in a record?
Anyways...
type
TCharArray = array[Char] of Char;
function StringToArray(Str: string): TCharArray;
begin
FillChar(Result, High(Byte), #0);
Move(Str[1], Result, length(Str));
end;
procedure TestCharArray;
var
question: TCharArray;
begin
question := StringToArray('123');
ShowMessage(PChar(#question));
end;
Also take a look at StrPCopy function.
If you don't need unicode characters, you should just define your string like string[50].
After that you don't need any functions or conversions to work with that string, and it'll be just as easy to read and write it to a file.
Hscores = record
var
_topscore : integer;
_topname : string[50];
end;
I'm pretty sure you can use strings in record types.
This blog entry shows an example: http://delphi.about.com/od/beginners/a/record_type.htm
In order to assign a value to the Char array, you have to index it, like any other array:
question[1] := 't';

Resources