![]() |
TreeView-Nodes anhand Pfad-String finden (zu langsam)
Ich baue ein TreeView aus einer Datenquelle auf.
Die daten kommen mit zwei Informationen an: Kategorie und Dateninhalt Die Kategorie ist wie ein Verzeichnis-Pfad aufgebaut: Kategorie\Subkategorie\Datenbeschreibung (bis Level 8 ) und wird damit passend in ein TreeView einsortiert. Leider kommen die Daten nicht nach Kategorie sortiert an, denn dann könnte ich den TreeView "on the fly" aufbauen. Ich muss also ständig suchen, welcher Node zum PfadString passt, dort einsortieren oder neu anlegen. Dazu schneide ich den Pfad aus (Bsp. Kategorie\Subkategorie\) und übergebe den an die u.a. Funktion "FindNodeByPath". Je nach Rückgabe lege ich nur den Datennode an oder eben die fehlende Kategorien-Nodes.
Delphi-Quellcode:
Das funktioniert auch, wird aber proportional zu Datenmenge immer langsamer. Vor allem weil bei der Methode auch die irrelevanten Daten-Nodes mit geprüft werden.
(***************************************************************************
Funktion, die aus einem Node (mit Parents) ein PathStr bildet Achtung! aNode-Pointer wird verändert ***************************************************************************) function GetNodePath(aNode:TTreeNode; WithDelimiter: Boolean): AnsiString; begin if WithDelimiter then Result := aNode.Text +'\' else Result := aNode.Text; while assigned(aNode.Parent) do begin aNode := aNode.Parent; Result := aNode.text + '\' + Result; end; end; (*************************************************************************** Finden einen Node durch Angabe des Pfadnamen Achtung! aPath-Wert wird verändert ***************************************************************************) function FindNodeByPath(const aTreeView: TTreeView; aPath: AnsiString): TTreeNode; var i : Integer; begin Result := NIL; if Assigned(aTreeView) and (Length(aPath)>0) then begin // Pfadstring UpperCase und Delimiter anfügen aPath := AnsiUpperCase(aPath); if aPath[length(aPath)] <> '\' then aPath := aPath +'\'; // Alle Items aus dem TreeView testen for i := 0 to aTreeView.Items.Count-1 do begin // Vergleich, wenn positiv, dann Abbruch der Schleife if AnsiUpperCase(GetNodePath(aTreeView.Items[i], True)) = aPath then begin Result := aTreeView.Items[i]; Break; end; end; end; end; Kann man das auch schneller lösen? Meine Ideen bisher: - Eine StringList parallel nur mit Ordnernamen und Node-Object, die ich dann via IndexOf durchsuche. - oder Node.Data kennzeichnen, damit ich wenigstens die Daten-Nodes beim Stringvergleich ausklammern kann Beide Ideen könnte ich selber umsetzen, aber nimmt mir die universelle Eigenschaft von "FindNodeByPath". Deshalb suche ich nach einer Idee, die innerhalb von "GetNodePath" oder "FindNodeByPath" verbessert, ohne jetzt spezielle Vorbereitung der TreeNodes zu fordern. PS: VirtualTreeView hätte ich mir gerne mal angeschaut, scheint aber nicht für D5 zur Verfügung zu stehen. PPS: String ist bei D5 noch ein AnsiString |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
anstatt zu jedem Node erstmal dessen Pfad zu bestimmen und dann zu vergleichen,
wäre es bestimmt schneller, wenn du den übergebenen Pfad zerlegst und dann diesen stückchenweise suchst. hätte den Vorteil, daß im schlimmsten Fall nicht der ganze Baum durchsucht werden muß. und du hättest gleich den ersten bereits existierenden Node-Teil (am unteren Beispiel die Suche nach b\x\u\q ... man braucht also ab b\x\ anfangen den neuen Node einzubauen ... der Erste Teil exisitert ja schon) z.B.
Code:
wenn man hier z.B. "b\x" möchte suchst du fast den gesamten Tree ab
a
d i m e j b f g k l n x c h
Code:
zusammen stückchenweise
a a a\d a\d\i a\d\i\m a\e a\e\j b b b\f b\f b\g b\g b\g\k b\g\l b\g\k\l b\x b\x PS:
Delphi-Quellcode:
irgendwie ist Result da immer das Selbe :angel2:
if WithDelimiter then Result := aNode.Text +'\'
else Result := aNode.Text +'\'; |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Also quasi erst Level 0 durchsuchen, dort dann bei passendem Namen weitermachen?
Klingt schonaml sehr gut, werde ich nachher versuchen umzusetzen... €: Dein Edit verwiirt mich jetzt kurz, das muss ich nochmal ganz langsam lesen. Scheine ich richtig verstanden zu haben... erstmal auf Level 0 bleiben. Zitat:
|
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Zitat:
den linken Suchvorgang machst du ja jetzt schon und beim rechten werden praktisch alle Subnodes ignoriert, die eh nicht passen |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
So, also ich bin auf eine Hürde gestossen:
Die Nodes kennen Ihre Sub-Nodes nicht. Keine Itemlisten wie z.B. beim MenuItem. Nur die globae Itemliste und eben Parent/HasChildren als einzige Zuordnung. Ich kann also den passenden LvL-0 Node ermitteln, aber danach die Schleife nicht auf die Sub-Nodes beschränken. Den Vergleich natürlich schon, indem ich auf Parent prüfe. Dadurch durchlaufe ich aber die ItemListe gleich mehrmals komplett (je nach LvL-Tiefe). Kann sein, das ich einen Denkfehler drin hab', dann bitte nochmal in die richtige Richtung schupsen ;) *** Dein Ansatz war aber natürlich trotzdem ein richtiger Weg, nur hab' ich jetzt das Pferd von hinten aufgezäumt. Ich ermittel vom Schleifendurchlauf den LvL des gesuchten Nodes, indem ich die \ im Pfad zähle. Erst wenn der LvL passt, dann vergleiche ich jeweils den kompletten Pfad miteinander. Könnte ich es wie oben beschrieben machen, würde sich bei jedem Fund die Datenmenge im Mittel halbieren, so ist es leider nicht ganz so effektiv. Das hat aber immerhin etwa 30% mehr Geschwindigkeit gebracht:
Delphi-Quellcode:
"GetNodePath" in die Schleife zu bauen (statt Funktionsaufruf) bringt übrigens keine 100ms. Auch nicht schneller, vorher neben LvL noch Node.Text zu vergleichen (das hatte ich auch schon mit drin).
function FindNodeByPath(const aTreeView: TTreeView; aPath: String): TTreeNode;
var i, lvl : Integer; NodeText : String; begin Result := NIL; if Assigned(aTreeView) and (Length(aPath)>0) then begin // Pfadstring UpperCase aPath := AnsiUpperCase(aPath); // Delimiter anfügen if aPath[Length(aPath)] <> '\' then aPath := aPath +'\'; // Zählen welcher Level der Node haben müsste lvl := -1; for i := 1 to Length(aPath) do if aPath[i]='\' then inc(lvl); // Alle Items aus dem TreeView testen for i := 0 to aTreeView.Items.Count-1 do begin // Vorbedingungen testen if (aTreeView.Items[i].Level = lvl) then begin // Jetzt evtl. passenden Node-Pfad mit Gesuchtem vergleichen if AnsiUpperCase(GetNodePath(aTreeView.Items[i], True)) = aPath then begin Result := aTreeView.Items[i]; Break; end; end; end; end; end; Im ganzen werden übrigens 24.000 Nodes angelegt. Die erste Methode benötigte dazu noch >30.000ms. Die etwas optimierte nur noch 20.000ms. Die ganz andere Methode mit der String/Object-Liste und IndexOf ist allerdings immer noch um Längen schneller (<3.000ms). Vielleicht hab' ich ja oben einen Denkfehler gemacht, indem ich himitsu's Vorschlag verworfen hab'. Aber sieht so aus, als ob die spezielle Datenstruktur des TreeView nicht mehr hergibt? €: Gerade kommt mir etwas in den Sinn. Kommt der Eintrag "Kat\SubKat" immer nach dem Eintrag "Kat" in der ItemListe? Also ganz egal wie oft zuvor Nodes zugefügt und gelöscht worden sind? Wenn ja, müsste ich mir himitsu's Vorschlag nochmal vornehmen ;) |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Zitat:
Delphi-Quellcode:
var Node: TTreeNode;
NodeList: TTreeNodes; i: Integer; begin NodeList := TreeView1.Items; // alle Items der Ebene 0 i := NodeList.Count; // Anzahl der SubItems Node := NodeList[0]; // erstes Item NodeList := Node.Item; // alle SubItem i := Node.Count // Anzahl der SubItems Node := Node.Item[0]; // erstes SubItem |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
|
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
@himitsu:
NodeList := TreeView1.Items; // alle Items der Ebene 0 gibt alle Items des TreeView, aber beim Rest hast Du natürlich recht. Es steht auch so in der OH von D5. Wieso ich das wiedermal komplett übersehen hab' soll die Tage mein Therapeut klären. @RWarnecke Werde ich mir auf jeden Fall anschauen, auch wenn mich jetzt der Ehrgeiz gepackt hat, meinem bisher fabrizierten Mist selber zu beheben. Wenn ich das beim ersten überfliegen richtig sehe (worauf man sich bei mir wohl nicht verlassen kann), vergleichst Du nur Node.Text und brichst beim Ersten auftreten ab. Bei mir kommt Node.Text aber vielfach vor, nur eben in verschiedene Kategorien verteilt. |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Zitat:
Zitat:
![]() |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
nicht getestet:
Delphi-Quellcode:
Function FindNodeByPath(aTreeView: TTreeView; Const aPath: String): TTreeNode;
Var Path: Array of String; i: Integer; Begin Result := nil; Path := Explode('\', ExcludeTrailingBackslash(aPath)); If Path = nil Then Exit; Result := aTreeView.Items.GetFirstNode; i := 0; While Assigned(Result) do Begin If Result.Text = Path[i] Then Begin If i < High(Path) Then Begin Inc(i); Result := Result.getFirstChild; End Else Exit; End Else Result := Result.getNextSibling; End; End; |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
^- Der Post kam während ich meinen geschrieben habe.
================================================== == Meine Daten sind aus einer alten Record-Datenbank. Die sind erst mal fix, aber die will ich ja später auf SQL exportieren, dann wird alles leichter. Ich werde deinen Source aber ganz sicher analysieren und wenn er passt auch verwenden. Aber ich muss jetzt, nachdem ich den Nachmittag dran gesessen bin, selber eine wenigstens brauchbare Version hinbekommen. Das kennst Du sicher ;) *** Also ich weis jetzt auch, warum ich die Liste der SubNode nicht gefunden hatte. Node.Item <> TTReeNodes, sondern nur eine Objekt-Liste (was auch reicht), aber weshalb mir die Codevervollständigung nichts angeboten hatte. Ohne himitsu's var-Deklaration wäre ich nie drauf gekommen. Dadurch musste ich die Schleife für TreeView.Items vom Konstrukt für Node.Item abspalten. Aber bin jetzt auf ~10.000ms für die 24.000 Node angekommen:
Delphi-Quellcode:
Das ganze muss ich jetzt noch feintunen...
function FindNodeByPath(const aTreeView: TTreeView; aPath: String): TTreeNode;
var i : Integer; found : Boolean; NodeText : String; begin Result := NIL; if Assigned(aTreeView) and (Length(aPath)>0) then begin // Pfadstring UpperCase und Delimiter anfügen aPath := AnsiUpperCase(aPath); if aPath[Length(aPath)] <> '\' then aPath := aPath +'\'; // NodeText des obersten Level ausschneiden und Pfad kürzen NodeText := Copy(aPath,1,Pos('\',aPath)-1); Delete(aPath,1,Pos('\',aPath)); // StartNode suchen for i := 0 to aTreeView.Items.Count-1 do if AnsiUpperCase(aTreeView.Items[i].Text) = NodeText then begin Result := aTreeView.Items[i]; Break; end; // Wenn StartNode gefunden und noch eine Ebene existiert while (aPath <> '') and (Result <> NIL) do begin // Name der nächste Ebene, Pfad kürzen NodeText := Copy(aPath,1,Pos('\',aPath)-1); Delete(aPath,1,Pos('\',aPath)); // Children durchsuchen found := false; for i := 0 to Result.Count do if AnsiUpperCase(Result[i].Text) = NodeText then begin Result := Result[i]; found := True; Break; end; // Wenn nicht gefunden, dann Result verwerfen if not found then Result := NIL; end; end; end; |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Delphi-Quellcode:
Explode/ExcludeTrailingBackslash kennt D5 nicht,
Path := Explode('\', ExcludeTrailingBackslash(aPath));
|
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
schau mal, was deine OH alles kennt, was mit "Exclude" anfängt und dahinter irgendwas mit "Dir" oder "Path" stehen hat.
[add] eventuell ExcludeTrailingPathDelimiter [/add] ![]() |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
ExcludeTrailingBackslash kennt D5, war nur Explode, das den Fehler verursacht hat.
Aber das Explode kann ich nachbauen. Das Trennzeichen ist noch dran oder in der Stringliste weggeschnitten? |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Funktioniert!
Aber nur etwa 200ms schneller (die 10.000ms sind damit aber knapp geknackt): Hier nochmal die D5 Variante:
Delphi-Quellcode:
Function FindNodeByPath(const aTreeView: TTreeView; aPath: String): TTreeNode;
Var Path: Array of String; i: Integer; Begin Result := nil; if aPath='' then exit; //Path := Explode('\', ExcludeTrailingBackslash(aPath)); if aPath[Length(aPath)] <> '\' then aPath := aPath +'\'; while aPath <> '' do begin i := Length(Path); SetLength(Path,i+1); Path[i] := Copy(aPath,1,Pos('\',aPath)-1); Delete(aPath,1,Pos('\',aPath)); end; If Path = nil Then Exit; Result := aTreeView.Items.GetFirstNode; i := 0; While Assigned(Result) do Begin If Result.Text = Path[i] Then Begin If i < High(Path) Then Begin Inc(i); Result := Result.getFirstChild; End Else Exit; End Else Result := Result.getNextSibling; End; End; |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Delphi-Quellcode:
erstellt durch
type
ArrOfStr = array of string; implementation function explode(sPart, sInput: string): ArrOfStr; begin while Pos(sPart, sInput) <> 0 do begin SetLength(Result, Length(Result) + 1); Result[Length(Result) - 1] := Copy(sInput, 0,Pos(sPart, sInput) - 1); Delete(sInput, 1,Pos(sPart, sInput)); end; SetLength(Result, Length(Result) + 1); Result[Length(Result) - 1] := sInput; end; ![]() Das ist die Funktion, die himitsu meinte. Gibt es bei den ![]() ![]() |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
nur 200? :shock:
selbst wenn .getFirstChild und .getNextSibling nicht optimal implementiert sind, hätt ich schon mehr erwartet Nja, die TTreeView ist auch nicht unbedingt der Schnellste. kennst du die VirtualTreeView? |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
@RWarnecke
Danke Dir. Den Code hab' ich in meine Tools-Unit übernommen. Auch wenn ich es ähnlich hinbekommen hatte, aber immer gut zu haben. Zitat:
TreeView ist wirklich lahm. Aber wirklich peinlich waren meine ersten Versuche mit TreeView (vor Jahren). Da musste ich Kaffee trinken gehen, wenn ich einen Baum aufgebaut habe. (BeginUpdate/EndUpdate kannte ich nicht) Immerhin ist die Wartezeit jetzt von fast 31 Sekunden auf unter 10 Sekunden gefallen. Aus der alten Datenbank müssen ein paar Informationen gezogen werden, leider vergesse ich das Startproblem immer und beende das Programm (um es kurz danach wieder zu starten). Mit ZEOS/Firebird mache ich sehr gute Fortschritte, je mehr man weis, desto einfacher wird es. Dann portiere ich die Datenbank nach SQL und kann RWarnecke Ideen/Code aus Code-Orakel zur Optimierung verwenden. PS: @himistu Der Fairness wegen muss noch erwähnt werden: Deine Version ist übrigens 1400ms schneller, nicht nur 200ms. Zuvor hatte ich bei meiner noch den "StringList-Cheat" aktiv. |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Hallo Satty67,
hattest Du es mal mit meiner Variante ausprobiert ? Wäre mal interessant, wie schnell meine Variante ist, vielleicht kann man da ja noch etwas optimieren. |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Hallo Satty67,
vielleicht bringt es noch was den Text-Vergleich mit:
Delphi-Quellcode:
durchzuführen.
CompareText
Bis bald Chemiker |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Du nutzt ja die Fähigkeiten von SQL, um Dir erst eine Liste zu erstellen, mit der Du dann optimiert arbeiten kannst (richtig?) Die Daten hier liegen aber auf einem alten Werkstatt-Rechner, der kein SQL hat. (Daten-Header werden per TFileStream seriell eingelesen, Tree angelegt und RecNo im Node für späteren Detailzugriff gespeichert).
Bin auch etwas überfordert, mir die ganze Funktion ohne SQL vorzustellen. Weis also im Moment nicht, wie ich die Hilfslisten ohne SQL erzeugen könnte ohne dadurch den Zeitvorteil wieder zu verlieren. Mein Versuch mit String/Object Liste für jeden Node war evtl. so ein Ansatz... da ich aber gleich eine universelle Funktion wollte, hab ich jede Vorbereitung der Daten verworfen. Das ganze ist auch nur als Zwischenlösung gedacht, um die Daten bei mir in Delphi zur Verfügung zu haben. Später wird das nach SQL konvertiert, aber das dauert noch etwas. €: Ist ja schon Freitag... werde mich am WE man mit 'ner Kanne Kaffee dransetzen um die Funktionsweise ganz genau zu verstehen und prüfen, wie das ohne SQL umzusetzen ist. @Chemiker werde ich heute Mittag schnell probieren und berichten. (das Projekt liegt auf dem Home-PC) |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Zitat:
Zitat:
|
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Zitat:
dann lönntest du diese Records auch direkt an den jeweiligen TreeNode anhängen. Zitat:
(bringt nur den Vorteil, daß es CaseInsensitiv vergleicht) |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Zitat:
Es geht also schon in erster Linie darum, eine Funktion zu haben, die nur TTreeView und einen PfadString bekommt und alleine damit arbeiten muss. Die Pfadstrings sollen dabei auch in unsortierter Reihenfolge ankommen dürfen. Wenn ich jetzt also bedenke, das ein lineares anlegen der 24.000 Nodes ~3500ms dauert, das wahllose anlegen mit String-Vergleich 9500ms (himitsus letzter Vorschag), dann könnte es gut sein, das wir nahe am Optimum sind. Immerhin sind wir von über 31.000ms auf 9.500ms runtergekommen. *** Was eine spätere SQL-version angeht... wenn ich die Datenbank nach Pfad sortieren lasse, dann muss ich überhaupt keinen Node suchen. Es kommt in der Pfadliste immer Parent vor Child bzw. neuer Parent-Zweig. Ich muss also nur prüfen, ob der letzte Pfad zum nächsten Pfad passt. |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
bau dir doch erstmal einen baum auf, aus diesem baum machst du dann den treeview. wäre jetzt eine überlegung von mir.
also baum sieht dann aus: Wurzelverzeichniss | | untervrz. untervrz. | | | | uuvz uuvz uuvz uuvz da kannst du sehr schnell suchen und einfügen, wenn du dann alle daten beieinander hast kannst du den treeview aufbauen. geht mittels dem baum auch recht flott dann... Andere Idee: In einer Liste Referenzen zu den Nodes speichern, dann anstatt in der TreeView zu suchen in der liste suchen und dann direkt einfügen. Sieht dann so aus: / -> TTreeNode; /asd -> TTreeNode; /bla -> TTreeNode; /asd/möp -> TTreeNode; wenn du nun /asd/möp/blubb einfügen möchtest, suchst du in der liste nach /asd/möp und speicherst blubb als kind knoten, dannach fügst du es der liste hinzu, damit /asd/möp/blubb -> TTreeNode; auch drinsteht. mfG P.S.: Bitte nicht hauen wenn Idee kagga :D mfG |
Re: TreeView-Nodes anhand Pfad-String finden (zu langsam)
Also hab' heute noch was gefunden, was den Aufbau doppelt so schnell ausführt. (da hatte ich gepennt)
Zwar sind viele Einträge durcheinander, aber doch nicht alle. Ich prüfe vorm Aufruf von "FindNodeByPath" jetzt, ob AktuellerPad=LetzterPfad und nehme dann den zuletzt gefundenen Node. (Quasi eine 1-Wert-Referenzliste) Könnte man auch innerhalb der Funktion implementieren, aber wegen der 3 nötigen globalen Variablen (LastTreeView, LastNode und LastPath) lasse ich es im Aufrufer. Sind jetzt 4,5 Sekunden für 24.000 Nodes, mir reicht das und ich kümmer mich jetzt lieber um den restlichen Code. Die erste Variante von mir läge dann bei ca 15 Sekunden, womit weiterhin >60% mehr Geschwindigkeit durch Eure Mithilfe rausgesprungen sind! Danke! |
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:01 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz