Reputation: 2068
I have a TStringList of Name/Value pairs. The names are all integer values 9stored as strings of course) and the values are all strings (comma separated).
e.g.
5016=Catch the Fish!,honeyman,0
30686=Ozarktree1 Goes to town,ozarktreel,0
. . .
I would like to call the add routine and add new lines in the TStringlist, but need a way to sort the list afterwards.
e.g.
Tags.Add(frmTag.edtTagNo.Text + '=' +
frmTag.edtTitle.Text + ',' +
frmTag.edtCreator.Text + ',' +
IntToStr(ord(frmTag.cbxOwned.Checked)));
Tags.Sort;
Here is what I tried:
Tags:= TStringList.Create;
Tags.CustomSort(StringListSortComparefn);
Tags.Sorted:= True;
my custom sort routine:
function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result:= CompareValue(i1, i2);
end;
However, it still seems to be sorting them like strings instead of integers.
I even tried creating my own class:
type
TXStringList = class(TStringList)
procedure Sort;override;
end;
implementation
function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result:= CompareValue(i1, i2);
end;
procedure TXStringList.Sort;
begin
CustomSort(StringListSortComparefn);
end;
I even tried some examples on SO (e.g. Sorting TSTringList Names property as integers instead of strings)
Can someone tell me what I am doing wrong? Everytime, the list gets sorted as strings and not as integers.
30686=Ozarktree1 Goes to town,ozarktreel,0
5016=Catch the Fish!,honeyman,0
Upvotes: 6
Views: 13166
Reputation: 163267
The CustomSort
command is a one-time operation. You appear to be using it as though you're setting a property so that further sorting will use the custom comparison function, but that's not really how it works. It sorts the (newly created, empty) list once. Then, when you set the Sorted
property, you re-sort the list using the default comparison, and you specify that any further additions to the list should be inserted using that default sort order.
When you override the Sort
method, you're a little closer to a solution, but insertions to a sorted list (where Sorted=True
) do not actually call Sort
! Instead, they perform a binary search for the correct insertion location and then insert there. Instead of overriding Sort
, you could try overriding CompareStrings
:
type
TXStringList = class(TStringList)
protected
function CompareStrings(const S1, S2: string): Integer; override;
end;
function TXStringList.CompareStrings(const S1, S2: string): Integer;
var
i1, i2, e1, e2: Integer;
begin
Val(S1, i1, e1);
Assert((e1 = 0) or (S1[e1] = NameValueSeparator));
Val(S2, i2, e2);
Assert((e2 = 0) or (S2[e2] = NameValueSeparator));
Result := CompareValue(i1, i2);
end;
Beware that this will break the IndexOf
method, though. It might also break Find
, but you might want that, depending on how you want to treat elements with the same numeric key. (Find
is what's used to locate the correct insertion point of a sorted list, and with the above code, it would treat all elements with the same key as equal.) They all use CompareStrings
just like Sort
does.
Upvotes: 4
Reputation: 3932
The question is, do you require the list to remain sorted? Or is it sufficient to sort it at the end, after all the items have been added.
If you just need to be able to sort the list as needed, you're first example is almost correct. You just need to call CustomSort at the end, after your items have been added.
Tags := tStringList . Create;
Tags . Add ( '5016=Catch the Fish!,honeyman,0' );
Tags . Add ( '30686=Ozarktree1 Goes to town,ozarktreel,0' );
Tags.CustomSort(StringListSortComparefn);
If you need the list to stay sorted, then you need to override CompareStrings.
type
TXStringList = class(TStringList)
function CompareStrings(const S1, S2: string): Integer; override;
end;
function NumberOfNameValue ( const S : string ) : integer;
begin
Result := StrToIntDef(copy(S,1,pos('=',S)-1), 0);
end;
function txStringList . CompareStrings ( const S1, S2 : string ) : integer;
var
i1, i2 : Integer;
begin
i1 := NumberOfNameValue ( S1 );
i2 := NumberOfNameValue ( S2 );
Result:= CompareValue(i1, i2);
end;
begin
Tags := txstringlist . Create;
Tags . Sorted := true;
Tags . Add ( '5016=Catch the Fish!,honeyman,0' );
Tags . Add ( '30686=Ozarktree1 Goes to town,ozarktreel,0' );
// List will be correctly sorted at this point.
end;
Upvotes: 4
Reputation: 125669
You can do a simple integer subtraction:
function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result := i1 - i2
end;
To reverse the sort order, simply reverse the operands in the subtraction:
Result := i2 - i1;
Here's a quick, compiilable console example:
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
function StringListSortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2: Integer;
begin
i1 := StrToIntDef(List.Names[Index1], -1);
i2 := StrToIntDef(List.Names[Index2], -1);
Result := i1 - i2;
end;
var
SL: TStringList;
s: string;
begin
SL := TStringList.Create;
SL.Add('3456=Line 1');
SL.Add('345=Line 2');
SL.Add('123=Line 3');
SL.Add('59231=Line 4');
SL.Add('545=Line 5');
WriteLn('Before sort');
for s in SL do
WriteLn(#32#32 + s);
SL.CustomSort(StringListSortProc);
WriteLn('');
WriteLn('After sort');
for s in SL do
WriteLn(#32#32 + s);
ReadLn;
SL.Free;
end.
And the resulting output:
Before sort
3456=Line 1
345=Line 2
123=Line 3
59231=Line 4
545=Line 5
After sort
123=Line 3
345=Line 2
545=Line 5
3456=Line 1
59231=Line 4
Upvotes: 6