Printing arrays elements using recursion - ada

I am learning Ada programming language and I can admit that it's strongly typed. In this code, I am trying to ask the user to enter 4 integers and then by using recursion, print out these numbers. However, I am facing trouble writing a recursion subprogram for it and wonder if I can get any suggestions on how to write it correctly?
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Dugga is
Y: Natural;
type arr is
array (1..4) of Natural;
A:arr;
function Recurs(Item: in Natural) return Natural is
begin
if Item >= 1 then
return Get(Item ); --Error: Context requires function call, found procedure name
end if;
end Recurs;
begin
Y:=Recurs(arr); --Error: expected type "Standard.Integer. Found type: arr
end Dugga;
Warnings: Program_error may be raised at run time
"return" statement missing following this statement
Error: Missing argument for parameter "Item" in call to get

You appear to be confused about what a recursive subprogram is. A recursive subprogram calls itself unless a termination condition is reached.
If your subprogram calls itself before printing the array element the array elements will be printed in reverse order. If your subprogram calls itself after printing the array element the array elements will be printed in the order they are entered.
with Ada.Text_Io; use Ada.Text_IO;
procedure Main is
subtype Idx is integer range 1..4;
type Arr is array (Idx) of Integer;
procedure Print(Nums : Arr; Index : Idx) is
begin
Put (Nums(Index)'Image);
if Index = Nums'Last then
New_Line;
else
Print(Nums, Index + 1);
end if;
end Print;
A : Arr := (1, 2, 3, 4);
begin
Print(A, A'First);
end Main;
The program above declares an array type and creates an instance of that array type.
The procedure Print is a recursive procedure that prints the value of an array at the specified index value. The procedure then checks the index value. If the index value is the last index value for the array the procedure calls New_Line, otherwise it calls Print with second parameter being Index + 1.

For any recursive algorithm you need to have some sort of control logic (IF, CASE, etc.) that decides between calling the function again with a different input (and returning that result with the potential result of this call) or otherwise returning a final value.
A couple of things.
You say our end game is to use recursion to "print" the values, so your recursive operation only needs to be a procedure. It doesn't need to return anything, it just needs to print something each time it runs. One way to do this is print the first element of the array and then recall the procedure with just the remaining elements. If there are no elements, don't print anything and leave the procedure.
In order to do this, I would recommend passing in the whole array instead of a single value (like your version does). Each time you recursively call the function, you shorten the array until there is no array left. To do this though, you need to adjust your array type to be any potential size, not just 1..4, so that you can use that to end the recursion.
Start with making an array type:
type Natural_Array is array (Positive range <>) of Natural;
Now change the specification of your function to a procedure that uses that type.
procedure Recurse(Numbers: in Natural_Array);
The last part is implementing the recursion:
procedure Recurse(Numbers: in Natural_Array) is
begin
-- See if the array has any elements first
if Numbers'Length > 0 then
-- There are elements in the array, so print the
-- first one. This is why we use Numbers'First
-- as the index
Put_Line(Numbers(Numbers'First)'Image);
-- Now that we printed the first number, lets
-- only pass in the remaining elements in the array
-- here. This is where the "recursion" happens
-- as it calls this function again with a reduced
-- input set. Eventually we'll pass in an
-- empty array once the last element is printed
-- above.
Recurse(Numbers(Numbers'First+1..Numbers'Last));
else
-- Here there weren't any eleemnts in the array
-- left, so lets just end the recursion by
-- doing nothing
null;
end if;
end Recurse;
Here's the full compilable example:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
type Natural_Array is array (Positive range <>) of Natural;
procedure Recurse(Numbers: in Natural_Array) is
begin
-- See if the array has any elements first
if Numbers'Length > 0 then
-- There are elements in the array, so print the
-- first one. This is why we use Numbers'First
-- as the index
Put_Line(Numbers(Numbers'First)'Image);
-- Now that we printed the first number, lets
-- only pass in the remaining elements in the array
-- here. This is where the "recursion" happens
-- as it calls this function again with a reduced
-- input set. Eventually we'll pass in an
-- empty array once the last element is printed
-- above.
Recurse(Numbers(Numbers'First+1..Numbers'Last));
else
-- Here there weren't any eleemnts in the array
-- left, so lets just end the recursion by
-- doing nothing
null;
end if;
end Recurse;
A : Natural_Array(1..4) := (1,2,3,4);
begin
Recurse(A);
end Hello;

Related

How can you store (an access to) Integer's operators in Ada?

In Ada, the context can determine that "+" is not a String but an integer operator, as in the expression: "+"(5,2). The question is, how do I store that operator in a variable? I want to pass that integer operator, or some other one, as a binary function taking two Integers and returning an Integer. In the code below, I made an explicit function that just calls the operator, which I can use as a workaround. Is there some way to avoid having this wrapper, and pass around (an access to) Integer's "+" operator directly?
with Ada.Text_IO; use Ada.Text_IO;
procedure operator is
type binary_int_operator is access function(lhs : Integer; rhs : Integer) return Integer;
--plus : binary_int_operator := Integer."+"'Access;
--plus : binary_int_operator := Integer'Access("+");
--plus : binary_int_operator := Integer'"+";
--plus : binary_int_operator := "+";
function plus(lhs : Integer; rhs : Integer) return Integer is
begin
return lhs + rhs;
end plus;
begin
Put_Line(Integer'Image("+"(5, 12)));
end operator;
The commented declarations show some attempts I made, which do not compile.
I'm afraid you can't do that. The "+" subprogram for Integer is defined in the package Standard [ARM A.1 (17)] and therefore intrinsic [AARM A.1 (2.a)]. It's not allowed to reference an intrinsic subprogram [ARM 3.10.2 (32.3)]. Hence, compiling the program
procedure Main is
type Binary_Int_Operator is
access function (lhs : Integer; rhs : Integer) return Integer;
Plus : Binary_Int_Operator := Standard."+"'Access;
begin
null;
end Main;
yields
6:34 prefix of "Access" attribute cannot be intrinsic
The only workaround is using an indirection. This program compiles
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main_Alt is
type Operation is
access function (Lhs, Rhs : Integer) return Integer;
-- Sticking to "+" and "-" instead of names like Add or Subtract
-- to demonstrate that you can reference operator subprograms
-- (using the Access attribute) as long as they're not intrinsic.
function "+" (Lhs, Rhs : Integer) return Integer is
(Standard."+" (Lhs, Rhs));
function "-" (Lhs, Rhs : Integer) return Integer is
(Standard."-" (Lhs, Rhs));
procedure Calc_And_Show (Lhs, Rhs : Integer; Op : Operation) is
begin
Put (Op (lhs, rhs));
New_Line;
end Calc_And_Show;
begin
Calc_And_Show (5, 3, "+"'Access);
Calc_And_Show (5, 3, "-"'Access);
end Main_Alt;
and yields (as expected)
$ ./main_alt
8
2
I would suggest considering a different approach using generics.
Generally, I think you end up with a simpler interface for the call then you
get trying to pass in access to subprogram parameters.
(i.e. no need to pass the operation for each call).
Using generics, you don't need to use 'Access at all, and you can pass intrinsic functions such as integer "+", as formal generic parameters.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
generic
with function Op (L, R : Integer) return Integer;
procedure Calc_And_Show (Lhs, Rhs : Integer);
procedure Calc_And_Show (Lhs, Rhs : Integer) is
begin
Put (Op (lhs, rhs));
New_Line;
end Calc_And_Show;
procedure Calc_And_Show_Plus is new Calc_And_Show (Op => "+");
procedure Calc_And_Show_Minus is new Calc_And_Show (Op => "-");
begin
Calc_And_Show_Plus (5, 3);
Calc_And_Show_Minus (5, 3);
end Main;
There might be reasons why you'd want to use access parameters instead, such as if you wanted Calc_And_Show to be callable from other languages such as C, or if you are in a nested level of code and all you have passed to your nested level is an access to subprogram value. But I think it's generally a good idea to otherwise use generics or at least consider that option as a first preference, unless you have good reason not to.

I am unable to create a plsql function inside a while loop.Is it possible to write plsql function inside while loop?

create or replace FUNCTION ACHEHBBDA40(p_sum_date IN VARCHAR2) RETURN NUMBER AS
CURSOR BISNES_T_INFO IS
SELECT a.TARGETID, a.CLIENTID,b.BSNSID
FROM CLIENT_XREF_T a
INNER JOIN BISNES_T b
ON
a.CLIENTID = b.CLIENTID AND
a.TYPE = '1' AND
b.WORKKBN = '0';
BEGIN
DBMS_OUTPUT.PUT_LINE('PGM_NAME');
LOOP
BEGIN
FETCH BISNES_T_INFO
INTO l_compid,l_bsnsid,l_clientid;
--beginning of nested function in declaration section
FUNCTION getClientXref(l_clientid VARCHAR2) RETURN Number AS
CURSOR CLIENT_XREF_T_INFO IS
SELECT d.BRNO, d.TRNO
FROM CLIENT_XREF_T x INNER JOIN CLIENT_T c ON x.CLIENTID = c.CLIENTID;
BEGIN
OPEN CLIENT_XREF_T_INFO;
FETCH CLIENT_XREF_T_INFO
INTO l_brno,l_trno
END;
return 1;
END getClientXref;
UTL_FILE.PUT_LINE(v_filehandle,l_compid ||CHR(9)|| l_brno ||CHR(9)|| l_trno);
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Close the file after the process is over
DBMS_OUTPUT.PUT_LINE('MSGID_ERREND : ' ||commonUtilities.GC_MSGID_ERREND);
DBMS_OUTPUT.PUT_LINE('MSG_ERREND : ' ||commonUtilities.GC_MSG_ERREND);
DBMS_OUTPUT.PUT_LINE('MSGID_NO_DATA : ' ||commonUtilities.GC_MSGID_NO_DATA);
DBMS_OUTPUT.PUT_LINE('MSG_NO_DATA : ' ||commonUtilities.GC_MSG_NO_DATA);
RETURN lc_failure;
EXIT;
END;
END LOOP;
When i create the nested function getClientXref inside the while loop its throws the compile time error "Error(213,21): PLS-00103: Encountered the symbol "GETCLIENTXREF" when expecting one of the following: := . ( # % ; " in sql developer.
Honestly I don't understand the purpose of the code you posted: it never calls the function you are trying to declare... But, since your question is about syntax, I have to correct what all other posters and commentators are saying:
You CAN declare procedures and functions inside a loop
The following code works!
begin
for c in (select * from dict) loop
DECLARE
-- procedure inside a loop
procedure local_print_current_row is
begin
-- here I am even accessing the external
-- "c" for loop variable
dbms_output.put_line(c.table_name || ' -> ' || c.comments);
end;
BEGIN
local_print_current_row;
END;
end loop;
end;
of course this is just a "toy" example to illustrate the syntax, but PL/SQL allows you to nest declarations (not only of variables) almost everywhere using the declare/begin/exception/end construct.
in your code you wrote the comment "--beginning of nested function in declaration section ", but you didn't actually define any nested declaration section. you need the DECLARE keyword.
A lot of people do not realize that in PL/SQL "begin/end" is not simply the same of "{"/"}" in java. The complete syntax of the begin/end block allows all these parts:
DECLARE
<declarations>
BEGIN
<code>
EXCEPTION
<exception handlers>
END
It is just optional to write the "DECLARE" and "EXCEPTION" sections, but the BEGIN/END block is actually made of all the above parts.
in any DECLARE section you can declare:
function and procedures
types
cursors
variables
exceptions
...
and all the things you declare in that section will be visible only within the corresponding begin[/exception]/end sections.
Moreover you can nest other blocks wherever you can write actual "runnable" code. This kind of nesting can be done:
declare
...
begin
declare
procedure MyLocalProc is
procedure NestedProc is
begin
end;
begin
....
declare
...
begin
...
exception
..
end
...
exception
end
begin
...
end
exception when others then
declare
...
begin
...
end
end
P.S.:Note that after a "procedure is" or "function ... is" the "DECLARE" section is implicit: this is why you can start declaring stuff immediately after a procedure/function declaration without writing "declare". This does not happen for triggers, where you actually have to write "declare" if you want to add local declarations.
I don't really get it why you have to create a function inside your while loop. Functions were not really meant for that kind of approach. You can just create your function separately and call it each time you needed inside your while loop and pass your parameters.

PLS-00487 Error-Invalid reference to Variable 'CHAR'

I'm designing a function that is part of a larger package. The function is intended to take a District Code and return a collection of unique IDs for 10-15 stores that are assigned to that district. The function is intended to return a collection that can be queried like a table, i.e., using the TABLE function in a SQL statement.
I've created the following Types:
Schema Level type:
create or replace TYPE HDT_CORE_ORGIDS AS TABLE OF CHAR(20);
and a Type inside the Package
TYPE CORE_ORGIDS IS TABLE OF CHAR(20) INDEX BY BINARY_INTEGER;
Here's the function code:
FUNCTION FindDistrictOrgs(
ParamOrgCode VARCHAR2
)
RETURN HDT_CORE_ORGIDS
AS
ReturnOrgs HDT_CORE_ORGIDS := HDT_CORE_ORGIDS();
FDOTemp HDT_CORE_MAIN.CORE_ORGIDS;
i BINARY_INTEGER := 0;
CURSOR FDOCurr IS
SELECT org.id AS OrgID
FROM tp2.tpt_company org
WHERE LEVEL = 2
START WITH org.name = ParamOrgCode
CONNECT BY PRIOR org.id = org.parent_id;
BEGIN
OPEN FDOCurr;
LOOP
i := i +1;
FETCH FDOCurr INTO FDOTemp(i);
EXIT WHEN FDOCurr%NOTFOUND;
END LOOP;
IF FDOTemp.EXISTS(FDOTemp.FIRST) THEN
ReturnOrgs.EXTEND(FDOTemp.LAST);
FOR x IN FDOTemp.FIRST .. FDOTemp.LAST LOOP
ReturnOrgs(x) := FDOTemp(x).OrgID;
END LOOP;
END IF;
CLOSE FDOCurr;
RETURN ReturnOrgs;
END FindDistrictOrgs ;
I'm getting the PLS-00487:Invalid Reference to variable 'CHAR' at the line:
ReturnOrgs(x) := FDOTemp(x).OrgID;
I've double-checked at the value returned by the SQL (the org.id AS OrgID) is of the CHAR(20 BYTE) datatype.
So...what's causing the error?
Any help is appreciated! :)
OrgID is the alias you gave the column in your cursor, it has no meaning to the collection. Since both collections are of simple types you should just be doing:
ReturnOrgs(x) := FDOTemp(x);
The syntax you're using is implying FDOTemp is a collection of objects and you're trying to reference the OrgID attribute of an object; but since CHAR isn't an object type, this errors. The error message even makes some sense when viewed like that, though it's not terribly helpful if you don't already know what's wrong... and not entirely helpful when you do.
Incidentally, you could use a bulk collect to populate the collection without the cursor or loops, or the extra collection:
SELECT org.id
BULK COLLECT INTO ReturnOrgs
FROM tp2.tpt_company org
WHERE LEVEL = 2
START WITH org.name = ParamOrgCode
CONNECT BY PRIOR org.id = org.parent_id;
RETURN ReturnOrgs;

Return ref_cursor from for loop of cursor object

I am trying to get a ref_cursor to be assigned to a variable inside a for loop then returned at the end of a function. The loop in going through a local cursor if it gets more than 1 result.
I have noted where the error occurs in the code. I am not sure how to create a loop where i can get a ref_cursor for the current point in the loop, assign it to a variable and then return it to the function. Could someone someone help me figure out how to do that? Below is my i-th attempt at logic based of reading around the google searches.
The error is "PLS-00382: expression is of wrong type" and i know that i obviously am not assigning the correct variably type based on this error but the code below with the error is an illustration of what I want to do and what I need help accomplishing.
FUNCTION GET_PARCEL(p_lat in number, p_long in number) return sys_refcursor
IS
v_distance number(10) := 100000000;
v_shortest_dist number(10) := v_distance;
v_centroid SDO_GEOMETRY;
v_rc_ref_cursor sys_refcursor;
v_ref_geom SDO_GEOMETRY := mdsys.SDO_GEOMETRY(2001, 8311, NULL, SDO_ELEM_INFO_ARRAY(1, 1, 1), SDO_ORDINATE_ARRAY(120.3214, -10.7088));
cursor query_cursor is select * from PARCEL_TABLE where code = 20134;
BEGIN
for query_row in query_cursor loop
v_centroid := SDO_GEOM.SDO_CENTROID(query_row.geometry, 0.05);
IF (v_centroid is not null) then
v_distance := SDO_GEOM.SDO_DISTANCE(v_centroid, v_ref_geom, 0.05);
IF v_distance < v_shortest_dist THEN
v_shortest_dist := v_distance;
v_rc_ref_cursor := query_row; -- Error on this line
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE('Centroid is not initialised for some reason.');
END IF;
end loop;
return v_rc_ref_cursor;
END;
As far as I know, you cannot build up a cursor. Cursors are created and maintained by the database with connections to their source data, transaction and session context, and the like.
I would suggest you declare a type, instantiate it, build up the values in the type.
When you are done, create a cursor by selecting * from table (cast (variable as your_type)).
Munge the cursor into a ref_cursor and return that.
Pro tip is to remember you have data in your table and you can use it for logic. Turns out if I use the IDENTIFIER or ROWID of the row/s which i want then i can do a where clause into a ref cursor that looks up by IDENTIFIER or ROWID.
eg.
open v_rc_ref_cursor for select * from PARCEL_TABLE n where n.identifier = query_row.identifier;
super simple stuff :)

dynamic array size determined at runtime in ada

Is it possible to have an array with size that is determined at runtime like so,
Procedure prog is
type myArray is array(Integer range <>) of Float;
arraySize : Integer := 0;
theArray : myArray(0..arraySize);
Begin
-- Get Array size from user.
put_line("How big would you like the array?");
get(arraySize);
For I in 0..arraySize Loop
theArray(I) := 1.2 * I;
End Loop;
End prog;
Is there a way to achieve this result other than using dynamically Linked Lists or another similar structure? Or is there a simple built in data structure that would be simpler than using dynamically linked lists?
Sure, declare it in a block as follows:
procedure prog is
arraySize : Integer := 0;
type myArray is array(Integer range <>) of Float;
begin
-- Get Array size from user.
put_line("How big would you like the array?");
get(arraySize);
declare
theArray : myArray(0..arraySize);
begin
for I in 0..arraySize Loop
theArray(I) := 1.2 * I;
end Loop;
end;
end prog;
or pass the arraySize as an argument into a subprogram and declare and operate on it in that subprogram:
procedure Process_Array (arraySize : Integer) is
theArray : myArray(0..arraySize);
begin
for I in arraySize'Range Loop
theArray(I) := 1.2 * I;
end Loop;
end;
This is just illustrative (and not compiled :-), as you need to deal with things like an invalid array size and such.
Yes, you can defer the declaration of a constrained object until you know the size. In this example, the array Candidates can be allocated in a nested block (introduced by the keyword declare) or on the heap (using the keyword new). In this related example, Line has a different size each time through the loop, depending on what Get_Line finds.

Resources