Thunderx
Thunderx

Reputation: 169

How can I sort numbers in stringlist?

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

Answers (5)

David Heffernan
David Heffernan

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:

  • Create an array TArray<Integer>, sized to hold each value of your string list.
  • Populate the array using StrToInt to convert each string from the list to an integer.
  • Sort the array using 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

Remy Lebeau
Remy Lebeau

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

Uwe Raabe
Uwe Raabe

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

Arioch &#39;The
Arioch &#39;The

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

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.

  1. Create the TStringList (Sorted property is False -default-)
  2. When you insert the numbers on TStringList (and convert it to Strings), use '0' on left part.

00000018

00000020

00000003

00000044

00000053

  1. Change the Sorted property to True and let the TStringList make the work.
  2. Now the numbers are sorted.

00000003

00000018

00000020

00000044

00000053

  1. When you obtain a number convert the string with standard StrToInt as you did before.

Upvotes: 0

Related Questions