Delphi Generics: TArray.Sort - sorting

I am just starting to get my feet wet with this .
PNode = ^TNode;
TNode = record
Obstacle : boolean;
Visited : boolean;
GCost : double; // Distance from Starting Node
HCost : double; // Distance from Target Node
x : integer;
y : integer;
vecNeighBour : array of PNode;
Parent : PNode;
end;
OnFormShow I fill the array :
SetLength(Node,4,4);
Image1.Height:=Length(Node)*50;
Image1.Width:=Length(Node)*50;
for x := Low(Node) to High(Node) do
for y := Low(Node) to High(Node) do
begin
Node[x,y].Obstacle:=false;
Node[x,y].Visited:=false;
Node[x,y].GCost:=infinity;
Node[x,y].HCost:=infinity;
Node[x,y].x:=x;
Node[x,y].y:=y;
end;
Now I would like to Sort this array by HCost , so I tried this .
TArray.Sort<TNode>(Node , TComparer<TNode>.Construct(
function(const Left, Right: TNode): double
begin
if Left.HCost > Right.HCost then
Result:=Left.HCost
else
Result:=Right.HCost;
end));
My knowledge is seriously lacking in this thing . I get a error from Delphi
"Incompatible types:
'System.Gerenics.Defaults.TComparison and 'Procedure'
What am I doing wrong here?

A comparison function has to return an Integer value. It is defined like so:
type
TComparison<T> = reference to function(const Left, Right: T): Integer;
Your function returns the wrong type.
function CompareDoubleInc(Item1, Item2: Double): Integer;
begin
if Item1=Item2 then begin
Result := 0;
end else if Item1<Item2 then begin
Result := -1
end else begin
Result := 1;
end;
end;
....
TArray.Sort<TNode>(
Node,
TComparer<TNode>.Construct(
function(const Left, Right: TNode): Integer
begin
Result := CompareDoubleInc(Left.HCost, Right.HCost);
end
)
);
Or if you want to make it even simpler you can delegate to the default double comparer:
TArray.Sort<TNode>(
Node,
TComparer<TNode>.Construct(
function(const Left, Right: TNode): Integer
begin
Result := TComparer<Double>.Default.Compare(Left.HCost, Right.HCost);
end
)
);

Related

TStringList.CustomSort: Compare() with variables

I am trying to custom sort a TStringList by a column in a .CSV file. My code below works (slowly, about 14 seconds for 200,000+ lines):
function Compare(List: TStringList; Index1, Index2: Integer): Integer;
function ColStr(const Ch: Char; const S: String; First, Last: Integer): String;
var
p1, p2: Integer;
function GetPos(const N: Integer; Start: Integer = 1): Integer;
var
I, Len, Count: Integer;
begin
Result := 0;
Len := Length(S);
if (Len = 0) or (Start > Len) or (N < 1) then Exit;
Count := 0;
for I := Start to Len do begin
if S[I] = Ch then begin
Inc(Count);
if Count = N then begin
Result := I;
Exit;
end;
end;
end;
end;
begin
p1 := GetPos(4, 1); // 4 should be a variable
p2 := GetPos(5, 1); // 5 should be a variable
if Last = 0 then Result := Copy(S, p1 + 1, length(S)) else Result := Copy(S, p1 + 1, p2 - p1 - 1);
end;
begin
Result := AnsiCompareStr(ColStr(',', List[Index1], 0, 1), ColStr(',', List[Index2], 0, 1));
end;
What I would want to do is not have this hard-coded but (where commented "should be a variable" depending on which column to sort). I know I can't use:
function Form1.Compare(List: TStringList; Index1, Index2: Integer): Integer;
for inserting variables, as I get the error:
Incompatible types: 'method pointer and regular procedure'.
I have searched through SO looking for instances of this error but cannot find one that fits my question. I would appreciate any pointers in the right direction.
This has to be done with Delphi 7 and Windows 11.
TStringList.CustomSort() does not let you pass in extra parameters, and it does not accept class methods or anonymous procedures. But, what it does do is pass the actual TStringList itself to the callback, so I would suggest deriving a new class from TStringList to add extra fields to it, and then you can access those fields inside the callback, eg:
type
TMyStringList = class(TStringList)
public
Count1: Integer;
Count2: Integer;
end;
function Compare(List: TStringList; Index1, Index2: Integer): Integer;
...
p1 := GetPos(TMyStringList(List).Count1, 1);
p2 := GetPos(TMyStringList(List).Count2, 1);
...
begin
...
end;
...
List := TMyStringList.Create;
// fill List ...
List.Count1 := ...;
List.Count2 := ...;
List.CustomSort(Compare);
So you are performing searching for k-th occurence of Ch and substring creation at every comparison.
You can optimize this process - before sorting make list/array of stringlists, created from every string, separated by needed character - use DelimitedText.
Inside compare function just work with this array and column numbers - sadly, you have to define them as global variables in current unit (for example, after Form1: TForm1)

Delphi - descending sort of string grid rows, sorting by 1 column

I've run into a bit of a wall with my sorting, I managed to sort the rows of my string grid from smallest to largest but now I'm not sure how to sort it in descending order. I've tried using the code I used from the other sort and I only changed the 2nd last loop in the code to see if I can read from the bottom of the TStringList, but it hasn't worked and only takes one row from the list and duplicates it into the rest of the rows. Is there perhaps a way to reverse read a TStringList after sorting?
Code I used for the other sort I have and tried to implement for this sort (only changed the 2nd last loop):
procedure TfrmPuntehou.SortLTSGrid(var grid: TStringGrid; columntotal: Integer);
const
separator = ',';
var
iCount,i,j,k,iPos:integer;
TheList:TStringList;
sString,sTempString:string;
m: Integer;
o: Integer;
begin
//procedure to sort from large to small values
//get row amount
iCount:=grid.RowCount-1;
//create list
TheList:=TStringList.Create;
TheList.Sorted:=False;
//start of try..finally block
try
begin
//fill the list
for i := 1 to (iCount - 1) do
begin
TheList.Add(grid.Rows[i].Strings[columntotal]+separator+grid.Rows[i].Text);
end;
//sort the list
TheList.Sort;
for k := 1 to TheList.Count do
begin
//take the line of the list and put it in a string var
sString:= TheList.Strings[(k-1)];
//get separator pos in that string
iPos:=AnsiPos(separator,sString);
sTempString:='';
//remove separator and the column text at the front of the string
sTempString:=Copy(sString,(iPos+1),Length(sString));
TheList.Strings[(k-1)]:= '';
TheList.Strings[(k-1)]:= sTempString;
end;
//fill the grid
for j:= (iCount - 1) downto 1 do
begin
for o := 1 to (iCount - 1) do
begin
grid.Rows[j].Text := TheList.Strings[(o-1)] ;
end;
end;
//fill the row numbers
for m := 1 to iCount do
begin
grid.Cells[0,m]:= IntToStr(m);
end;
end;
finally
TheList.Free;
end;
//end of try..finally block
end;
Thanks in advance for the help!Kind RegardsPrimeBeat
Use TStringList.CustomSort to sort the list using a specific method for comparison.
The specification for the comparer is given here.
Example:
function Compare1( // Normal alphanum sort
List : TStringList;
Index1 : Integer;
Index2 : Integer) : Integer;
begin
if List[Index1] = List[Index2] then
Result := 0
else if List[Index1] < List[Index2] then
Result := -1
else
Result := 1;
end;
function Compare2( // Reverse alphanum sort
List : TStringList;
Index1 : Integer;
Index2 : Integer) : Integer;
begin
if List[Index1] = List[Index2] then
Result := 0
else if List[Index1] < List[Index2] then
Result := 1
else
Result := -1;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
SList : TStringList;
S : String;
begin
SList := TStringList.Create;
try
SList.Add('Pierre');
SList.Add('Albert');
SList.Add('Paul');
SList.Add('Jean');
SList.Add('Simon');
Memo1.Lines.Add('=== Compare1 ===');
SList.CustomSort(Compare1);
for S in SList do
Memo1.Lines.Add(S);
Memo1.Lines.Add('=== Compare2 ===');
SList.CustomSort(Compare2);
for S in SList do
Memo1.Lines.Add(S);
finally
SList.Free;
end;
end;

Read integers from a string

I'm learning algorithms and I'm trying to make an algorithm that extracts numbers lets say n in [1..100] from a string. Hopefully I get an easier algorithm.
I tried the following :
procedure ReadQuery(var t : tab); // t is an array of Integer.
var
x,v,e : Integer;
inputs : String;
begin
//readln(inputs);
inputs:='1 2 3';
j:= 1;
// make sure that there is one space between two integers
repeat
x:= pos(' ', inputs); // position of the space
delete(inputs, x, 1)
until (x = 0);
x:= pos(' ', inputs); // position of the space
while x <> 0 do
begin
x:= pos(' ', inputs); //(1) '1_2_3' (2) '2_3'
val(copy(inputs, 1, x-1), v, e); // v = value | e = error pos
t[j]:=v;
delete(inputs, 1, x); //(1) '2_3' (2) '3'
j:=j+1; //(1) j = 2 (2) j = 3
//writeln(v);
end;
//j:=j+1; // <--- The mistake were simply here.
val(inputs, v, e);
t[j]:=v;
//writeln(v);
end;
I get this result ( resolved ) :
1
2
0
3
expected :
1
2
3
PS : I'm not very advanced, so excuse me for reducing you to basics.
Thanks for everyone who is trying to share knowledge.
Your code is rather inefficient and it also doesn't work for strings containing numbers in general.
A standard and performant approach would be like this:
type
TIntArr = array of Integer;
function GetNumbers(const S: string): TIntArr;
const
AllocStep = 1024;
Digits = ['0'..'9'];
var
i: Integer;
InNumber: Boolean;
NumStartPos: Integer;
NumCount: Integer;
procedure Add(Value: Integer);
begin
if NumCount = Length(Result) then
SetLength(Result, Length(Result) + AllocStep);
Result[NumCount] := Value;
Inc(NumCount);
end;
begin
InNumber := False;
NumCount := 0;
for i := 1 to S.Length do
if not InNumber then
begin
if S[i] in Digits then
begin
NumStartPos := i;
InNumber := True;
end;
end
else
begin
if not (S[i] in Digits) then
begin
Add(StrToInt(Copy(S, NumStartPos, i - NumStartPos)));
InNumber := False;
end;
end;
if InNumber then
Add(StrToInt(Copy(S, NumStartPos)));
SetLength(Result, NumCount);
end;
This code is intentionally written in a somewhat old-fashioned Pascal way. If you are using a modern version of Delphi, you wouldn't write it like this. (Instead, you'd use a TList<Integer> and make a few other adjustments.)
Try with the following inputs:
521 cats, 432 dogs, and 1487 rabbits
1 2 3 4 5000 star 6000
alpha1beta2gamma3delta
a1024b2048cdef32
a1b2c3
32h50s
5020
012 123!
horses
(empty string)
Make sure you fully understand the algorithm! Run it on paper a few times, line by line.

Swap two numbers in pascal

I'm trying to swap two values but I'm getting a Warning: Local variable "temp" does not seem to be initialized. I want to do it similar as how I've done it. I'm compiling it from the command line with fpc Main.pas. I've tried initializing the temp variable to 0, but it still says Fatal: there were 3 errors compiling module, stopping.
'Main.pas'
Program Main;
procedure Main();
var
n1, n2: Integer;
begin
n1 := 5;
n2 := 10;
Swap(#n1, #n2);
writeln('n1 = ', n1);
writeln('n2 = ', n2);
end;
BEGIN
Main();
END.
'Number.pas'
unit Number;
interface
type
IntPtr = ^Integer;
procedure Swap(n1, n2: IntPtr);
implementation
procedure Swap(n1, n2: IntPtr);
var
temp: Integer;
begin
temp = n1^;
n1^ = n2^;
n2^ = temp;
end;
end.
As you have already discovered, you mixed up the assignment (:=) and equality (=) operators. Thus,
procedure Swap(A, B: PInteger);
var
Temp: Integer;
begin
Temp := A^;
A^ := B^;
B^ := Temp;
end;
where PInteger is defined as ^Integer, does the job:
Swap(#Val1, #Val2); // swaps integers Val1 and Val2
However, I suggest you do this slightly differently:
procedure Swap(var A, B: Integer);
var
Temp: Integer;
begin
Temp := A;
A := B;
B := Temp;
end;
Using a var parameter is more idiomatic and it allows you to write simply
Swap(Val1, Val2); // swaps integers Val1 and Val2
and it also gives you a bit more type safety.

Pascal error: left side cannot be assigned when trying to compile

I got this error when trying to compile.
left side cannot be assign to, ./number 21,22 in pastebin
here is my code
Program urut;
Uses Wincrt;
Const N = 5;
data: Array [1..N] Of Integer = (2,4,5,3,1);
Var
j,k,temp : Integer;
Begin
Clrscr;
Writeln ('Data sebelum diurutkan');
For j:=1 To N Do
Begin
Writeln('data[' ,j, ']= ',data [j]);
End;
For j:=1 To N-1 Do
Begin
For k :=N Downto j+1 Do
Begin
If data[k] < data[k-1] Then
Begin
temp := data[k];
data[k] := data[k-1]; //left side cannot be assigned to
data[k-1] := temp; //left side cannot be assigned to
End;
End;
End;
Writeln;
Writeln ('Data setelah diurutkan ');
For j:=1 To N Do
Begin
Writeln ('data[' ,j, '] = ',data[j]);
End;
Writeln;
End.
sorry for uncorrectly pattern post
, thank you so much.
Like tom Tom Brunberg says, my array is const, it can't be changed. Therefore I need to remove that const.
So it should be
data: Array [1..5] Of Integer = (value);
without const, and put it under var with another variable

Resources