Reputation: 169
I have a string list containing some numbers. I have sorted them using a bubble sort that I wrote. The output is:
18
20
3
44
53
I cannot understand why this above was output rather than what I expected:
3
18
20
44
53
What am I missing?
Upvotes: 4
Views: 6895
Reputation: 613612
First of all, you don't need to write your own sort code to sort those strings in your string list. The string list class comes with a sort function. You can use that rather than writing your own sort code.
Now moving on to the crux of the question, in the output from your program those values are ordered correctly, when treated as text. They are correctly ordered by lexicographic or dictionary order. For instance
'20' < '3'
because text is compared character by character, and '2' < '3'
.
But you want the values to be ordered as numbers. In which case you should first convert them to numbers, and then sort numbers rather than text. Do that by:
TArray<Integer>
, sized to hold each value of your string list. StrToInt
to convert each string from the list to an integer.TArray.Sort<T>
from the Generics.Collections
unit. Or perhaps a better approach is not to store the values as text in the first place. Hold the data as an array or list of integers and then sort that. After all, if your data are integer valued it makes sense to store them as such.
If you hold the data in an array, you can sort as described above with TArray.Sort<T>
. Or if you use TList<T>
then avail yourself of its Sort
method.
Upvotes: 10
Reputation: 598448
As David explained in his answer, you are sorting the strings as text characters, not as integers.
You can use the TStringList.CustomSort()
method, converting the strings to integers when comparing them:
function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
Value1, Value2: Integer;
begin
Value1 := StrToInt(List[Index1]);
Value2 := StrToInt(List[Index2]);
if Value1 < Value2 then
Result := -1
else if Value2 < Value1 then
Result := 1
else
Result := 0;
end;
SL.Add('18');
SL.Add('20');
SL.Add('3');
SL.Add('44');
SL.Add('53');
SL.CustomSort(MySortProc);
Alternatively, store the actual integer values in the Objects
property so you do not have to continuously convert the strings while sorting:
function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
Value1, Value2: Integer;
begin
Value1 := Integer(List.Objects[Index1]);
Value2 := Integer(List.Objects[Index2]);
if Value1 < Value2 then
Result := -1
else if Value2 < Value1 then
Result := 1
else
Result := 0;
end;
SL.Add('18', TObject(18));
SL.Add('20', TObject(20));
SL.Add('3', TObject(3));
SL.Add('44', TObject(44));
SL.Add('53', TObject(53));
SL.CustomSort(MySortProc);
Alternatively, you can use StrCmpLogicalW()
to let Windows compare the strings for you:
function StrCmpLogicalW(const psz1, psz2: PWideChar): Integer; stdcall; external 'Shlwapi.dll';
function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := StrCmpLogicalW(PChar(List[Index1]), PChar(List[Index2]));
end;
SL.Add('18');
SL.Add('20');
SL.Add('3');
SL.Add('44');
SL.Add('53');
SL.CustomSort(MySortProc);
Upvotes: 7
Reputation: 47889
This is an approach quite similar to that of Remy, but instead of using CustomSort
it uses a new class derived from TStringList
that overrides CompareStrings
. The implementation uses CompareValue
from System.Math and the string helper function ToInteger
.
The advantage is that the new class can have the Sorted
property set to true (which triggers a sort btw). This makes the Find
function work, which in turn is used by IndexOf
. Also newly added (Integer) strings are inserted in sorted order.
type
TIntegerStringList = class(TStringList)
protected
function CompareStrings(const S1: string; const S2: string): Integer; override;
end;
function TIntegerStringList.CompareStrings(const S1, S2: string): Integer;
begin
result := CompareValue(S1.ToInteger, S2.ToInteger);
end;
If this is not a requirement I would also opt for David's solution for performance reasons.
Upvotes: 1
Reputation: 16125
The fastest method is described by David - transform strings into array of integers, sort that array and then re-create the stringlist if you must (or better keep it integerlist).
I would add the most lazy approach though. It might be good enough for small lists (about 100 lines) but it would be slower than the proper method aforementioned, and it would get more and more slow the more items you have.
The lazy approach is to install Jedi Code Library and use the enhanced StringList.
There you can do it like this:
var sl: iJclStringList;
begin
sl := JclStringList();
sl.Split( '18, 20, 3, 44, 53', ',' ).Trim().SortAsInteger();
ShowMessage( sl.Join(', ') );
ShowMessage( sl.Join(^M^J) );
sl := nil;
end;
Again, this method is doing lot of redundant extra work, so for any more or less significant data amounts proper way of using integers is much preferred.
Upvotes: 0
Reputation: 6848
You're using TStringList
(list of string), so the sort works for strings, not numbers.
A simple solution is use the sort method that comes witgh TStringList
.
You must introduce a small variant in code.
If you are using TStringList
you are converting Numbers to Strings and viceversa. With the same work you can insert the Numbers in TStringList
with a format that make the sort method work correctly.
TStringList
(Sorted
property is False -default-)TStringList
(and convert it to Strings), use '0' on left part.00000018
00000020
00000003
00000044
00000053
Sorted
property to True
and let the TStringList
make the work.00000003
00000018
00000020
00000044
00000053
StrToInt
as you did before.Upvotes: 0