Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Sort will nicht so, wie ich das will, Problem mit "-"


Gausi - Do 12.03.20 16:44
Titel: Sort will nicht so, wie ich das will, Problem mit "-"
Ich steh hier grade vor einem merkwürdigem Problem.

Gegeben ist StringList, die ich sortieren möchte.
Beispiel:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
a:--b
a:--c
a:--d
a:-b
a:-c
a:-d
a:---b
a:---c
a:---d


Mit Sort, oder auch mit einer eigenen Sortiermethode auf Basis von AnsiCompareText erhalte ich diese Sortierung:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
a:-b
a:--b
a:---b
a:-c
a:--c
a:---c
a:-d
a:--d
a:---d

Die "-" werden anscheinend für die Sortierung ignoriert, bzw. haben eine andere Bedeutung.

Ich hätte aber gerne diese Sortierung:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
a:-b
a:-c
a:-d
a:--b
a:--c
a:--d
a:---b
a:---c
a:---d


Was übersehe ich hierbei, bzw. wie kann ich das ändern? :gruebel:

Verhalten reproduziert in Delphi 2009 und der aktuellen 10.3 CE. Andere "Sonderzeichen" wie ";" oder "_" werden so sortiert wie gewünscht.


jaenicke - Fr 13.03.20 07:51

AnsiCompareText ist nichts anderes als ein Wrapper um die API-Funktion CompareString. Diese führt eine wortbasierte Sortierung durch. Deshalb werden Bindestriche und Apostrophe bei der Sortierung ignoriert um z.B. Web-Entwickler und Webentwickler zusammen zu lassen.

Man kann das mit einem Flag ändern, aber das nur über die direkte API-Funktion. Ein Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
function AnsiCompareText2(const S1, S2: string): Integer;
begin
  Result := CompareString(LOCALE_USER_DEFAULT, SORT_STRINGSORT, PChar(S1),
    Length(S1), PChar(S2), Length(S2)) - CSTR_EQUAL;
end;

function StringListCompareExample(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := AnsiCompareText2(List[Index1], List[Index2]);
end;

procedure TForm47.FormCreate(Sender: TObject);
var
  MyClass: TStringList;
begin
  MyClass := TStringList.Create;
  try
    MyClass.Text := Memo1.Lines.Text;
    MyClass.CustomSort(StringListCompareExample);
    Memo1.Lines.Text := MyClass.Text;
  finally
    MyClass.Free;
  end;
end;

Das ergibt:
Zitat:
a:---b
a:---c
a:---d
a:--b
a:--c
a:--d
a:-b
a:-c
a:-d
a:blub
a:Chamäleon
a:delta
a:Delta

Bindestriche kommen da natürlich vor Buchstaben und dementsprechend passiert die Sortierung hier genau umgekehrt zu deinem Wunsch. Solange du nur solche Strings hättest, könntest du es natürlich einfach umkehren.

Es gibt übrigens auch noch ein Flag SORT_DIGITSASNUMBERS um "Test 10" nach "Test 2" einzusortieren (ab Windows 7, wie dort auch im Windows Explorer eingeführt).


Th69 - Fr 13.03.20 08:38

Schreib doch einfach eine eigene Compare-Funktion und rufe TStringList.CustomSort [http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Classes_TStringList_CustomSort.html] auf.


Gausi - Fr 13.03.20 09:46

Danke, das wird es dann wohl gewesen sein - so was in der Richtung hatte ich auch vermutet. Aber das wird so ein Bugfix, bei dem ich dann drei weitere einbaue :lol:. Ich hatte irgendwie gehofft, dass es da ein globales Setting für gibt.

Die CustomSort-Funktionen sind kein Problem - die sind eh schon im Code drin. Tatsächlich habe ich ObjectLists, die ich nach einer String-Property sortiere. Das kann ich also recht einfach anpassen.

Wo ich dann aufpassen muss ist, dass ich wirklich alle relevanten (ggf. versteckten) Aufrufe von CompareString erwische. Denn diese Listen werden nicht nur sortiert, darin wird dann auch binär gesucht. Und nicht nur einzelne Objekte, sondern auch mal alle, die mit einem bestimmten Teilstring beginnen ... das ist immer ein Spaß, wenn man so tief unten eine Änderung machen muss. :mrgreen:


hRb - Do 19.03.20 01:07

Hallo Gausi,
ich bin nicht sicher das Probleme korrekt verstanden zu haben. Mir stellt es sich dar, als wäre die Stringlänge (mit Vorrang) wichtiger als das was folgt. Hatte ein vergleichbares Problem.
Ich habe Personenlisten und muss diese "familiengerecht" sortieren, d.h. bei gleichen Nachnamen gilt: Familien zusammenhalten (über Wohnadresse), Eltern vor den Kindern (übers Alter). Ich habe folgende Zeilenstruktur in Richedit:
Name Vorname Alter Straße Stadt weitereDaten ...
Meine Lösung: (verkürzt)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
MyListBox.Clear;
MyListBox.sorted:=false
for i:=0 to Richedit.Lines.Count-1 do begin
  s:=copy(Name)+copy(Stadt)+copy(Straße)+copy(Alter)+copy(Vorname)+ copy(Originalzeile); //Sortieren nach Name, Adresse, Alter, Vorname 
  MyListBox1.Items.Add(s)
end;
MyListBox.Sorted:=true //Einträge sortieren
Nun vorderen Teil wieder Löschen und alles zurück nach Richedit.

Meine Überlegung also für Dich: Setze Zeilenlänge vor den Sortiertext. Habe folgendes getestet: funktioniert!
1. nehme Richedit, Listbox, Button


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
function IntToString(w : Longint; i:integer; ch: Char{Ansichar}) : string;
{füllt einen string mit führenden Zeichen bis zur gewünschten stringlänge}
var
  s : string; k:integer;
begin
  Str(w:0,s);
  if Length(s) < i then
     for k:=length(s)+1 to i do  s:=ch + s;
  IntToString:= s;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i: integer; s:string ;
begin
  MyListBox1.Clear;
  MyListBox1.sorted:=false;
  for i:=0 to Richedit1.Lines.Count-1 do
  begin
    s:=Richedit1.Lines[i];
    s:=IntToString(length(s),5,'0')+s;
    MyListBox1.Items.Add(s)
  end;
  MyListBox1.Sorted:=true //Einträge sortieren
end;

In MyListBox steht gewünschtes Sortier-Ergebnis, nun alle Zeilen zurück nach Richedit, dabei die ersten 5 Zeichen der Zeile löschen


Gausi - Do 19.03.20 09:01

Ne, das ist glaube ich was anderes. In deinem Fall würde ich auch nicht über Strings gehen und diese sortieren, sondern über ObjectLists, und in deren CustomSort die enthaltenen Objekte nach den passenden Werten sortieren.

Bei mir sind die Strings, die ich sortieren möchte, Dateipfade, und ich möchte diese nach Ordnern sortiert ausgeben. Wenn ich dabei einen Ordner "Sampler" habe, und einen Ordner "-Sampler", dann kommt es bei meiner alten Methode zu diesem unerwünschtem Verhalten. Dann sind eben nicht alle "Sampler" zusammen und dahinter (oder davor) alle "-Sampler". Ist mir bis heute noch nicht aufgefallen ... :oops:


Sinspin - Do 19.03.20 13:58

Hallo, das sind intern ja nur Zahlen. Und Minus (ASCII index 45) kommt for allen Zahlen und Buchstaben.


Gausi - Do 19.03.20 14:55

Das ist mir schon klar. Aber die Sortiermethoden von Strings sind da schon etwas komplizierter geworden. Wie jaenicke schon richtig schrieb, wird da eine API-Funktion aufgerufen. Und wenn man sich die Parameter so anguckt, wird einem ganz schwummrig. :lol:

Einer dieser Parameter (der bei StringList.Sort bzw. AnsiCompareText verwendet wird) sorgt eben dafür, dass "a-b" und "ab" als "gleich" angesehen werden.