PrimeBeat
PrimeBeat

Reputation: 484

Sorting a string list containing numbers and strings

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;

Upvotes: 1

Views: 863

Answers (2)

Dsm
Dsm

Reputation: 6013

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;

Upvotes: 1

sddk
sddk

Reputation: 1115

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.

Upvotes: 3

Related Questions