My sort functions have gone rogue...

Sultan Mustafa

Honorary Master
Joined
Nov 7, 2020
Messages
24,866
Reaction score
17,097
Ok, so for once I'm posting about something else than an assignment, lol. But now I face another problem, long ago I wrote a point keeping program for the local stock car club. Been working fine until June this year.

Now my sort functions have gone rogue, and I'm stuck on how to solve this. I made this program with Delphi btw.

Background:
So in a race, there are a bunch of classes (Hotrods, 2.1s, etc). And every class has a certain amount of heats to race. Now you enter the first two heats results, whether they came first or whatever since every position finish has its own points value (I checked and the point conversion is completely fine), then it automatically adds the results of the two heats into the Subtotal 1 column (which is used to determine starting positions for Heat 3). And it works similarly for Heat 3 and onwards. The columns that can be sorted are Sub 1, Sub 2 and the Final Heat as well as the final Total.

Sub 1 has to be sorted from smallest to highest (which will sort the grid). All the other columns have to be sorted from largest to smallest. The problem is there is no problem with the sorting themselves but I get wonky results... It's as if it just randomizes the sorting... And it worked perfectly until now...

Example:
1664296440382.png1664296440382.png

Code:
Code:
procedure TfrmPuntehou.SortLTSGrid(var grid: TStringGrid; columntotal: Integer);
var
    TheList : TStringList;
    i,l,iCount,m:integer;
    const
    separator = ',';
begin
  //sorts grid largest to smallest according to one column

    //get grid row amount
    iCount:=grid.RowCount - 1;
    //create and fill the string list
    TheList := TStringList.Create;
      //fill the list
      for i := 1 to (iCount) do
      begin
        TheList.Add(grid.Rows[i].Strings[columntotal]+separator+grid.Rows[i].Text);
      end;

    //try block to sort and write all strings in the list to the grid correctly
    try

       TheList.CustomSort(Compare2);

      for i:= 1 to (iCount) do
      begin
      grid.Rows[i].Text := TheList.Strings[(i-1)] ;
      end;

      //fill the row numbers
      for m := 1 to iCount do
      begin
      grid.Cells[0,m]:= IntToStr(m);
      end;


    finally
        TheList.Free;
    end;
end;

Code:
procedure TfrmPuntehou.SortSTLGrid(var grid:TStringGrid; columntotal:integer);
const
separator = ',';
var
iCount,i,j,k,iPos:integer;
TheList:TStringList;
sString,sTempString:string;
  m: Integer;
  l: Integer;
begin
  //procedure to sort from small to large 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) do
      begin
        for l := 1 to Length(arrCodes) do
        begin
          if grid.Rows[i].Strings[columntotal] = arrCodes[l] then
          begin
          TheList.Add('0'+separator+grid.Rows[i].Text);
          end;
        end;
        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+2),Length(sString));
      TheList.Strings[(k-1)]:= '';
      TheList.Strings[(k-1)]:= sTempString;
      end;

      //fill the grid
      for j:= 1 to (iCount) do
      begin
      grid.Rows[j].Text := TheList.Strings[(J-1)] ;
      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;
 
Ok, so for once I'm posting about something else than an assignment, lol. But now I face another problem, long ago I wrote a point keeping program for the local stock car club. Been working fine until June this year.

Now my sort functions have gone rogue, and I'm stuck on how to solve this. I made this program with Delphi btw.

Background:
So in a race, there are a bunch of classes (Hotrods, 2.1s, etc). And every class has a certain amount of heats to race. Now you enter the first two heats results, whether they came first or whatever since every position finish has its own points value (I checked and the point conversion is completely fine), then it automatically adds the results of the two heats into the Subtotal 1 column (which is used to determine starting positions for Heat 3). And it works similarly for Heat 3 and onwards. The columns that can be sorted are Sub 1, Sub 2 and the Final Heat as well as the final Total.

Sub 1 has to be sorted from smallest to highest (which will sort the grid). All the other columns have to be sorted from largest to smallest. The problem is there is no problem with the sorting themselves but I get wonky results... It's as if it just randomizes the sorting... And it worked perfectly until now...

Example:
View attachment 1390522View attachment 1390522

Code:
Code:
procedure TfrmPuntehou.SortLTSGrid(var grid: TStringGrid; columntotal: Integer);
var
    TheList : TStringList;
    i,l,iCount,m:integer;
    const
    separator = ',';
begin
  //sorts grid largest to smallest according to one column

    //get grid row amount
    iCount:=grid.RowCount - 1;
    //create and fill the string list
    TheList := TStringList.Create;
      //fill the list
      for i := 1 to (iCount) do
      begin
        TheList.Add(grid.Rows[i].Strings[columntotal]+separator+grid.Rows[i].Text);
      end;

    //try block to sort and write all strings in the list to the grid correctly
    try

       TheList.CustomSort(Compare2);

      for i:= 1 to (iCount) do
      begin
      grid.Rows[i].Text := TheList.Strings[(i-1)] ;
      end;

      //fill the row numbers
      for m := 1 to iCount do
      begin
      grid.Cells[0,m]:= IntToStr(m);
      end;


    finally
        TheList.Free;
    end;
end;

Code:
procedure TfrmPuntehou.SortSTLGrid(var grid:TStringGrid; columntotal:integer);
const
separator = ',';
var
iCount,i,j,k,iPos:integer;
TheList:TStringList;
sString,sTempString:string;
  m: Integer;
  l: Integer;
begin
  //procedure to sort from small to large 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) do
      begin
        for l := 1 to Length(arrCodes) do
        begin
          if grid.Rows[i].Strings[columntotal] = arrCodes[l] then
          begin
          TheList.Add('0'+separator+grid.Rows[i].Text);
          end;
        end;
        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+2),Length(sString));
      TheList.Strings[(k-1)]:= '';
      TheList.Strings[(k-1)]:= sTempString;
      end;

      //fill the grid
      for j:= 1 to (iCount) do
      begin
      grid.Rows[j].Text := TheList.Strings[(J-1)] ;
      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;
1664298254999.png

Another example
 
Seems to be some missing code to complete the picture and also maybe some more context like the format of the input etc, post the full code with the input maybe so that we don't make assumptions
 
Seems to be some missing code to complete the picture and also maybe some more context like the format of the input etc, post the full code with the input maybe so that we don't make assumptions
Well, the only code I can post is the functions that handle the point and the calculations, but that is also part of the problem, I checked all of it and there's nothing wrong, it seems the problem lies solely with the sort it seems...
 
And it worked perfectly until now...
...probably because with that set of sample data your sub1 column had all two digit totals? You are sorting string values ... and where you have a single digit sub1 value it is not going to be in integer numerical order, it is in number as text order.

You may have to derive a custom Tstringlist class and override the CompareStrings function with an integer comparision ... or maybe pad leading zero(s) into the sub1 column.
 
I skimmed over this to be honest. My first thought is the algorithm being used to sort isn't stable. Googling stable sort could potentially point you in the right direction. Lexigraphical order as pointed out above is another potential candidate but shouldn't result in "random" order so I have doubts about it
 
Because you are doing lexicographic sorting, where your variable is treated as a string and sorted as an array of chars from left to right. If you had line numbers it would be easier to point out your errors. Ensure that you treat the numbers as integers.
 
...probably because with that set of sample data your sub1 column had all two digit totals? You are sorting string values ... and where you have a single digit sub1 value it is not going to be in integer numerical order, it is in number as text order.

You may have to derive a custom Tstringlist class and override the CompareStrings function with an integer comparision ...
Hmmm, I'll look into it.

I suspected that the single digits might be going for lexical order instead of numerical.
or maybe pad leading zero(s) into the sub1 column.
So just making digits like "6" into "06"? Is that what you mean?
 
So just making digits like "6" into "06"? Is that what you mean?
No, I'm assuming he means treating the numbers as integers (in the machine sense) and not strings. I still think, just to rule it out, check the stability of the algorithm used
 
You are sorting string values ... and where you have a single digit sub1 value it is not going to be in integer numerical order, it is in number as text order.
And also, if I'm honest, this seems to be an edge case. Since positions, 1 to 10 have scores between 30 and 10, and 11 to 21 have scores lower than 10.

And the usual amount of cars isn't more than 10 a race unless it's a massive event. I originally made the program with usual events in mind and not big ones.

So now when things go to single digits (usually when a code like DNF, etc is added for the first heat of a racer and there are more than 10 cars in the race and the racer is say placed eleventh in the next heat, then things dip below 10) it seems the sorting is going rogue, but oddly enough if you add enough points so that the score is above 10, then it sorts as it should. Oddly enough, 0 doesn't pose any problems when sorting...
 
Hmmm, I'll look into it.

I suspected that the single digits might be going for lexical order instead of numerical.

So just making digits like "6" into "06"? Is that what you mean?
Yes. If you pad them, this will band aid your “broken” sorting (which is not broken, it is sorting exactly as expected).

I also would definitely not consider this an “edge case”

But the better/correct thing is to sort them as ints.
If there are other codes besides DNF, you will need to assign them all integer values too (as well as giving DNF a value)
 
Yes. If you pad them, this will band aid your “broken” sorting (which is not broken, it is sorting exactly as expected).

I also would definitely not consider this an “edge case”

But the better/correct thing is to sort them as ints.
I've been thinking of maybe making 2 parallel arrays and having pure integers in one and the row contents in another. And then sorting both at the same time to get the wanted results. And of course, putting the rows back as they should be.
If there are other codes besides DNF, you will need to assign them all integer values too (as well as giving DNF a value)
That's the thing, in calculation the codes count as 0. Since they don't add to the score or anything.
 
I've been thinking of maybe making 2 parallel arrays and having pure integers in one and the row contents in another. And then sorting both at the same time to get the wanted results. And of course, putting the rows back as they should be.

That's the thing, in calculation the codes count as 0. Since they don't add to the score or anything.
There is no need for any parallel work

“TStringList” has a method called “CustomSort”, that takes a comparator function

Here is a copy paste example for dates stored as strings (which is no different to ints stored as strings)

Code:
function CompareDates(List: TStringList; Index1, Index2: Integer): Integer;
var
  d1, d2: TDateTime;
begin
  d1 := StrToDate(List[Index1]);
  d2 := StrToDate(List[Index2]);
  if d1 < d2 then
    Result := -1
  else if d1 > d2 then Result := 1
  else
    Result := 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    // listbox1.Sorted := False !
    sl.Assign(listbox1.Items);
    sl.CustomSort(CompareDates);
    listbox1.Items.Assign(sl);
  finally
    sl.Free
  end;
 
There is no need for any parallel work

“TStringList” has a method called “CustomSort”, that takes a comparator function

Here is a copy paste example for dates stored as strings (which is no different to ints stored as strings)

Code:
function CompareDates(List: TStringList; Index1, Index2: Integer): Integer;
var
  d1, d2: TDateTime;
begin
  d1 := StrToDate(List[Index1]);
  d2 := StrToDate(List[Index2]);
  if d1 < d2 then
    Result := -1
  else if d1 > d2 then Result := 1
  else
    Result := 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    // listbox1.Sorted := False !
    sl.Assign(listbox1.Items);
    sl.CustomSort(CompareDates);
    listbox1.Items.Assign(sl);
  finally
    sl.Free
  end;
Code:
function Compare2(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
begin
  //comparer for custom sort used in SortLTSGrid
    if List[Index1] = List[Index2] then
        Result := 0
    else if List[Index1] < List[Index2] then
        Result := 1
    else
        Result := -1;
end;
Here's the compare function I'm using for CustomSort btw.
 
Code:
function Compare2(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
begin
  //comparer for custom sort used in SortLTSGrid
    if List[Index1] = List[Index2] then
        Result := 0
    else if List[Index1] < List[Index2] then
        Result := 1
    else
        Result := -1;
end;
Here's the compare function I'm using for CustomSort btw.
Except that won’t work as you are still comparing strings.

The first 2 lines of your function should be trying to convert the string to an int, and if that convert fails, set to a default int value.
 
Except that won’t work as you are still comparing strings.

The first 2 lines of your function should be trying to convert the string to an int, and if that convert fails, set to a default int value.
Code:
function Compare2(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
begin
  //comparer for custom sort used in SortLTSGrid
    if StrToInt(List[Index1]) = StrToInt(List[Index2]) then
        Result := 0
    else if StrToInt(List[Index1]) < StrToInt(List[Index2]) then
        Result := 1
    else if StrToInt(List[Index1]) > StrToInt(List[Index2]) then
        Result := -1;
end;

Something like this?
 
Code:
function Compare2(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
begin
  //comparer for custom sort used in SortLTSGrid
    if StrToInt(List[Index1]) = StrToInt(List[Index2]) then
        Result := 0
    else if StrToInt(List[Index1]) < StrToInt(List[Index2]) then
        Result := 1
    else if StrToInt(List[Index1]) > StrToInt(List[Index2]) then
        Result := -1;
end;

Something like this?


Like this

Code:
function CompareAsIntegers(List: TStringList; Index1, Index2: Integer): Integer;
var
  int1, int2: LongInt;
begin
  if (TryStringToInt(List[Index1], int1) == false then
    int1 := -1; //or whatever you want non ints to become
  end;
  if (TryStringToInt(List[Index2], int2) == false then
    int2 := -1; //or whatever you want non ints to become
  end;
 
  if int1 < int2 then
    Result := -1
  else if int1 > int2 then Result := 1
  else
    Result := 0;
end;

You could even add another helper function

Code:
function StringToIntWithDefaukt(Value: String; Default: Integer): Integer;
var
  result: Integer;
begin
  if (TryStringToInt(List[Index1], result) == false then
    result := Default
  end;
  Result := result
end;

function CompareAsIntegers(List: TStringList; Index1, Index2: Integer): Integer;
var
  int1, int2: LongInt;
begin
  int1 := StringToIntWithDefault(List[Index1], -1);  
  int2 := StringToIntWithDefault(List[Index2], -1);
  
  if int1 < int2 then
    Result := -1
  else if int1 > int2 then Result := 1
  else
    Result := 0;
end;

Disclaimer, I am coding this on my phone on the beach with cocktail in hand. I do not claim this will compile
 
Top
Sign up to the MyBroadband newsletter