Remove in a simple linked list node in pascal - delphi

I have made a piece of code that works in simple linked list node in pascal. Now I need add a procedure that can delete or remove an element in my simple linked list. I have made many procedures that do test and adding but I can't figure out how to add a procedure that can remove a item. Here is my code:
type
node = ^MyRec;
MyRec = record
value: Integer;
index: integer;
next: node
end;
var
head, tail: node;
i, ix: Integer;
const
el = 14;
procedure Insert;
var
tmp: node;
ix: Integer;
begin
Randomize;
for i := 1 to el do
begin
new (tmp);
inc(ix);
tmp^.value := Random(88);
tmp^.index := ix;
tmp^.next:= nil;
if head = nil
then head := tmp
else
tail^.next := tmp;
tail:= tmp;
end;
end;
//displays the list
procedure Display;
var
tmp: node;
begin
tmp := head;
while tmp <> nil do
begin
WriteLn('Val=', tmp^.value, ' ID=', tmp^.index);
tmp := tmp^.next
end;
end;
begin
head := nil;
ReadLn;
end.

I'm going to give you some information and pseudocode so you will be able to fix this problem yourself.
You can see a linked list as a chain of items:
Head -> Item1 -> Item2 -> Item3 -> Item4 <- Tail
If you need to delete Item3, you need to change the next pointer of Item2 so it points to Item4 (The item following Item3). Then you can release Item3.
But there are some special cases:
If you want to delete Item1 (the first item), you need to change the Head pointer, so it points to Item2.
If you want to delete Item4 (the last item), you need to change the Tail pointer, so it points to Item3.
If the list contains only one item and you want to delete that, you need to change both the Head and the Tail to nil.
If the list is empty, you don't need to delete anything, but your code should not crash because of that.
So in order to fix all these, you can use 2 pointers to walk the list, (current and previous).
Current start at the first item (Head) and previous start at nil.
As long as current is not nil and it does not point to the item to be deleted, set previous to the value of current and set current to the value of the next item.
If current is nil, then the item is not found and there is nothing to delete.
If current points to the correct item, and previous is nil, you need to change the head pointer.
If current points to the correct item and previous is not nil, set the next pointer of previous to the next pointer of current.
If the next pointer of current is nil (you are at the tail), you also set the tail pointer to previous.
If all pointers are changed, you can dispose of the current item.
A side note on your code. You have an insert procedure that inserts several random elements. A better idea is to have a separate procedure to insert a single element and a separate procedure to add several items like:
procedure Insert(index, value: Integer);
var
tmp: node;
begin
new (tmp);
tmp^.value := value;
tmp^.index := index;
tmp^.next:= nil;
if head = nil
then head := tmp
else
tail^.next := tmp;
tail:= tmp;
end;
procedure FillList;
var
ix: Integer;
begin
Randomize;
for i := 1 to el do
begin
inc(ix);
Insert(ix, Random(88));
end;
end;

Related

Recursive procedure for getting child records in hierarchical dataset

I have a data table (MDTasks) that has records represented in a tree view. Each record has a unique field, 'ID', and a field 'Parent_ID', which refers to the ID of the parent. I am trying to make a list of all the children at any level of a record with a given ID. I have the following, which gets the first child at each level, but doesn't go back up to get the siblings at any level. I would be grateful for any help. Thank you.
procedure TfmList.GetChildren(TaskID: integer);
var
iChildID: integer;
begin
with MDTasks do
begin
first;
while not EOF do
begin
if FieldByName('Parent_ID').AsInteger = TaskID then
begin
iChildID := FieldByName('ID').AsInteger;
Memo1.Lines.Add(IntToStr(iChildID));
GetChildren(iChildID);
end;
next;
end;
end;
end;
The code below should do what you want, if I understand you correctly. It uses a ClientDataSet so that my answer is self contained and so that the test data can easily be set up.
It uses a call to CloneCursor to do the recursive search for the children of the specified parent node ID.
procedure TForm1.FormCreate(Sender: TObject);
begin
CDS1.CreateDataSet;
CDS1.InsertRecord([1, -1]);
CDS1.InsertRecord([2, -1]);
CDS1.InsertRecord([3, -1]);
CDS1.InsertRecord([4, 2]); // This and the following rows are all children of ID = 2
CDS1.InsertRecord([5, 2]);
CDS1.InsertRecord([6, 4]);
CDS1.InsertRecord([7, 4]);
CDS1.InsertRecord([8, 7]);
FindChildren(2);
end;
procedure TForm1.FindChildren(ParentID : Integer);
procedure FindChildrenInner(ParentID : Integer);
var
TempCDS : TClientDataSet;
ID : Integer;
begin
TempCDS := TClientDataSet.Create(Nil);
try
TempCDS.CloneCursor(CDS1, False, True);
TempCDS.First;
while not TempCDS.Eof do begin
if TempCDS.FieldByName('Parent_ID').AsInteger = ParentID then begin
ID := TempCDS.FieldByName('ID').AsInteger;
Memo1.Lines.Add(Format('ID: %d, Parent: %d', [ID, ParentID]));
FindChildrenInner(ID);
end;
TempCDS.Next;
end;
finally
TempCDS.Free;
end;
end;
begin
Memo1.Lines.BeginUpdate;
try
Memo1.Lines.Clear;
Assert(CDS1.Locate('ID', ParentID, []));
FindChildrenInner(ParentID);
finally
Memo1.Lines.EndUpdate;
end;
end;
Looking at the code, I would say that it gets all the childs at the last level (not only first one). The problem is that you "share" the dataset (MDTasks) between levels - so when the last level iterates to the end of the table and returns to the caller the while not EOF do evaluates to false (on the parent level now) and loop ends, thus only the first child is gotten on levels above the last one.
One solution would be to iterate each level fully, logging Parent_IDs into local array, then use that array to get the next level below it.
Another solution is probably to use bookmarks in the table MDTasks. Let your procedure set a bookmark at the beginning and return to this bookmark at the end so the position/cursor in MDTasks is not changed from GetChildren.
your problem is that you are calling recursive function against the same data. you can use filtering, for example:
procedure GoGoGo (Id: integer);
var
OldFilter: string;
begin
OldFilter := Table.Filter;
Table.Filter := 'ParentId = ' + IntToStr (Id);
try
Table.First;
while not Table.Eof do begin
Memo.Lines.Add (Format('ID: %d, Parent: %d',
[Table.FieldByName ('I'd).AsInteger,
Table.FieldByName ('ParentID').AsInteger]));
GoGoGo (Table.FieldByName ('Id').AsInteger);
if Table.Locate ('Id', Id, []) then
Table.Next;
end;
finally
Table.Filter := OldFilter;
end;
end;
<...>
Memo.Lines.Clear;
Table.Filtered := TRUE;
Table.First;
GoGoGo (Table.FieldByName ('Id').AsInteger);
haven't tested this code, it may contain errors, but hopefully, idea is quite clear. with the same idea you can use another approaches, like create new TQuery with dynamically generated where clause, etc. I'm not a fan of such things just because in a really big tree, creating new objects in every iteration of recursive function may result in a stack overflow.
gl there ;)

delete all elements from objectlist except one selected element

What is best coding practise in deleting all elements from a list except one element which I would like to keep inside the list?
TMyCLass = Class()
....
end;
MyObjectList = TObjectList <TMyClass>;
var MySaveClass : TMyCLass;
begin
MySaveClass = MyObjectList.items[saveindex];
for i = 1... MyObjectList.count-1 do
if i <> saveindex then
MyObjectList.delete (i); ?? // not working :-)
end;
As TLama said in a comment, Extract the item, Clear the list (if OwnsObjects is false, loop through and free each item first), and then Add the item back in.
var
SavedItem: TMyClass;
...
SavedItem := MyObjectList.Extract(MyObjectList.Items[i]);
// Loop here to free if needed because list doesn't own objects
MyObjectList.Clear;
MyObjectList.Add(SavedItem);
If the Objectlist has OwnsObjects := True then you can just delete the elements.
But care to make it backwards otherwise you might get an error.
for i := MyObjectlist.count -1 downto 0 do
if i = Saveindex then
continue
else
MyObjectList.Delete(i);

Dynamic TList of TList

i have this problem: as i can add list to one list? i have tried so, but main list return always one list, and not understand where i mistake.
The structure is this:
PCombArray = array of Integer;
PStatsRecord = record
Comb: PCombArray;
Freq: Integer;
end;
PStatsList = TList<PStatsRecord>;
TStatsList = TList<PStatsList>;
Where Comb is a field that work as primary key. But here all ok. i define main list as:
var
MainList: TStatsList;
MySubList: PStatsList;
and create it, without problem. A procedure work for populate a subList; for esample i call this procedure as MakeSubList and assign a MySubList the list maked, then i add it to main list:
MainList := TList<PStatsList>.Create;
try
MainList.Clear;
for index := 1 to N do // generate N subList
begin
...
MySubList := MakeSubList(index); // contain correct sub list, no problem here
...
MainList.Add(MySubList); // add mysublist to mainlist
end;
writeln (mainList.count); // return 1, and not N-1 sublist
finally
MainList.Free;
end;
Thank who help me to understand so i can solve it. Thanks again.
Your make sub list function might be wrong, or you might have confused yourself with your naming conventions which did not follow in any way the usual Delphi/Pascal naming conventions for types.
The following code works, though.
Note that a TList is an Object so if you want to make a List of TList be sure to use TObjectList instead of plain TList unless you like memory leaks. Your inner list is around a record type and does not need to be TObjectList, but if you changed StatsRecord to TStatsData as a TObject (class) type, you should also change to TObjectList.
unit aUnit5;
interface
uses Generics.Collections;
procedure Test;
implementation
type
CombArray = array of Integer;
StatsRecord = record
Comb: CombArray;
Freq: Integer;
end;
TStatsList2 = TList<StatsRecord>;
TStatsList = TObjectList<TStatsList2>;
var
MainList: TStatsList;
MySubList: TStatsList2;
index:Integer;
procedure Test;
begin
MainList := TStatsList.Create;
try
for index := 1 to 10 do // generate N subList
begin
MySubList := TStatsList2.Create;
MainList.Add(MySubList); // add mysublist to mainlist
end;
finally
MainList.Free;
end;
end;
end.
MainList is a list of PStatsList, so you're certainly allowed to add instances of PStatsList to it. If you weren't allowed, your code would not have compiled or run at all.
If the loop runs N times, then MainList.Add will be called N times, and MainList.Count will be N. So, if WriteLn(MainList.Count) prints 1, then we can only conclude that N = 1.
TList allows duplicates, so even if MakeSubList is returning the same object each time, it can still get added to the main list multiple times.
..
implementation
type
S64 = string[64];
art_ptr = ^art_rec;
art_rec = record
sDes: S64;
rCost: real;
iQty: integer;
end;
art_file = file of art_rec;
var
lCatalog: TList;
procedure disp_all;
var
lArt: TList;
pA: art_ptr;
c,
a: integer;
begin
for c:= 0 to lCatalog.Count -1 do begin
lArt:= lCatalog[c];
for a:= 0 to lArt.Count -1 do begin
pA:= lArt[a];
Dispose(pA);
end;
lArt.Clear;
lArt.Free;
end;
lCatalog.Clear;
end;// disp_all
procedure TfrmMain.FormCreate(Sender: TObject);
begin
lCatalog:= TList.Create;
end;// FormCreate
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
disp_all;
lCatalog.Free;
end;// FormDestroy
// a normal BitButton click that adds 3 records to each art list (2)
procedure TfrmMain.bbMCreate_Click(Sender: TObject);
const
nArt = 2;
nRec = 3;
var
pA: art_ptr;
lArt: TList;
c: integer;
begin
// clears any previous added pointer record to art list that was added to the catalog
disp_all;
// creates art lists and add 'em to the catalog list
for c:= 0 to nArt -1 do begin
lArt:= TList.Create;
lCatalog.Add(lArt);
// creates records and add 'em to the art list
for a:= 0 to nArt -1 do begin
pA:= New(art_ptr);
with pA^ do begin
sDes:= Format('cat%d-art%d', [c, a]);
rCost:= (c+1) * (a +1) *1.0;
iQty:= (c+1) *10 + (a +1);
end;
lArt.Add(pA);
end;
end;
end;// bbMCreate_Click
// a normal BitButton click that reads 1 record from 1 art list
procedure TfrmMain.bbMRead_Click(Sender: TObject);
const
c = 1;
a = 2;
var
pA: art_ptr;
lArt: TList;
begin
// gets art list #2 from catalog (index is 0 based. c=1)
lArt:= lCatalog[c];
// gets record #3 from art list (index is 0 based. a=2)
pA:= lArt[a];
// displays the record in a string grid called sgArt... at row a+1
with sgArt, pA^ do begin
// col 0 contains cat index and art index, can be useful...
cells[0,a+1]:= Format('\%d\%d', [c,a]);
cells[1,a+1]:= sDes;
cells[2,a+1]:= FormatFloat('0.00', rCost);
cells[3,a+1]:= IntToStr(iQty);
end;
end;// bbMRead_Click

List and Contains method

i have this problem: starting from an empty list (0 elements) i want check if an element is present or not present in this list. In case this record not is present in list then i add this record to list, otherwise update element in list.
I have tried writing this code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Generics.Collections, System.Generics.Defaults;
type
TDBStats = record
Comb: Integer;
Freq: Integer;
end;
TDBStatsList = TList<TDBStats>;
procedure Add(ODBStats: TDBStatsList; const Item: TDBStats);
var
rItem: TDBStats;
begin
rItem := Item;
rItem.Freq := 1;
oDBStats.Add(rItem);
end;
procedure Update(ODBStats: TDBStatsList; const Item: TDBStats; const Index: Integer);
var
rItem: TDBStats;
begin
rItem := Item;
Inc(rItem.Freq);
oDBStats[Index] := rItem;
end;
var
oDBStats: TDBStatsList;
rDBStats: TDBStats;
myArr: array [0..4] of integer;
iIndex1: Integer;
begin
try
myArr[0] := 10;
myArr[1] := 20;
myArr[2] := 30;
myArr[3] := 40;
myArr[4] := 10;
oDBStats := TList<TDBStats>.Create;
try
for iIndex1 := 0 to 4 do
begin
rDBStats.Comb := myArr[iIndex1];
if oDBStats.Contains(rDBStats) then
Update(oDBStats, rDBStats, oDBStats.IndexOf(rDBStats))
else
Add(oDBStats, rDBStats);
end;
// Check List
for iIndex1 := 0 to Pred(oDBStats.Count) do
Writeln(oDBStats[iIndex1].Comb:3, oDBStats[iIndex1].Freq:10);
finally
oDBStats.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
and should return this result:
10 2
20 1
30 1
40 1
50 1
but return this result:
10 1
20 1
30 1
40 1
50 1
10 1
I have understood about problem: when i use oDBStats.Contains(rDBStats) it control if rDBStats element is contained in list; the first time not found it and add in list; but when it is added in list i update freq field to 1; so second time when i check again being rdbstats with freq = 0 not found it.
As i can solve this problem? I need to have a counter, where i get from input a "comb" and i want check if this "comb" is present in list, indipendetely from value of the other field of the record. In case i find "comb" in list, then i update, increasing freq field.
Thanks for help.
When you call Contains on a generic list, it looks if the given value is already inside the list. The value in your case is a record which consists of two fields. As you didn't specify a custom comparer, Delphi will use a default comparer which in case of a record does a binary compare. So only when two records are binary equal they will be treated as equal.
To make your example work you have to specify a custom comparer that compares only the comb field of the records. This is an example:
oDBStats := TList<TDBStats>.Create(TDelegatedComparer<TDBStats>.Create(
function(const Left, Right: TDBStats): Integer
begin
result := CompareValue(Left.comb, Right.comb);
end));
In addition you have an error in your update routine. Instead of incrementing the existing value, you are incrementing the undefined value of the item parameter. The change in the first line should make it work:
rItem := oDBStats[Index];
Inc(rItem.Freq);
oDBStats[Index] := rItem;
You have the wrong data structure since what you really need is a dictionary.
The fundamental problem with using a list is that you want to search on a subset of the stored record. But lists are not set up for that. Solve the problem by re-writing using TDictionary<Integer, Integer>.
I can recommend that you have a thorough read of the dictionary code example at the Embarcadero docwiki.
The key to the dictionary is what you call comb and the value is freq. To add an item you do this:
if Dict.TryGetValue(Comb, Freq) then
Dict[Comb] := Freq+1
else
Dict.Add(Comb, 1);
I'm assuming your dictionary is declared like this:
var
Dict: TDictionary<Integer, Integer>;
and created like this:
Dict := TDictionary<Integer, Integer>;
You can enumerate the dictionary with a simple for in loop.
var
Item: TPair<Integer, Integer>;
...
for Item in Dict do
Writeln(Item.Key:3, Item.Value:10);
Although be warned that the dictionary will enumerate in an odd order. You may wish to sort before printing.
If you wish to store more information associated with each entry in the dictionary then put the additional fields in a record.
type
TDictValue = record
Freq: Integer;
Field1: string;
Field2: TDateTime;
//etc.
end;
Then your dictionary becomes TDictionary<Integer, TDictValue>.

List and BinarySearch index not every correct

i have some problem again about list and binarysearch. In general, i have:
type
TMyArr = array [1..5] of Integer;
PMyList = record
Comb: TMyArr;
... // other fields
end;
TMyList = TList<PMyList>;
var
MyArr: TMyArr;
MyList: TMyList;
rMyList: PMyList;
i load value in array MyArr and want find element MyArr (with all values in it) in list TMyList, then i use:
rMyList.Comb := MyArr;
MyList.BinarySearch(rMyList, iIndex3, TDelegatedComparer<PMyList>.Construct(Compare));
with Compare so defined:
function CompareInt(const Left, Right: Integer): Integer;
begin
if Left < Right then
Result := -1
else if Left > Right then
Result := 1
else
Result := 0;
end;
function Compare(const Left, Right: PMyList): Integer;
begin
Result := CompareInt(Left.Comb[1], Right.Comb[1]);
if Result = 0 then
Result := CompareInt(Left.Comb[2], Right.Comb[2]);
if Result = 0 then
Result := CompareInt(Left.Comb[3], Right.Comb[3]);
if Result = 0 then
Result := CompareInt(Left.Comb[4], Right.Comb[4]);
if Result = 0 then
Result := CompareInt(Left.Comb[5], Right.Comb[5]);
end;
Now, my problem is that not every result is correct. In sense that often i have correct index of element and other time i have other index corresponding to other element, in casual.
As i can solve it? Where i have mistake?
I want only find index corresponding of MyArr in TMyArr.
Thanks again very much.
Your Compare function is just fine. If the binary search fails to work correctly then that can only be because the list is not ordered by the order defined by Compare. Call the Sort function on the list once you have finished populating, and before you start searching. When you call Sort, you must make sure that it use your compare function.

Resources