![]() |
Levenshtein-Distanz
Hallo,
ich bin gerade dabei ein Programm meines Vaters zu überarbeiten. Er benutzte eine Funktion zur prozentualen Abweichung zweier Strings an Hand der Levenshtein Distanz, von der er leider nicht mehr weiß, woher er sie hat. Die Funktion sieht wie folgt aus:
Delphi-Quellcode:
Leider ist diese nicht besonders schnell, da sie in diesem Programm rund 12 Millionen mal hintereinander aufgerufen werden muss. Ich frage mich, ob es schnelle bzw. bessere Funktionen gibt, die diese Arbeit erledigen. Oder aber was man an der oberen noch verbessern könnte.
function Levenshtein(S,T:String):integer;
var D:array[0..255,0..255] of integer; M:Integer; // length of t N:Integer; // length of s I:Integer; // iterates through s J:Integer; // iterates through t SI:Char; // ith character of s TJ:Char; // jth character of t Cost:Integer; // cost begin N:=Length(S); M:=Length(T); if (N=0) then begin Result:=M; Exit; end; if (M=0) then begin Result:=N; Exit; end; for I:=0 to N do D[I,0]:=I; for J:=0 to M do D[0,J]:=J; for I:=1 to N do begin SI:=S[I]; for J:=1 to M do begin TJ:=T[J]; if (SI=TJ) then Cost:=0 else Cost:=1; if (D[I-1,J-1]+Cost >= D[I,J-1]+1) then begin if (D[I-1,J]+1 >= D[I,J-1]+1) then D[I,J]:= D[I,J-1]+1 else D[I,J]:= D[I-1,J]+1 end else begin if (D[I-1,J]+1 >= D[I-1,J-1]+Cost)then D[I,J]:= D[I-1,J-1]+Cost else D[I,J]:= D[I-1,J]+1 end; end; end; Result:=D[N,M]*100 div M; end; Zitat:
Naja, vielleicht habt ihr ja eine Idee... Vielen Dank im voraus Nicolai |
Re: Levenshtein-Distanz
Allgemein ist der Algorithmus wohl kaum zu optimieren.
Die Laufzeit ist ungefähr: (N + M) + (N*M) (mit N = Length(S) und M = Length(T)) N+M sind die beiden ersten for-Schleifen. N*M sind die verschachtelten for-Schleifen (die sind das eigentliche Problem). Dabei ist das was in den for-Schleifen steht jetzt nicht wahnsinnig zeitaufwändig, das Problem ist eher die verschachtelte Schleife an sich. Ob man da im Allgemeinen die Funktion vereinfachen kann bezweifle ich. Dafür müßte man genaueres über die Eingabedaten wissen. So ist eventuell einer der beiden Strings immer gleich und es läßt sich ein Teil der Rechnung nur einmal machen, anstatt bei jedem Funktionsaufruf, bzw. vielleicht sogar N*M zu N machen (deutlich schneller). Hängt aber wie gesagt von den Eingabedaten ab. Poste am besten mal den Quelltext der die Funktion aufruft und typische Eingabebeispiele. EDIT: Eine andere Möglichkeit ist natürlich den Algorithmus genauer zu studieren und zu hoffen einen besseren Ansatz zu finden (aber Webrecherchen legen nahe, daß das nicht trivial ist). Konkrete Anwendungsbeispiele sind eventuell einfacher zu optimieren. |
Re: Levenshtein-Distanz
![]() Vielleicht hilft ein heuristischer Ansatz. Ich habe ![]() ![]() Der Algo hat eine gewisse Ähnlichkeit zum Quicksort. Ganz simpel ausgedrückt: man sucht sich die längste Übereinstimmung in den beiden Strings und arbeitet dann rekursiv auf den beiden verbleibenden Hälften weiter. |
Re: Levenshtein-Distanz
Es gibt noch ein paar Optimierungen.
Delphi-Quellcode:
Besser
SI:=S[I];
for J:=1 to M do begin TJ:=T[J]; if (SI=TJ) then Cost:=0 else Cost:=1;
Delphi-Quellcode:
SI und TJ sind Unsinn, wenn sie nur einmal gebraucht werden.
for J := 0 to M do
begin Cost := Ord(S[I] <> T[J]); Den schoenen Trick mit Ord sollte man immer parat haben. |
Re: Levenshtein-Distanz
Zitat:
Zitat:
Es handelt sich dabei um ca. 6000 Zeilen. Augerufen wird die Funktion mit jedem der 6000 Titel, da er jeden Titel mit jedem wieder vergleicht. Nicolai |
Re: Levenshtein-Distanz
eventuell würde es schon etwas bringen die function als methode zu nutzen, und dann
Delphi-Quellcode:
im private zu definieren. Damit müsste nicht jedes mal speicher dafür angelegt werden
var D:array[0..255,0..255] of integer;
|
Re: Levenshtein-Distanz
Zitat:
|
Re: Levenshtein-Distanz
Zitat:
Zu den Eingabedaten: Hmm, das ist leider relativ allgemein, vielleicht kann man irgendwie ausnutzen, daß der erste String mit allen nachfolgenden Strings verglichen wird, also über "längere Zeit gleich bleibt". Irgendwie läßt sich vielleicht dadurch etwas initialisieren und die Schleife vereinfachen. Dafür muß ich den Algo aber erst mal genau untersuchen... |
Re: Levenshtein-Distanz
Zitat:
@Nicolai1605: Wenn ich heute etwas Zeit hätte, dann würde ich den oben geschilderten heuristischen Ansatz auf dein Problem umformulieren. Vom Verfahren her so:
Delphi-Quellcode:
Das Ganze kann man natürlich noch optimieren und auch einen Zweig der Rekursion durch eine Schleife auflösen. Der Ansatz könnte aber schon schneller sein - insbesondere wenn man "FindeLaengsteUebereinstimmung" durch ein Hash-Array (Zeichen aus s1 -> Mögliche Stellen in s2) optimiert.
function FindeLaengsteUebereinstimmung(const s1, s2: string;
var Pos1, Pos2, Len: integer): boolean; begin // Suche die längste Übereinstimmung zwischen den beiden Strings // und liefere "Pos1"=deren Position in "s1", "Pos2"=deren Position // in "s2" und "Len"=deren Länge. // Ergebnis: true falls es eine Übereinstimmung gibt, sonst "false" end; function Levenshtein(const s1, s2: string): integer; var Pos1, Pos2, Len: integer; begin if s1 = s2 then Result := 0 else if (s1 = '') or (s2 = '') then Result := 1 else if not FindeLaengsteUebereinstimmung(s1, s2, Pos1, Pos2, Len) then Result := 1 else Result := Levenshtein(Copy(s1, 1, Pos - 1), Copy(s1, 1, Pos2 - 1) + Levenshtein(Copy(s1, Pos1 + Len, Length(s1) - Pos1 - Len), Copy(s2, Pos2 + Len, Length(s2) - Pos2 - Len); end; |
Re: Levenshtein-Distanz
Also ich habe jetzt erstmal die oben genannten Änderungen eingebaut. Jetzt führt er pro Sekunde die Funktion 20.000 mal aus. Das ist schon recht ordentlich, wenn auch keine große Steigerung.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:22 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