Bug in TList.Sort?

Für Fragen rund um die Ide und zum Debugger
Antworten
br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Bug in TList.Sort?

Beitrag von br_klaus »

Hallo,
mir scheint, daß in TList.Sort ein Bug ist.

Ich versuchte zunächst folgendes Programm:

Code: Alles auswählen

   var aa:array[0..14] of integer = (4,1,1,2,5,2,3,1,2,8,4,1,6,9,2);
          Liste: Tlist;
 
    function IntSort(p,q:Pchar):integer;
       begin
         result:= Pinteger(p)^-Pinteger(q)^;
       end;
Procedure SortInt;
var i,n:integer;
  begin
    n:=length(aa);
   Liste:=Tlist.create;
   with liste do
     begin
          for i:=0 to n-1 do add(@aa[i]);
          sort(@IntSort);   // Error!
          for i:=0 to n-1 do aa[i]:= Pinteger(items[i])^;
     end;
  end;
 
 
Beim Aufruf von Liste.Sort(@IntSort) bekam ich jedoch eine Fehlermeldung SIGSEGV o.ä.

Woran mag das liegen? Was mache ich da verkehrt?

Schließlich erstellte ich mir eine eigene Sortierprozedur für beliebige Arrays (mit Hilfe der Unit Variants), wobei ich da zwischen den Sortieralgorithmen wechseln kann.
Als Sortierliste, in der alle Verschiebe- und Vertauschoperationen erfolgen, nehme ich jedoch statt eines Pointerarrays wie in Lazarus ein Inegerarray. Damit kann ich m.E. leichter den Sortiervorgang verfolgen.
(Da hätte ich auch eine Frage: warum ist die Listlänge MaxListSize in Lazarus nur maxint div 16, und nicht maxint div 4? Ein Pointer hat doch nur 4 Bytes (bzw 8 bei CPU64), und keine 16.

Je nach varType der ArrayElemente wird eine andere Vergleichsfunktion aufgerufen:

Code: Alles auswählen

function VarTypeSort(vt:TVarType; p,q:Pchar):integer;
  begin  result:=0;
    case vt of
      varbyte:        result := byte(p^) - byte(q^);
      varShortint:    result := shortint(p^) - Shortint(q^);
      varWord:        result := Pword(p)^ - PWord(q)^;
      varSmallint:    result := PSmallint(p)^ - PSmallint(q)^;
      varinteger:     result := Pinteger(p)^ - Pinteger(q)^;
      varLongWord:  if PLongWord(p)^>PLongWord(q)^ then result := 1 else
                    if PLongWord(p)^<PLongWord(q)^ then result := -1;
      varint64:     if PInt64(p)^>PInt64(q)^ then result := 1 else
                    if PInt64(p)^<PInt64(q)^ then result := -1;
      varQWord:     if PQWord(p)^>PQWord(q)^ then result := 1 else
                    if PQWord(p)^<PQWord(q)^ then result := -1;
 
  // real, double
      vardouble:  if PDouble(p)^ < PDouble(q)^ then result := -1 else
                  if PDouble(p)^ > PDouble(q)^ then result := 1;
      varsingle:  if PSingle(p)^ < PSingle(q)^ then result := -1 else
                  if PSingle(p)^ > PSingle(q)^ then result := 1;
 
      varstring:      result := Comparestr(p,q);
    end;
  end;
 
 
Habe dann ein Object (oder Class?) erstellt mit folgender Struktur

Code: Alles auswählen

{$DEFINE Zeigen}
{$DEFINE JumpSort} // meine eigene Prozedur
 
Type 
 
TDWordArray = array[0..maxint div 4 -1] of DWOrd; PDWordArray = ^TDWordArray;
 
TSortArrayType = object  // eindimensionale Records
  Fdata:Pchar;
  FSize, FCount:dword; Ftype:word;
  FSkip_Doubles:boolean;  
  pp:PDWordArray; pplg:dword; ppos:dword;
 
{$IFDEF JumpSort}
  temp:PDwordArray;   templg:dword;
  next: array of integer; // Vergleich mit nächstem Element
akt:dword; // aktuelles zu sortierendes Element
  ReverseCount:integer;
  function Get_Next(x:dword; neu:boolean):integer;
  function Get_Next:integer; overload;
  procedure _move(x,y,cnt:dword); // cnt Elemente von x nach y verschieben, x>y
  procedure _insert(x:integer); // n,n+1.. ab x einfuegen, solange<=x
  Procedure Erhoehen; // Solange rechts grüßer
  procedure Reverse(start, ende:dword); // start..ende Reihenfolge umkehren
  Procedure Get_Reverse(n:dword);
  procedure JumpSort;  // ProcModus: 2
{$ENDIF}
 
 
 public
  {$IFDEF Zeigen}
  bb: array[0..20] of integer;
  Procedure Zeigen;
  {$ENDIF}
 
  procedure SortArray(var a; lg:dword; size:integer; v:variant; modus:array of integer);
            // modus = [SortProc, Skip_doubles, ReverseCount]
  function Sort(x,y:dword):integer;
  procedure Swap(x,y:dword);
  procedure _Delete(n,x:dword); // löscht n..x
  procedure Delete_Doubles;
 
  Procedure LazQSort(L,R:dword);  // ProcModus: 1
  procedure KOLQSort(start,anz:Dword); // ProcModus: 0
 
 
 end;
 
 
LazQSort ist Lazarus-Quicksort
KOLQSort ist KOL-QSorthelp

JumpSort ist eine eigene SortProzedur (vielleicht gibt es die schon unter anderem Namen: Jedes Mal wird der Suchbereich um die Hälfte verkleinert, bis der Einfügeplatz gefunden wurde. Habe aber noch ein paar Extras hinzugefügt, wodurch es glaube ich noch schneller geht:
u.a. Umkehr der Reihenfolge, sobald diese eine bestimmte Anzahl in absteigender Folge überschreiten.

Mit der Zuhilfenahme von Arrays für die Varianttypen und den Offset läßt sich das auch auf beliebige Records erweitern. Braucht aber noch ein bißchen mehr Prorammieraufwand.
Werde es noch nachschicken, sobald es funktioniert.

Wenn ich nun mit obiger adaptierter Lazarus-QuickSort-Prozedur (habe wortwörtlich die Angaben übernommen, nur an mein Integerarray angepasst) das zu Beginn genannte array aa sortiere, dann bekomme ich als Ergebnis der Sortierung folgendes:

Len=15: {1, 1, 1, 1, 2, 2, 2, 3, 2, 4, 4, 5, 6, 8, 9}


mit KOL (und JumpSort) erhalte ich hingegen das korrekte Ergebnis:

Len=15: {1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4, 5, 6, 8, 9}


irgendwo muß da also ein Bug sein. Bei mir? in Lazarus?




Hier noch die Implementierung (in der Datei Sort.inc)
Dateianhänge
sort.inc
(9.08 KiB) 57-mal heruntergeladen

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Re: Bug in TList.Sort?

Beitrag von theo »

Ich habe nur mal die erste Aufgabe angeschaut:

So müsste es gehn. Du solltest nicht mit Pointern auf das Array arbeiten, es geht nur um typecasting (Pointer als Integer missbrauchen).
Aber ganz sauber ist das wahrsch. nicht mit Integer. Cardinal evtl.

Code: Alles auswählen

function IntSort(p, q: Pointer): integer;
begin
  Result := integer(p) - integer(q);
end;
 
procedure SortInt;
var
  i, n: integer;
begin
  n := length(aa);
  Liste := TList.Create;
  for i := 0 to n - 1 do  Liste.add(Pointer(aa[i]));
  Liste.sort(@IntSort);   
  for i := 0 to n - 1 do  aa[i] := PtrInt(Liste[i]);
  Liste.free;
end;
 

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Bug in TList.Sort?

Beitrag von Socke »

theo hat geschrieben:So müsste es gehn. Du solltest nicht mit Pointern auf das Array arbeiten, es geht nur um typecasting (Pointer als Integer missbrauchen).
Aber ganz sauber ist das wahrsch. nicht mit Integer. Cardinal evtl.
Um Zeiger in Ganzzahlen zu konvertieren gibt es die Typen PtrInt (mit Vorzeichen) und PtrUInt (ohne Vorzeichen). Diese sind immer genau so groß wie ein Zeiger.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Re: Bug in TList.Sort?

Beitrag von theo »

Socke hat geschrieben:
theo hat geschrieben:So müsste es gehn. Du solltest nicht mit Pointern auf das Array arbeiten, es geht nur um typecasting (Pointer als Integer missbrauchen).
Aber ganz sauber ist das wahrsch. nicht mit Integer. Cardinal evtl.
Um Zeiger in Ganzzahlen zu konvertieren gibt es die Typen PtrInt (mit Vorzeichen) und PtrUInt (ohne Vorzeichen). Diese sind immer genau so groß wie ein Zeiger.
Ja, habe ich in SortInt eigentlich auch verwendet. Das müsste man im IntSort auch nocht tun.

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: Bug in TList.Sort?

Beitrag von br_klaus »

Herzlichen Dank für die vielen Hinweise!

Beim Nachschauen, was PtrInt ist, habe ich gesehen, daß das letztlch nichts anderes ist als longint.
Das heißt, daß man auf dieselbe Weise auch Word, Byte, smallint, qword etc. abfragen kann.
(result := qword(p) - qword(q) bzw. byte(p) - byte(q) ).
Nur bei komplexeren Zahlen wie single und real muß ich doch beim Vergleich bleiben mit PointerCasting (if PDouble(p)^>PDouble(q)^ then result := 1 etc.)

Habe mit den vorgeschlagenen Änderungen mein Array mit Tlist.Sort sortiert - korrektes Ergebnis!
Also muß der Bug doch bei mir liegen. Bloß wo? Werde noch einmal genauer nachschauen, ob ich nicht vielleicht doch ein < statt <= geschrieben habe o.ä.

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Re: Bug in TList.Sort?

Beitrag von theo »

br_klaus hat geschrieben: Beim Nachschauen, was PtrInt ist, habe ich gesehen, daß das letztlch nichts anderes ist als longint.
Aber nur auf 32bit. Bei CPU64 ist es Int64 und PtrUInt ist QWord. Das ist ja der Witz dabei.

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: Bug in TList.Sort?

Beitrag von br_klaus »

Ich habe auch gemerkt, daß das nicht so ganz geht. Habe statt PtrInt integer geschrieben.
Mit result:=integer(p)-integer(q) bekomme ich aber nur die Differenz zwischen den beiden Adressen, aber nicht die zwischen den Werten, auf die die beiden Pointer zeigen. Da muß ich wohl doch schreiben
result:=integer(p^)-integer(q^)
bzw bei Bytes result:=byte(p^) - byte(q^), oder?


Habe auch versucht, meinen Bug zu finden.

Ich blicke noch nicht so ganz durch, wie der Algorithmus von Quicksort aufgebaut ist, mir ist aber aufgefallen, daß da noch ein paar Dinge verbessert werden können, um unnötige Programmschritte (und damit Zeitverlust) zu vermeiden.

1) Zunächst heißt es da (bei mir ab Zeile 312)

Code: Alles auswählen

     If I <= J then
     begin
       Q := FList^[I];
       Flist^[I] := FList^[J];
       FList^[J] := Q;
       I := I + 1;
       J := J - 1;
     end;
 
Die ersten drei Zeilen machen nichts anderes als Zeiger i und j zu vertauschen. Das ist aber wenig sinnvoll, falls i=j ist. Da wäre wohl besser zu schreiben:
if i<j then begin ... Tausch(i,j)... end;

2) Dann wird Quicksort (L,R) auch aufgerufen, auch wenn nur eine Differenz von 1 zwischen den L und R ist. Und auch dann, wenn beide Elemente gleich sind.
Da würde eine einfache Abfrage genügen, ob <, > oder = und entsprechend tauschen.


3) Es heißt zu Beginn der Prozedur (Zeile 304)

Code: Alles auswählen

   I := L;
   J := R;
   P := FList^[ (L + R) div 2 ];
   repeat
     while Compare(P, FList^[i]) > 0 do
       I := I + 1;
     while Compare(P, FList^[J]) < 0 do
       J := J - 1;
       ....
 
 
Das heißt, sollten die beiden Elemente gleich sein, dann geschieht keine Iteration. Wäre es da nicht sinnvoller, bei diesen beiden while's nicht > 0 oder < 0 zu schreiben, sondern >= 0 und <= 0. Das Programm kann sich ja Beginn bzw Ende des Blocks gleicher Elemente merken und dann gleich mehrere Zeiger en bloc verschieben als nur immer zwei Zeiger miteinander zu vertauschen. (So habe ich es auch in meiner Sortprozedur gemacht)

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Re: Bug in TList.Sort?

Beitrag von theo »

Ich glaube du hast irgendwie einen Knopf in der Leitung :wink:

Nochmal zum ersten Problem: Du kannst schon dort mit Pointern arbeiten, wenn du möchtest, aber du brauchst dann ein zweites Array, sonst überschreibst du die Werte beim zurückkopieren und dann wird das Resultat falsch.

Schau dir mal das an:

Code: Alles auswählen

var
  aa: array[0..14] of integer = (4, 1, 1, 2, 5, 2, 3, 1, 2, 8, 4, 1, 6, 9, 2);
  bb: array[0..14] of integer;
  Liste: TList;
 
function IntSort(p, q: Pointer): integer;
begin
  Result := Pinteger(p)^ - Pinteger(q)^;
end;
 
procedure SortInt;
var
  i, n: integer;
begin
  n := length(aa);
  Liste := TList.Create;
  for i := 0 to n - 1 do
  begin
    Liste.add(@aa[i]);
    writeln(PtrUInt(@aa[i]), ' ', aa[i]);
  end;
  Liste.sort(@IntSort);   
  writeln('SORT');
  for i := 0 to n - 1 do
  begin
    bb[i] := Pinteger(Liste.items[i])^;
    writeln(PtrUInt(Liste.items[i]), ' ', bb[i]);
  end;
  Liste.free;
end;    

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: Bug in TList.Sort?

Beitrag von br_klaus »

Jezt ist ein anderes Problem aufgetaucht:
Ich wollte verfolgen, wie Lazarus die Sortierung vollzieht, aber beim Drücken von F7 an der Stelle Liste.Sort(@Sortint) liest der Compiler nicht die Datei mit dem Quelltext (list.inc o.ä.), sondern geht gleich auf Assembler über. (Da würde ich übrigens auch lieber die Intel-Syntax sehen, aber ich weiß nicht, wie ich die Ausgabe ändern kann). Da nützt es überhaupt nichts, wenn ich Breakpoints im Quellcode von Quicksort setze.
Wie kann ich dem Compiler sagen, er soll den Quellcode während der Programmausführung neu einlesen?
Kann es sogar sein, daß die beiden (Quellcode und compilierter Code) unterschiedlich sind? Ich habe nämlich die neueste Version der snapshots von Lazarus mit FPC 2.71 neu installiert, ohne jedoch die alten Dateien vorher vollständig zu löschen.

Praktisch wäre auch, man könnte im AssemblerCode selber Breakpoints setzen, und auch eine Tastenkombination (oder Image) für "Run bis Cursor".

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Re: Bug in TList.Sort?

Beitrag von theo »

Die RTL ist meistens nicht mit Debug Info kompiliert.
http://www.lazarusforum.de/viewtopic.php?f=5&t=3919

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: Bug in TList.Sort?

Beitrag von br_klaus »

theo hat geschrieben:Die RTL ist meistens nicht mit Debug Info kompiliert.
http://www.lazarusforum.de/viewtopic.php?f=5&t=3919
Herzlichen Dank, ich habe diese Beiträge gelesen. Da kommt mir eine Frage: kann man denn die Debug-Infos in RTL und FCL nicht mittels Compilerschalter aktivieren/deaktivieren ? Das heißt, man bräuchte wohl zwei Lazarus-Programme nebeneinander, das eine mit, das andere ohne Debug-Informationen (lazarus.exe, dbglazarus.exe) . Aber das ließe sich doch sicher leicht autmatisch schon beim Installieren machen, oder?

Antworten