Autor Beitrag
Heiko
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 3169
Erhaltene Danke: 11



BeitragVerfasst: Mi 05.10.05 15:21 
Kann jemand vlt. mir sagen, welches Sortierverfahren am schnellsten ist für Strings, denn QuickSort ist ja bekanntlich nicht die schnellste Variante. Die Anzahl der Strings beläuft sich da von 0-10 000, wobei es meistens im Bereich von 500-2000 ist. ;)

PS: Am liebsten wäre es mit gleich mit einem Link zu der Erklärung des Verfahrens und am besten gleich mit Quelltext, der aber nicht zwingend dabei sein muss ;).
uall@ogc
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1826
Erhaltene Danke: 11

Win 2000 & VMware
Delphi 3 Prof, Delphi 7 Prof
BeitragVerfasst: Mi 05.10.05 15:40 
Wofür brauchst du was schnelleres als Qicksort? Das wird doch wohl für deine Zwecke reichen. Qucksort hat schon nen laufzeitverhalten von n*log(n) was schnell ist, gibt noch heapsort und mergesort die gleich schnell sind. aber glaub für die paar elemente ist das egal. ich kenn nur einen alg mit dem laufzeitverhalten n, aber den kannste auf strings nicht anwenden.

_________________
wer andern eine grube gräbt hat ein grubengrabgerät
- oder einfach zu viel zeit
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Mi 05.10.05 15:45 
Wenn es sehr viele relativ kurze Strings sind, würde sich RadixSort empfehlen. den müsste man evtl. nur anpassen, da deine Elemente (Strings) nicht alle gleich lang sind.

Da du aber nur ziemlich wenige Strings sortieren musst, und du für jedes Zeichen einen Durchlauf machen musst (z.B wenn du das Wort "Sortierverfahren" in deiner Stringliste hast, musst du 16 Durchläufe machen), dürfte der Aufwand von O(N LogN) im Mittel bei Quicksort in deinem Fall fast besser sein als der lineare Aufwand bei RadixSort.
Hinzu kommt ein erhöhter Speicherbedarf für RadixSort.

Überschlagsrechnung:
ausblenden Quelltext
1:
2:
3:
16 * N ?<? logN * N
16 ?<? logN
2^16 ?<? N


Fazit: Wenn du so in etwa mehr als 2^16 (bzw. 2^x, wobei x die Länge deines längsten Wortes ist) Strings sortieren musst, dann würde ich von Quicksort abraten, ansonsten bleib besser dabei.
Anmerkung: Ich weiss, dass in der Laufzeit von Quicksort auch noch Konstanten drin sind, und man deshalb eigentlich nicht so rechnen kann, aber das Prinzip sollte klar sein...

_________________
We are, we were and will not be.
Grishnak
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 221

Windows XP Home
Delphi 7 PE, Delphi 2005 PE
BeitragVerfasst: Mi 05.10.05 15:47 
@Heiko: Wichtig zu wissen: Sind diese Strings a) aufsteigend vorsortiert, b) absteigend vorsortiert oder c) unsortiert? Bei aufsteigend vorsortierten Feldern ist Quicksort mWn nicht so effektiv und wird von "primitiveren" Algorithmen geschlagen. Ansonsten kenne ich Quicksort als schnellste Möglichkeit.

_________________
Mach' etwas idiotensicher und irgendjemand erfindet einen besseren Idioten!
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Mi 05.10.05 15:55 
user profile iconGrishnak hat folgendes geschrieben:
Bei aufsteigend vorsortierten Feldern ist Quicksort mWn nicht so effektiv und wird von "primitiveren" Algorithmen geschlagen.

Das kommt sehr darauf an, wie man bei Quicksort das Pivotelement wählt. Die Methode "Sort" bei Delphi verwendet einen Quicksort mit Pivot-Element = mittleres Element(*), sodass bei auf-/ absteigend sortierten Listen eine optimale Aufteilung in zwei Teilfolgen erreicht wird. Daher zweifele ich deine Aussage zumindest teilweise an.

Wenn man als Pivotelement jedoch das erste/letzte Element wählt, bekommt man bei solchen Listen natürlich eine quadratische Laufzeit. Da Bubblesort die Vorsortierung in dem Fall besser ausnutzt, liegt dann Quicksort weit hinten.


(*) hab ich herausgefunden, als ich Sort aufgerufen habe mit einer Liste von 10.000 Elementen, die dadurch entstanden ist, dass ich 2 identische 5000er Listen aneinandergehägt habe. Der Sortiervorgang hat solange gedauert, dass ich das Prog mit Gewalt beenden musste. Hab ich diese Liste vorher "randomisiert", oder eine sortierte Liste mit 10.000 Elementen sortiert, war er in nullkommanix fertig.

_________________
We are, we were and will not be.
Udontknow
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: Mi 05.10.05 16:03 
Ein allgemeine Praxishinweis (vielleicht ein wenig Offtopic, aber bei 5000-10000 Strings und trotdem langer Wartezeit liegt es vielleicht sogar daran):

Variablen immer mit const oder var an Subroutinen übergeben, ansonsten kann sich die benötigte Zeit dramatisch erhöhen, da das Programm bei jedem Aufruf von Subroutinen erst den Variableninhalt kopieren muß. Bei Pointern, Integerwerten etc. ist das egal, aber alle Variablen, die größer als ein Pointer (also 4 Byte) sind, sollten so an Subroutinen übergeben werden (es sei denn, das spezielle Verhalten, daß Variablen innehrhalb der Routine änderbar sind, aber die Änderung nicht an die aufgerufene "Ebene" zurückgegeben werden, ist erwünscht).

Cu,
Udontknow
Grishnak
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 221

Windows XP Home
Delphi 7 PE, Delphi 2005 PE
BeitragVerfasst: Mi 05.10.05 16:54 
@Gausi:

Ich hab jetzt mal 50.000 Zufalls-Strings der Länge 8 in einer StringList gespeichert und mittels Sort-Methode sortieren lassen. Das ganze wurde anschließend nochmals sortiert.

Dabei hat das erneute Sortierten der bereits sortierten Liste ca. 66% der Zeit gedauert, die das Sortieren einer unsortierten Liste dauerte. Das deutet mMn nicht auf die Wahl des mittleren Elementes als Pivot-Element hin!

_________________
Mach' etwas idiotensicher und irgendjemand erfindet einen besseren Idioten!
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Mi 05.10.05 17:05 
user profile iconGrishnak hat folgendes geschrieben:
@Gausi:

Dabei hat das erneute Sortierten der bereits sortierten Liste ca. 66% der Zeit gedauert, die das Sortieren einer unsortierten Liste dauerte. Das deutet mMn nicht auf die Wahl des mittleren Elementes als Pivot-Element hin!


Hast du dich hier vertippt, oder bestätigst du damit nicht meine Aussage? Wenn das sortieren der sortierten Liste nur 66% der Zeit zum sortieren der unsortieren Liste benötigt, dann ist das weniger ;-)

Wie ich sagte, basiert meine Vermutung auf einem Test, den ich gemacht habe. Und es erscheint mir auch sinnvoll, nicht das letzt Element zu nehmen, da es doch öfter vorkommt, dass man eine fast sortierte Liste novhmal schnell "drübersortiert".

Und Schwankung um den Faktor 2 sind durchaus noch im "Messfehlerbereich" ;-)

_________________
We are, we were and will not be.
Heiko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 3169
Erhaltene Danke: 11



BeitragVerfasst: Mi 05.10.05 17:12 
So bin jetzt wieder vom Einkaufen zurück und kann auf ein paar Nachfragen jetzt Antwort geben :).

@Länge der Strings: Sie ist ganz verschieden. Wie sich bei dieser Typendeklaration sicherlich denken kann.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
type
  TMusikInfo=record
    DateiPfad: String;
    Titel: String;
    Interpret: String;
    Album: String;
    Genre: String;
    Jahr: Integer;
    TitelNr: Integer;
    Kommentar: String;
    Komponist: String;
    Encoder: String;
    Copyright: String;
    Sprache: String;
    Link: String;
    Format: String;
  end;


Ich will also immer nach einem dieser Elemente sortieren (dynamisches Array), wodurch ich vorher nicht sagen kann, wie lang ein String ist, da es ja je nach dem Kriterium verschieden ist und ich nicht für jedes Element ein extra Verfahren haben will.

@Vorsortiert: Jein, die sind nach einem Kriterium sortiert, welches keine Rolle für das Suchverfahren besitzten wird. Sprich: Es kann sein das die Liste nach dem titel sortiert ist und jetzt nach dem Album sortiert werden soll ;).
Udontknow
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: Mi 05.10.05 17:43 
Noch einmal offtopic:

Weitere Geschwindigkeitsvorteile gewinnst du, indem du in deinem Array nur Pointer auf per Getmem erstellte Records speicherst, sodaß beim Sortieren nicht der gesamte Record, sondern nur der Pointer auf einen Record ausgetauscht wird.

Cu,
Udontknow
Heiko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 3169
Erhaltene Danke: 11



BeitragVerfasst: Mi 05.10.05 17:45 
Mhm, ich kenne mich nicht so gut mit Pointern aus. Das mit GetMem und so bekomme ich hin, aber wie ich genau auf die einzelnen Elemente zeigen kann nicht. Muss ich mir mal angucken ;).

//Edit: Wie macht man es am besten, wenn in man die Größe eines Strings nicht kennt, den man einlesen will?
Udontknow
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: Mi 05.10.05 18:19 
Hier mal ein objektorientierter Ansatz. Dadurch, daß nun der Record als Objekt (sprich Pointer) definiert ist, sollte das auch schon einen Effekt auf die Geschwindigkeit haben.

Ich hätte es wohl von Anfang an als DB-Projekt konzipiert, aber naja...

Du musst ein TMusikinfos-Objekt erstellen. Bei diesem kannst du dann sooft du eben Daten hast Add aufrufen und den neuen Eintrag mit Daten bestücken. Sortiert wird diese Liste dann über die angegebenen zwei Methoden (ist leider unflexibel, da man die einzelnen Felder des Records nicht dynamisch über einen Index ansprechen kann, wie man es bei Feldern eines Datasets machen könnte).

ausblenden volle Höhe 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:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
unit U_Musikinfo;

interface

uses
  SysUtils, Classes;

type
  TMusikInfo=Class
    DateiPfad: String;  
    Titel: String;  
    Interpret: String;  
    Album: String;  
    Genre: String;
    Jahr: Integer;  
    TitelNr: Integer;  
    Kommentar: String;  
    Komponist: String;  
    Encoder: String;  
    Copyright: String;  
    Sprache: String;  
    Link: String;  
    Format: String;  
  end;

type TMusikInfos=class
  private
    FItems:TList;
    function GetItems(Index: Integer): TMusikInfo;
    procedure SetItems(Index: Integer; const Value: TMusikInfo);
    function GetCount: Integer;
  public
    function Add:TMusikInfo;
    procedure Delete(Index:Integer);
    procedure Clear;
    property Items[Index:Integer]:TMusikInfo read GetItems write SetItems;
    property Count:Integer read GetCount;

    procedure SortierenNachDateiPfad;
    procedure SortierenNachTitel;

    constructor Create; virtual;
    destructor Destroy; override;
end;

implementation

{$R *.dfm}

//Interne Funktionen zum Vergleichen

function Vergleich_Titel(Item1, Item2: Pointer): Integer;
var I1,I2:TMusikInfo;
begin
  I1:=TMusikInfo(Item1);
  I2:=TMusikInfo(Item2);

  Result:=0;
  if I1.Titel<I2.Titel then
    Result:=-1
  else
  if I1.Titel>I2.Titel then
    Result:=1
end;

function Vergleich_DateiPfad(Item1, Item2: Pointer): Integer;
var I1,I2:TMusikInfo;
begin
  I1:=TMusikInfo(Item1);
  I2:=TMusikInfo(Item2);

  Result:=0;
  if I1.DateiPfad<I2.DateiPfad then
    Result:=-1
  else
  if I1.DateiPfad>I2.DateiPfad then
    Result:=1
end;

{ TMusikInfos }

function TMusikInfos.GetItems(Index: Integer): TMusikInfo;
begin
  //Listenobjekt ausgeben
  Result:=TMusikInfo(FItems[Index]);
end;

procedure TMusikInfos.SetItems(Index: Integer; const Value: TMusikInfo);
begin
  //Listenobjekt setzen
  FItems[Index]:=Value;
end;

function TMusikInfos.Add: TMusikInfo;
begin
  //Neues Element erstellen und in Liste einfügen
  Result:=TMusikInfo.Create;
  FItems.Add(Result);
end;

procedure TMusikInfos.Delete(Index: Integer);
begin
  //Element entfernen (dabei freigeben)
  TMusikInfo(FItems[Index]).Free;
  FItems.Delete(Index);
end;

procedure TMusikInfos.Clear;
begin
  //Liste leeren (dabei Elemente freigeben)
  While FItems.Count>0 do
    Delete(0);
end;

function TMusikInfos.GetCount: Integer;
begin
  //Anzahl der Elemente zuückgeben
  Result:=FItems.Count;
end;

constructor TMusikInfos.Create;
begin
  //Interne Liste erstellen
  FItems:=TList.Create;
end;

destructor TMusikInfos.Destroy;
begin
  //Alle Elemente freigeben
  Clear;
  //interne Liste freigeben
  FItems.Free;
  inherited;
end;

procedure TMusikInfos.SortierenNachTitel;
begin
  FItems.Sort(Vergleich_Titel);
end;

procedure TMusikInfos.SortierenNachDateiPfad;
begin
  FItems.Sort(Vergleich_DateiPfad);
end;

end.


Cu,
Udontknow
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Mi 05.10.05 18:31 
Also, wenn deine Strings SO aussehen ;-), dann kann ich dir aus eigener Erfahrung sagen: Nimm ruhig die Standardvariante mit dem Quicksort. Das ist schnell genug. Und insbesondere beim Sortieren nach Pfad dürfte Quicksort wesentlich schneller sein.

Ich hab auch ziemlich viele solche "Strings", und wenn ich auf den Header in meinem VirtualTreeView klicke, dann gibt es ne Verzögerung von ca. 0,5 Sekunden, bis "1910 Fruitgum Company - Simon Says" ganz oben und "ZZ Top - What Would You Do" ganz unten ist ;-) Und bei mir ist die Anzahl noch etwas über deinem Maximalbereich (0-10.000)

_________________
We are, we were and will not be.
Grishnak
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 221

Windows XP Home
Delphi 7 PE, Delphi 2005 PE
BeitragVerfasst: Mi 05.10.05 19:03 
@Gausi: "Schneller" ist nicht das gleiche wie "schnell"! Wenn ein Sortiert-Algorithmus ca. 2/3 der Zeit für das sortieren eines bereits sortierten Feldes braucht, dann ist er dafür weniger geeignet - genau das wollte ich andeuten! Aber: back-to-topic!

_________________
Mach' etwas idiotensicher und irgendjemand erfindet einen besseren Idioten!
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 05.10.05 19:21 
Soweit ich weiss, soll Mergesort etwas schneller sein, aber ich denke, es lohnt den Aufwand nicht. Kopier dier das Quicksort von TStringlist und experimentier damit rum.

Wenn Du noch schneller werden willst, dann nimm ein iteratives Quicksort, das ist nochmal 10% schneller. Grundsätzlich sollte auf Rekursion verzichtet werden (so schon, wie sie ist), wenn es schnell gehen soll.

Hier, QnD (Quick'n Dirty):
ausblenden volle Höhe 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:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
procedure QuickSortIt (a : TIntegerArray; L,R : Integer);
var
  sp, I, J,p,t: Integer;
  s : Array [0..100,0..1Of Integer; // Stack

begin
  fillchar (s,sizeOf(s),0);
  sp := 1;
  s[0,0] := L;
  s[0,1] := R;
  While sp>0 Do Begin           // solange noch Teillisten abzuarbeiten sind ...
    dec (sp);                   // Hole nächste Teillösung von Stack
    l := s[sp,0];
    r := s[sp,1];

    repeat                      // --------- Quicksort Anfang
      I := L;
      J := R;
      P := a[(L + R) shr 1];
      repeat
        while a[i]<p do inc (i);
        while a[j]>p do dec (j);
        if I <= J then
        begin
          T := a[I];a[I] := a[J];a[J] := T;
          Inc(I);
          Dec(J);
        end;
      until I > J;
      if L < J then begin       // Statt rekursivem Aufruf wird die Teilliste
        s[sp,0] := l; s[sp,1] := j; // auf den Stack geschoben und später
        inc (sp);               // sortiert
        End;
      L := I;
    until I >= R;               // --------- Quicksort Ende
    End;
end;

_________________
Na denn, dann. Bis dann, denn.
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8548
Erhaltene Danke: 477

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Mi 05.10.05 19:25 
Ist ja eigentlich nicht OT, da es ja um geeignete (=schnelle) Sortierverfahren geht. Ich hatte dich da falsch verstanden. Ich dachte, du vergleichst beide Verfahren, aber die Angaben waren wohl für Quicksort bei 2 Listen.
Das widerspricht aber nicht meiner Vermutung. Sie bestätigt nur die Güte von Quicksort und die Tatsache, dass "im Mittel" die Liste gut aufgeteilt wird. Nur daher kommt ja die schnelle Laufzeitabschätzung.
D.h. eine unsortierte Liste wird nur um 30% langsamer sortiert, als wenn man eine optimale Aufteilung hat (ich gehe weiter von meiner Vermutung aus).

Es ist richtig, dass Quicksort keine Vorsortierung ausnutzen kann. Deshalb ist Quicksort nie dann optimal, wenn man sortierte Listen sortiert. Aber Qicksort ist in vielen Fällen sehr "quick", daher ja auch der Name. In der Praxis hat der sich ganz einfach bewährt.

Und da du hier keinen "Spezialfall" hast, wo du ganz spezielle Voraussetzungen an deine Liste stellst, würde ich einfach dabei bleiben. Einen spürbar schnelleren Algo wirst du kaum finden. (und das ist wieder voll On-Topic ;-))

_________________
We are, we were and will not be.
MSCH
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: Mi 05.10.05 19:33 
ich auch mal; bei einer solchen Menge, die wie ich vermute, als File of Record (?) irgentwo auf der Platte dümpelt, ist da nicht eine kleine Datenbank besser? Es reicht ja dazu Access und Ado. Und du hast deine Probleme mehr oder weniger gelöst, wenn du dort die richtigen Indizes setzt. Dann ist das Laden und Umsortieren nur noch abhängig von der Hardware.
grez
msch

_________________
ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 05.10.05 19:40 
Kurze Frage am Rande:
Wozu sortieren? Zum schnellen finden? Dann wäre eine Dictionary (Hashliste) ideal. Einfügen ist O(1), Suchen ist O(1), Löschen auch. Nur mit der sortierten Ausgabe haperts.

Eine weitere Möglichkeit könnten die Skiplist sein. Das hat ein nahezu lineares Laufzeitverhalten.

_________________
Na denn, dann. Bis dann, denn.
Heiko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 3169
Erhaltene Danke: 11



BeitragVerfasst: Mi 05.10.05 20:25 
Ne, die ich erListe sortiere ich nicht zum schnellen finden. Das sortieren soll für den mp3-Tag-Editor von [url=www.killprocess.de.vu]KillProcess[/url] sein. Dabei suche ich nach allen Musikdateien und will dem User die Daten dann nach z.B. dem Titel sortiert präsentieren.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mi 05.10.05 20:33 
Ach so. Na dann Quicksort. Oder einfach TStringlist.Sort einfach gehts doch nicht. Und ob der anwender 100 oder 120ms wartet ist doch peng.

Hier: Teste selbst
Einloggen, um Attachments anzusehen!
_________________
Na denn, dann. Bis dann, denn.