1

I'm trying to sort a string grid filled with scores and strings like so:

enter image description here

via a sorted string list which is sorted by single columns from the grid called SUB 1, SUB 2, FINAL, TOTAL respectively (all these columns work except FINAL) and I'm not getting the results I need in the FINAL column.

I'm trying to get the column sorted like so, for example :

24
20
12
5
DNF
EXE
WE

but I'm getting this result instead (the result I do not want):

DNF
EXE
WE
24
20
12
5

In what way could I change my code to sort the grid as I want to sort it?

My 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;

procedure TfrmPuntehou.SortLTSGrid(var grid: TStringGrid; columntotal: Integer);
var
    TheList : TStringList;
    i,l,iCount,m:integer;
    const
    separator = ',';
const
    arrCodes:array[1..10] of string = ('DNF','DNS','WD','WE','DNA','OD','RD','EXR','EXE','PP');
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
        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;

    //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;

15
  • Use TryStrToInt to detect whether or not the values are integers, and if so, make sure that they are always ordered after everything else. Commented May 24, 2021 at 12:25
  • @DavidHeffernan I see that this function returns a boolean, should I check for true and false then? Commented May 24, 2021 at 13:16
  • I am confused. All columns contain the same elements. Yet it works for all but 'Final'? Are you wanting a different order for 'Final' to the others? In the table shown, foe example, I don't see how it could work for 'Heat 2' and not work for 'Final' Commented May 24, 2021 at 18:15
  • @dsm In short, yes. I'm not sure either why the FINAL column is full of it. The thing is all the other columns I sort by only have integer values, while FINAL has strings AND integers in it. The SUB and TOTAL columns are totals/subtotals respectively, the FINAL column represents the final stage/race of the evening for a specific group (I'm refining software I wrote for a local dirt oval racing club). So there might be a racer who didn't finish, the cell in the column for that racer would then have a DNF in it for example. And so those who did race should display above those who didn't Commented May 24, 2021 at 19:09
  • that's where the problem I mentioned above comes in Commented May 24, 2021 at 19:09

2 Answers 2

3

You can use two different lists to store items, and two different sorting functions (because you want to sort them in different direction; numbers will be ordered as decreasing and strings will be ordered as ascending) to sort lists. Sort the lists separately, and than merge them.

Please consider @David Heffernan's performance warning.

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Classes;

var
 slStrings, slNumbers:TStringList;
 test:string;

function CompareForNumbers(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
 var
  val1, val2:Integer;
begin
  val1 := StrToInt(List[Index1]);
  val2 :=  StrToInt(List[Index2]);

  if val1 = val2 then
    Result := 0
  else if val1 < val2 then
    Result := 1
  else
    Result := -1;
end;

function CompareForStrings(
    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;

begin
  slStrings := TStringList.Create();
  slNumbers := TStringList.Create();
  try
    slStrings.Add('EXE');
    slStrings.Add('WE');
    slStrings.Add('DNF');

    slNumbers.Add('5');
    slNumbers.Add('20');
    slNumbers.Add('24');
    slNumbers.Add('12');

    slNumbers.CustomSort(CompareForNumbers);
    slStrings.CustomSort(CompareForStrings);

    slNumbers.AddStrings(slStrings);

    Writeln(slNumbers.Text);

    Readln(test);
  finally
    slStrings.Free();
    slNumbers.Free();
  end;
end.

To use single list to handle @David Heffernan's performance warning, i've write this;

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Classes;

var
 slStrings:TStringList;
 test:string;


function Compare(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
 var
  val1, val2:Integer;
  val1integer, val2integer:Boolean;
begin
  val1integer := TryStrToInt(List[Index1], val1);
  val2integer := TryStrToInt(List[Index2], val2);

  if val1integer and val2integer then
    begin
      if val1 = val2 then
        Result := 0
      else if val1 < val2 then
        Result := 1
      else
        Result := -1;
    end
  else if (not val1integer) And (not val2integer) then
    begin
      if List[Index1] = List[Index2] then
        Result := 0
      else if List[Index1] > List[Index2] then
        Result := 1
      else
        Result := -1;
    end
  else
    begin
      if val1integer then
        Result := -1
      else
        Result := 1;
    end;
end;

begin
  slStrings := TStringList.Create();
  try
    slStrings.Add('EXE');
    slStrings.Add('5');
    slStrings.Add('WE');
    slStrings.Add('20');
    slStrings.Add('DNF');
    slStrings.Add('24');
    slStrings.Add('12');
    slStrings.Add('A');
    slStrings.Add('6');
    slStrings.Add('E');
    slStrings.Add('B');
    slStrings.Add('4');
    slStrings.Add('T');
    slStrings.Add('444');

    slStrings.CustomSort(Compare);

    Writeln(slStrings.Text);

    Readln(test);
  finally
    slStrings.Free();
  end;
end.
Sign up to request clarification or add additional context in comments.

18 Comments

The TryStrToFloat functions aren't working. Delphi says there is no overloaded version of TryStrToFloat that can be called with these arguments
AddStrings is giving me the same error as well
ok, so I gave the code you posted another try, it still works with the other columns that use this procedure (SUB 2 and TOTAL), but it still gives me the same result as I mentioned before with the strings on top and integers below it in the final column (I'm trying to achieve the opposite). But thx anyway
still no change, I get the same output, btw what would tmpFloat be (I don't see it assigned anywhere, although it is declared as a variable), funny that I actually tried that before I posted my previous comment, lol
@ssdk Nope, still no difference.
|
1

I believe you want to sort times by shortest to longest first, then all text alphabetically (although that is arguable).

I would not modify the texts in the way that you do. Instead I would simply modify the comparer function and pass the texts 'as is'.

To test it I used a TMemo, but the principle applies to the tables - just copy the appropriate column to the string list.

function Compare2(
    List   : TStringList;
    Index1 : Integer;
    Index2 : Integer) : Integer;
var
  i1IsNumeric, i2IsNumeric : boolean;
  i1, i2 : integer;
begin
  //comparer for custom sort used in SortLTSGrid
  i1IsNumeric := TryStrToInt( List[Index1], i1 );
  i2IsNumeric := TryStrToInt( List[Index2], i2 );
  if i1IsNumeric and (not i2IsNumeric) then
  begin
    Result := -1;
  end
  else if i2IsNumeric and (not i1IsNumeric) then
  begin
    Result := 1;
  end
  else if i1IsNumeric then
  begin
    Result := Sign( i1-i2);
  end
  else
  begin
    Result := CompareStr( List[Index1], List[Index2] );
  end;

end;

Here is my test routine using a memo

procedure TForm4.Button1Click(Sender: TObject);
var
  iList : TStringList;
begin
  iList := TStringList.Create;
  try
    iList.Assign( Memo1.Lines );
    iList.CustomSort( Compare2 );
    Memo1.Lines.Assign( iList );
  finally
    iList.Free;
  end;
end;

Your routine would be more like (although this I have not tested)

procedure TfrmPuntehou.SortLTSGrid(var grid: TStringGrid; columntotal: Integer);
var
    TheList : TStringList;
    i,l,iCount,m:integer;
    const
    separator = ',';
const
    arrCodes:array[1..10] of string = ('DNF','DNS','WD','WE','DNA','OD','RD','EXR','EXE','PP');
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].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;

8 Comments

So I tested this and this doesn't work at all (not on the SUB 2 or TOTAL columns either). It jumbles everything instead of sorting but thx anyway
This compare function is fine @PrimeBeat. If your code doesn't do what you want, then that's an issue with your code, not the code here.
@Dsm One minor point, doesn't the use of i1 - i2 risk overflow?
@PrimeBeat there was an error in the original code I posted, but that is now corrected.
@DavidHeffernan, I guess so in theory, although in practice it would have to be a mistake involving at least one negative time. If all times are positive, as they should be, then no. The OP could check for that first.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.