Pascal - doubly linket list - list

I'm trying compile Doubly linked list but I have one small problem with this part of code :
It's a "insert after"
When I insert data to the list : 1 2 3 4 5 and want insert after number 4 for example number 9, number 9 is inserted like this : 1 9 2 3 4 5. I really dont know where I have wrong code:(
everytime i insert new number to second position..
procedure insertAfter(var L:Plist; n_p:integer);
var novy_za:Plist;
begin
new(novy_za);
novy_za^.info:=n_p;
novy_za^.next:=L^.next;
novy_za^.prev:=L;
L^.next:=novy_za;
if novy_za^.next<>nil
then novy_za^.next^.prev:=novy_za;
end;
procedure call :
begin
writeln;
write('which number insert : '); readln(x);
writeln;
write('insert after : '); readln(Y);
InsertAfter(P,x);
end;

Here's a corrected version of your routine. Your original failed to accept the value you want to insert after as an argument. I made it a function so that it returns the pointer to the new added element if it was able to find the one you wanted to insert after. Otherwise, it returns nil. You call it with a second argument which tells it what value you want to insert after.
function insertAfter(var L: Plist; n_a, n_p: integer): Plist;
var novy_za, cur_za: Plist;
begin
cur_za := L;
novy_za := nil;
{ Traverse the list, looking for value n_a and, if found, insert the new
element n_p after it }
while cur_za <> nil do begin
{ Insert the new element after the one with value n_a }
if cur_za^.info = n_a then begin
new(novy_za);
novy_za^.info := n_p;
novy_za^.next := cur_za^.next;
novy_za^.prev := cur_za;
cur_za^.next := novy_za;
if novy_za^.next <> nil then
novy_za^.next^.prev := novy_za;
break; { Exit the loop }
end;
cur_za := cur_za^.next;
end;
insertAfter := novy_za;
end;
Function call (this assumes P is defined somewhere as a valid Plist, and x and y are defined as info type):
begin
writeln;
write('which number insert : '); readln(x);
writeln;
write('insert after : '); readln(y);
{ Insert the value x after the number y }
if insertAfter(P, y, x) = nil then
writeln('The value ', y, ' was not found')
else
writeln('The value ', x, ' was successfully inserted after ', y);
end;

Related

How can I optimize my code so that I dont duplicate it

I'm trying to create a procedure that puts "-" between different dates and "0" if the is single digit, but i'm having a very hard time not duplicating my code.
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 1);
Put("-");
if Date.M <= 9 then
Put("0");
end if;
Put(Date.M, Width => 1);
Put("-");
if Date.D <= 9 then
Put("0");
end if;
Put(Date.D, Width => 1);
end put;
This is the best solution I came up with
An example of a nested procedure is:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
subtype Year_Num is Integer range 1_900 .. 2_040;
subtype Month_Num is Integer range 1 .. 12;
subtype Day_Num is Integer range 1 .. 31;
type Date_Type is record
Y : Year_Num;
M : Month_Num;
D : Day_Num;
end record;
procedure Put (Date : Date_Type) is
procedure zerofill (Val : in Integer) is
begin
Put ("-" & (if (Val < 10) then "0" else ""));
Put (Item => Val, Width => 0);
end zerofill;
begin
Put (Item => Date.Y, Width => 0);
zerofill (Date.M);
zerofill (Date.D);
end Put;
A_Date : Date_Type := (2022, 12, 8);
begin
Put (A_Date);
end Main;
The nested nature of this answer is because the zerofill procedure is defined within the put procedure.
Came to this solution, I didnt duplicate my code but I somehow feel like I made it more complicated
procedure Zero(item : in integer) is
begin
Put("-");
if item < 10 then
Put('0');
end if;
Put(Item,Width =>0);
end Zero;
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 0);
Zero(Date.M);
Zero(Date.D);
end put;

Using RecordRef to Work with Multiple Tables

I have a group of tables that I need to the integer key from and I would like to be able to pass in any of them into a single and get the next value for the key.
I believe that RecordRef is the way to do this, but the code so far doesn't seem quite right.
I am trying to build a function that will take a table record and then return an integer value, that integer value will be the next record for the primary key. IE: if the last record's key is is 62825 the function will return 62826.
FunctionA
BEGIN
Id := GetNextId(SalesRecord); //Assignment not allowed
END;
FunctionB
BEGIN
Id := GetNextId(CreditMemoRecord); //Assignment not allowed
END;
GetNextId(pTableReference: RecordRef) rNextId : Integer
BEGIN
CASE pTableReference.NUMBER OF
DATABASE::SalesRecord: BEGIN
//Find last Record
pTableReference.FINDLAST;
lFieldRef := pTableReference.FIELD(1); //Set to the PK field
END;
DATABASE::CreditMemoRecord: BEGIN
//Find last Record
pTableReference.FINDLAST;
lFieldRef := pTableReference.FIELD(10); //Set to the PK field
END;
... //do more here
END; //CASE
EVALUATE(rNextId,FORMAT(lFieldRef.VALUE)); //Get the integer value from FieldRef
rNextId := rNextId + 1; //Add one for the next value
EXIT(rNextId); //return the value
END;
With this code I am getting the error "Assignment is not allowed for this variable." on the Function Call to GetNextId
Idea of the Table Structure:
Table - SalesRecord
FieldId, Fieldname, Type, Description
1 id integer PK
2 text1 text(30)
3 text2 text(30)
4 dec1 decimal
5 dec2 decmial
Table - CreditMemoRecord
FieldId, Fieldname, Type, Description
10 id integer PK
20 text1 text(30)
30 text2 text(30)
40 dec1 decmial
50 dec2 decmial
Just put function like this in both tables
GetNextId() rNextId : Integer
BEGIN
RESET;
FINDLAST;
EXIT(id+1);
END;
an then call it from record variable
FunctionA
BEGIN
Id := SalesRecord.GetNextId();
END;
FunctionB
BEGIN
Id := CreditMemoRecord.GetNextId();
END;
This is common practice I believe.
You mean "GetNextValue" get next record? I don't quite understand your use-case.
If you want to pass in a generic record, then you'll want to use the VARIANT data type. This is a wildcard type that will accept Records from any table, and allow you to return records from any table.
This is untested, but hopefully give you an idea of how they could work;
LOCAL NextRecord(VAR RecVariant : Variant)
IF RecVariant.ISRECORD THEN BEGIN
RecRef.GETTABLE(RecVariant);
// RecRef.NUMBER is useful for Database::"Customer" style comparisons
RecRef.NEXT;
RecRef.SETTABLE(RecVariant); // Might not be necessary
END;

Passing array of aliased records to a procedure

In Ada 2012, I want to have a linked list inside a declared array, rather than allocated. I want to have the linking pointers set by a procedure.
I have simplified my program to the following that demonstrates the principle I want to use but I cannot get this to compile in Gnat 4.9.2 (Debian Jessie) running on Raspi...
procedure Arr is
type Cell;
type Cell_Ptr is access all Cell;
type Cell is
record
Number : Integer := 0;
Next : Cell_Ptr := null;
end record;
type Chain is array (1 .. 100) of aliased Cell;
procedure Make_Links (Ch : in out Chain);
procedure Make_Links (Ch : in out Chain) is
begin
for I in Ch'First .. Ch'Last - 1 loop
Ch (I).Next := Ch (I + 1)'Access; -- ERROR HERE
end loop;
end Make_Links;
My_Chain : Chain;
begin
Make_Links (My_Chain);
end Arr;
I get this compiler error:
"non-local pointer cannot point to local object" at the line indicated above.
I know I'm trying to do something a bit odd but I plan on having a few other functions that perform the linking in different ways (backwards, or randomly etc) based on which procedure I pass this array of cells to.
How do I fix this code so that it compiles? Can't quite get my head around this one (I'm still a novice but enjoying the learning process).
As you are not actually allocating & freeing memory, in dont see the need for pointers. I would achieve the same functionality by doing something like this:
procedure Arr is
type Cell_Index is new Integer range 0 .. 100;
subtype Valid_Cell_Index is Cell_Index range 1 .. Cell_Index'Last;
type Cell is
record
Number : Integer := 0;
Next : Cell_Index := 0;
end record;
type Chain is array (Valid_Cell_Index) of Cell;
procedure Make_Links (Ch : in out Chain);
procedure Make_Links (Ch : in out Chain) is
begin
for I in Valid_Cell_Index'First .. Valid_Cell_Index'Last - 1 loop
Ch (I).Next := I+1;
end loop;
end Make_Links;
My_Chain : Chain;
begin
Make_Links (My_Chain);
end Arr;
This way you are still using Next as an index into the same array, and can pre-load your array with whatever linking pattern you want.
Instead of using ’Access, use ’Unrestricted_Access. This is one of GNAT’s “implementation-defined” attributes:
The Unrestricted Access attribute is similar to Access except that all accessibility and aliased view checks are omitted. This is a user-beware attribute.
I figured it out in the end. The following code is a modified version of the one in the OP. It does what I originally wanted without doing anything unpleasant...
with Ada.Integer_Text_IO, Ada.Text_IO;
use Ada.Integer_Text_IO, Ada.Text_IO;
procedure Arr is
type Cell;
type Cell_Ptr is access all Cell;
type Cell is
record
Number : Integer := 0;
Next : Cell_Ptr := null;
end record;
type Chain is array (1 .. 100) of aliased Cell;
type Chain_Ptr is access all Chain;
procedure Make_Links (CP : in out Chain_Ptr);
procedure Make_Links (CP : in out Chain_Ptr) is
begin
for I in CP'First .. CP'Last - 1 loop
CP.all (I).Next := CP.all (I + 1)'Access;
end loop;
end Make_Links;
My_Chain : aliased Chain;
My_CP : Chain_Ptr := null;
My_C : Cell_Ptr := null;
begin
My_CP := My_Chain'Access;
Make_Links (My_CP);
-- verify that the code works by writing values into the array
for I in My_Chain'Range loop
My_Chain (I).Number := 1000 * I;
end loop;
-- and read them back out using the pointer links
My_C := My_Chain (My_Chain'First)'Access;
while My_C /= null loop
Put (My_C.Number);
Put_Line ("");
My_C := My_C.Next;
end loop;
end Arr;
Instead of passing the array directly, I passed a pointer to the array instead, which Gnat seems happy with. I think what I was trying to do before was being scuppered by the "pass by copy" rules for procedure parameters.

Single linked list - delphi

I am starting to learn programming.
I have to do (HW) program where I can add in example name and surname, add it to single linked list and than display this single linke list.
I tried to do it, program is compiling and it is even working. I can add data and it is displayed. But I propably dont understan something and make mistake. I am adding first person, it is displayed, than I am adding second person, it is also displayed but the first person data is override by the second person I added. So I have two the same records. Listbox is only showing data from the single linked list so I suppose there is no problem. Each pointer should point different data so why each record is the same as the last one I added?
Here is my code:
type
wskaznik = ^Lista;
Lista = record
lp : string;
dane : string;
wsk : wskaznik;
end;
var
Form1 : TForm1;
First, current : wskaznik;
tekst : string;
liczba : string;
i, k : integer;
implementation
{$R *.lfm}
{ TForm1 }
procedure AddToList(dane : string; lp : string; var current : wskaznik);
var
prev, Next : wskaznik;
begin
if current <> nil then
begin
prev := current;
Next := current^.wsk;
end
else
begin
prev := nil;
Next := nil;
end;
new(current);
current^.dane := dane;
current^.lp := lp;
current^.wsk := Next;
if prev <> nil then
prev^.wsk := current;
end;
procedure GetAddr(dane : string; var First, current : wskaznik);
var
Next : wskaznik;
begin
if First <> nil then
begin
Next := First;
repeat
if Next^.wsk <> nil then
Next := Next^.wsk
until (Next^.wsk = nil) or (Next^.dane = dane);
current := Next;
end;
end;
procedure GetNum(n : integer; var First, current : wskaznik);
var
Next : wskaznik;
begin
if First <> nil then
if n = 1 then
current := First
else
if (n = 2) and (First^.wsk = nil) then
n := 0
else
begin
Next := First;
i := 1;
repeat
Inc(i);
if Next^.wsk <> nil then
Next := Next^.wsk
until (i = n) or (Next^.wsk = nil);
if (Next^.wsk = nil) and (i < n) then
n := 0
else
current := Next;
end;
end;
procedure List;
var
l : integer;
begin
form1.listbox1.Clear;
form1.listbox2.Clear;
for l := 1 to i do
begin
Getnum(l, First, current);
if l > 1 then
form1.listbox1.items.add(current^.dane);
form1.listbox2.items.add(current^.lp);
end;
end;
procedure findLess(dane : string; lp : string; var First, current : wskaznik);
var
tmp, Next : wskaznik;
begin
if First <> nil then
begin
Next := First;
repeat
if (Next^.wsk <> nil) then
begin
tmp := Next;
Next := Next^.wsk;
end;
until (Next^.wsk = nil) or (Next^.dane > dane);
if Next^.dane > dane then
current := tmp
else
current := Next;
if Next^.lp > lp then
current := tmp
else
current := Next;
end;
end;
procedure TForm1.Button1Click(Sender : TObject);
begin
Inc(i);
findLess(edit1.Text, edit2.Text, First, current);
addtolist(edit1.Text, edit2.Text, current);
label3.Caption := 'Elementów: ' + IntToStr(i - 1);
//edit1.SetFocus;
list;
end;
end.
You never assign anything to First (assuming Firstis to be the beginning of the list).
The first time you call AddToList you should assign Current to First

Optimisation of a Dijkstra Shortest Path Search in Delphi

I'm looking for advices to speed up my implementation of Dijkstra Shortest Path Search on a weighted graph which is a square matrix N x N. The weight on horizontal vertice is called H (resp. V on vertical ones).
A picture is worth a thousand words:
(source: free.fr)
Of course, this is part of a bigger application, but I've extracted the relevant bit here:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
N = 200; //Working on a grid of N x N, here for a quick test, in practice, it's more 10000
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
TNode = class
public
ID, //Number of the Node
origin, //From which Node did I came?
weight : integer; //The total weight of the path to Node ID
done : boolean; //Is the Node already explored?
constructor Create(myID, myOrigin, myweight: integer);
end;
var
Form1: TForm1;
implementation
var
H, V : array of integer;
{$R *.dfm}
constructor TNode.Create(myID, myOrigin, myweight: integer);
begin
ID:=MyID;
origin:=MyOrigin;
weight:=MyWeight;
end;
{------------------------------------------------------------------------------}
Function GetNodeFromID(ID: integer; NodeList: TList) : TNode; overload;
var
I: Integer;
Node: TNode;
begin
result:=nil;
for I := 0 to NodeList.count-1 do
begin
Node := NodeList[i];
if Node.ID=ID then
begin
result:=Node;
break;
end;
end;
end;
{------------------------------------------------------------------------------}
Function GetNodeOfMiniWeight(NodeList: TList) : TNode; overload;
var
I, min: Integer;
Node: TNode;
begin
result:=nil;
min :=maxint;
for I := 0 to NodeList.count-1 do
begin
Node := NodeList[i];
if Node.done then continue;
if Node.weight < min then
begin
result:=Node;
min := Node.weight;
end;
end;
end;
{------------------------------------------------------------------------------}
procedure SearchShortestPath(origin,arrival: integer);
var
NewWeight: integer;
NodeList : Tlist;
NodeFrom, //The Node currently being examined
NodeTo :TNode; //The Node where it is intented to go
s : string;
begin
NodeList := Tlist.Create;
NodeFrom := TNode.Create(origin,MaxInt,0);
NodeList.Add(NodeFrom);
while not (NodeFrom.ID = arrival) do //Arrived?
begin
//Path toward the top
if (NodeFrom.ID > N-1) //Already at the top of the grid
and not(NodeFrom.origin-NodeFrom.ID = N) then //Coming from the top
begin
NewWeight:=NodeFrom.weight + H[NodeFrom.ID-N];
NodeTo := GetNodeFromID(NodeFrom.ID-N, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID-N,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
//Path toward the bottom
if (NodeFrom.ID < N*N-N) //Already at the bottom of the grid
and not(NodeFrom.Origin- NodeFrom.ID = N) then //Coming from the bottom
begin
NewWeight:=NodeFrom.weight + H[NodeFrom.ID];
NodeTo := GetNodeFromID(NodeFrom.ID+N, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID+N,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
//Path toward the right
if not(NodeFrom.ID mod N = N-1) //Already at the extrem right of the grid
and not(NodeFrom.Origin - NodeFrom.ID = 1) then //Coming from the right
begin
NewWeight:=NodeFrom.weight + V[NodeFrom.ID];
NodeTo := GetNodeFromID(NodeFrom.ID+1, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID+1,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
//Path toward the left
if not (NodeFrom.ID mod N = 0) //Already at the extrem right of the grid
and not(NodeFrom.Origin - NodeFrom.ID = -1) then //Coming from the left
begin
NewWeight:=NodeFrom.weight + V[NodeFrom.ID-1];
NodeTo := GetNodeFromID(NodeFrom.ID-1, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID-1,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
NodeFrom.done :=true;
NodeFrom:=GetNodeOfMiniWeight(NodeList);
end;
s:='The shortest path from '
+ inttostr(arrival) + ' to '
+ inttostr(origin) + ' is : ';
//Get the path
while (NodeFrom.ID <> origin) do
begin
s:= s + inttostr(NodeFrom.ID) + ', ';
NodeFrom:=GetNodeFromID(NodeFrom.Origin, NodeList);
end;
s:= s + inttostr(NodeFrom.ID);
ShowMessage(s);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SearchShortestPath(Random(N*N),Random(N*N));
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
//Initialisation
randomize;
SetLength(V,N*N);
SetLength(H,N*N);
for I := 0 to N*N-1 do
begin
V[I]:=random(100);
H[I]:=random(100);
end;
end;
end.
The code spend most of the time in the routines: GetNodeFromID and GetNodeOfMiniWeight, and a substantial time to create nodes.
I thought that I could use a binary search, but since it requires the list to be sorted, I think that I'll loose the time in sorting the list. Any advice is welcome.
First of all, use a profiler! For instance, see http://www.delphitools.info/samplingprofiler
Your current code has several weaknesses:
It leaks memory (TNode/TNodeList instances);
You may use dynamic arrays of records instead of individual class instances for nodes (with a count stored outside);
I was not able to recognize your algorithm just by reading the code - so I guess you may enhance the code design.
The pseudo-code of this algorithm is as followed:
for all vertices v,
dist(v) = infinity;
dist(first) = 0;
place all vertices in set toBeChecked;
while toBeChecked is not empty
{in this version, also stop when shortest path to a specific destination is found}
select v: min(dist(v)) in toBeChecked;
remove v from toBeChecked;
for u in toBeChecked, and path from v to u exists
{i.e. for unchecked adjacents to v}
do
if dist(u) > dist(v) + weight({u,v}),
then
dist(u) = dist(v) + weight({u,v});
set predecessor of u to v
save minimum distance to u in array "d"
endif
enddo
endwhile
Did you try this library from DelphiForFun ? Sounds like something already proven, updated recently, and well written. May be improved (e.g. using an array of bits instead array of boolean), but sounds pretty correct for a start.
I've implemented modification of Dijkstra Shortest Path algorithm for sparsed graphs. Your graph is very sparsed (E << V^2). This code uses priority queue based on binary heap, that contains (VerticeNum, DistanceFromSource) pairs as TPoints, ordered by Distance (Point.Y). It reveals loglinear (close to linear) asymptotic behavior. Example for small graph:
Times for i5-4670
N V time, ms
100 10^4 ~15
200 4*10^4 ~50-60 //about 8000 for your implementation
400 1.6*10^5 100
1600 2.5*10^6 1300
6400 4*10^7 24000
10000 10^8 63000
//~max size in 32-bit OS due to H,V arrays memory consumption
code:
function SparseDijkstra(Src, Dest: integer): string;
var
Dist, PredV: array of integer;
I, j, vert, CurDist, toVert, len: integer;
q: TBinaryHeap;
top: TPoint;
procedure CheckAndChange;
begin
if Dist[vert] + len < Dist[toVert] then begin
Dist[toVert] := Dist[vert] + len;
PredV[toVert] := vert;
q.Push(Point(toVert, Dist[toVert]));
//old pair is still stored but has bad (higher) distance value
end;
end;
begin
SetLength(Dist, N * N);
SetLength(PredV, N * N);
for I := 0 to N * N - 1 do
Dist[I] := maxint;
Dist[Src] := 0;
q := TBinaryHeap.Create(N * N);
q.Cmp := ComparePointsByY;
q.Push(Point(Src, 0));
while not q.isempty do begin
top := q.pop;
vert := top.X;
CurDist := top.Y;
if CurDist > Dist[vert] then
continue; //out-of-date pair (bad distance value)
if (vert mod N) <> 0 then begin // step left
toVert := vert - 1;
len := H[toVert];
CheckAndChange;
end;
if (vert div N) <> 0 then begin // step up
toVert := vert - N;
len := V[toVert];
CheckAndChange;
end;
if (vert mod N) <> N - 1 then begin // step right
toVert := vert + 1;
len := H[vert];
CheckAndChange;
end;
if (vert div N) <> N - 1 then begin // step down
toVert := vert + N;
len := V[vert];
CheckAndChange;
end;
end;
q.Free;
// calculated data may be used with miltiple destination points
result := '';
vert := Dest;
while vert <> Src do begin
result := Format(', %d', [vert]) + result;
vert := PredV[vert];
end;
result := Format('%d', [vert]) + result;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
t: Dword;
I, row, col: integer;
begin
t := GetTickCount;
if N < 6 then // visual checker
for I := 0 to N * N - 1 do begin
col := I mod N;
row := I div N;
Canvas.Font.Color := clBlack;
Canvas.Font.Style := [fsBold];
Canvas.TextOut(20 + col * 70, row * 70, inttostr(I));
Canvas.Font.Style := [];
Canvas.Font.Color := clRed;
if col < N - 1 then
Canvas.TextOut(20 + col * 70 + 30, row * 70, inttostr(H[I]));
Canvas.Font.Color := clBlue;
if row < N - 1 then
Canvas.TextOut(20 + col * 70, row * 70 + 30, inttostr(V[I]));
end;
Memo1.Lines.Add(SparseDijkstra({0, n*n-1}random(N * N), random(N * N)));
Memo1.Lines.Add('time ' + inttostr(GetTickCount - t));
end;
Edit: TQPriorityQueue is class for internal use, but you can try any implementation of heap-based priority queue. For example, this one. You have to change Pointer to TPoint, Word to Integer in this module.
Edit2:
I've replaced proprietary queue method names in my procedure by BinaryHeap methods.