Autor Beitrag
Gausi Threadstarter
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 20.07.05 14:11 
Keine Angst... den Spass lass ich mir durch sowas nicht verderben. :wink:
Hab das mit der Editierdistanz mal probiert, und für meine Zwecke modifiziert:
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:
const IgnoreChars :Set of Char = ['.',' ','-','_','´','`','/','~'];

function min2(a,b:integer) : integer;
begin
  if a < b then
    result := a
  else
    result := b;
end;
function min3(a,b,c:integer) : integer;
begin
  if a < b then
    result := min2(a,c)
  else
    result := min2(b,c);
end;


function costChange(c1,c2: char): integer;
begin
  if (AnsiLowerCase(c1)=AnsiLowercase(c2))
    // Umlaute würden ja sonst doppelt bestraft: einmal für a-ä und dann für "e nach nix"
    or ((AnsiLowerCase(c1)='ä'AND (AnsiLowerCase(c2)='a') )
    or ((AnsiLowerCase(c1)='ö'AND (AnsiLowerCase(c2)='o') )
    or ((AnsiLowerCase(c1)='ü'AND (AnsiLowerCase(c2)='ü') )
    or ((AnsiLowerCase(c1)='ß'AND (AnsiLowerCase(c2)='s') )
    or ((AnsiLowerCase(c2)='ä'AND (AnsiLowerCase(c1)='a') )
    or ((AnsiLowerCase(c2)='ö'AND (AnsiLowerCase(c1)='o') )
    or ((AnsiLowerCase(c2)='ü'AND (AnsiLowerCase(c1)='ü') )
    or ((AnsiLowerCase(c2)='ß'AND (AnsiLowerCase(c1)='s') )
    then result := 0
  else result := 1;
end;

function costGap(c1: char):integer;
begin
  if c1 in IgnoreChars then
    result :=0
  else
    result :=1;
end;

function costGap2(c1: char):integer;
begin
  if c1 in IgnoreChars then
    result :=0
  else
    result :=1;
end;

function EditierDistanz(s1,s2: string; tolerance:integer) : integer;
{ Diese Funktion berechnet eine modifizierte Editierdistanz,
  bzw. bricht die Berechnung ab, wenn die Editierdistanz größer
  wird als ein vorgegebener Wert}

var A: array of array of integer;
  i, j, min: integer;
  ls1,ls2:integer;
begin
{ s1: Der String, der gesucht wird
  s2: Der String, in dem gesucht wird
  Array A, was zur Berechnung der Distanz verwendet wird:

      String_in_dem_gesucht_wird
    s 01...                    |
    t  0                       |
    r   0...                   |
    1 .........................X <-Ergebnis }


  ls1 := length(s1);
  ls2 := length(s2);
  setlength(A,ls1+1);
  for i:=0 to length(A)-1 do
  setlength(A[i],ls2+1);

{ Ziel: Der zu suchende String soll in dem anderen möglichst gut enthalten sein
  Daher: Nullte Zeile des Arrays mit 0 initialisieren}

  for j := 0 to ls2 do
    A[0,j] := 0;

{ Änderungen am Suchstring sollen aber bestraft werden
  Daher: Nullte Spalte mit Werten füllen, die u.a. vom char an der betreffenden
        Stelle abhängen }

  for i := 1 to ls1 do
    A[i,0] := A[i-1,0] + costGap(s1[i]);

{ Bedeutung des Parameters 'tolerance':
  Die Editierdistanz kann von oben nach unten nur immer weiter zunehmen
  Wenn man das Minimum der Werte einer Zeile schon größer als die Toleranz ist,
  dann kann abgebrochen werden.}

  min := tolerance + 1;
  for i := 1 to ls1-1 do // Zeilenweise arbeiten
  begin
    min := tolerance + 1;
    for j := 1 to ls2 do // jede Zelle der Zeile durchgehen, davon gibt es |s2| viele
    begin
      A[i,j] := min3(
        A[i-1,j] + costGap2(s1[i]),  // insertion von s1[i] in s2, bzw. löschen in s1
        A[i,j-1] + costGap(s2[j]) ,  // löschen in s2, bzw. insertion in s1, in der letzten Zeil soll das ok sein
        A[i-1,j-1] + costChange(s1[i],s2[j]) );
      if A[i,j] < min then
        min := A[i,j];
    end;
    if min > tolerance then break;
  end;

  if min > tolerance then
  begin
    result := tolerance+1;
    exit;
  end;
{ Die letzte Zeile verdient besondere Aufmerksamkeit:
  Ein löschen von Buchstaben aus dem s2-String soll hier nicht mehr bestraft werden
  -> finde "die toten" in "die toten hosen"}

  for j := 1 to ls2 do
  begin
      A[ls1,j] := min3(
        A[ls1-1,j] + costGap2(s1[ls1]),
        A[ls1,j-1]  , // !!!!
        A[ls1-1,j-1] + costChange(s1[ls1],s2[j]));
  end;
  result := A[ls1,ls2];
end;

Die funktioniert ganz gut. Als Toleranz nehme ich zur Zeit LengthOhneIgnoreChars(suchstring) Div 5, d.h. für je 5 Buchstaben lass ich einen Fehler (löschen/ersetzen/einfügen) zu.
Großes Manko: Die Funktion ist recht lahm - das Durchsuchen von schlappen 40.000 Titeln nach einem Artist mit 'Hosen' im Namen dauert gut 15 Sekunden - auf nem 1800MHz-Rechner. Da ist SoundEx deutlich schneller.

_________________
We are, we were and will not be.
StefanH
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1144

Win XP
D5 Standard, D7 Pers, D2005 Pers
BeitragVerfasst: Mi 20.07.05 14:29 
@Gausi: mich würde brennend interessieren, wo hier der unterschied ist:
ausblenden Delphi-Quelltext
 
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
{ ... }
function costGap(c1: char):integer;
begin
  if c1 in IgnoreChars then
    result :=0
  else
    result :=1;
end;

function costGap2(c1: char):integer;
begin
  if c1 in IgnoreChars then
    result :=0
  else
    result :=1;
end;

_________________
"Als es noch keine Computer gab, war das Programmieren noch relativ einfach."(Edsger W. Dijkstra)
"Ich bin nicht von Sinnen, sondern ich rede wahre und vernünftige Worte." (Paulus)
Gausi Threadstarter
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 20.07.05 14:39 
Da ist (noch) kein Unterschied. Idee dabei ist, dass ich evtl. ein Einfügen eines Chars anders bewerten kann, jen nachdem, in welchem String ich das für einen Match einfügen muss (also im zu suchenden Muster oder im Textstring, in dem ich suche)

Beispiel:
ich suche 'hosen', und vergleiche grade mit "hossen" (Muster erweitern für einen Match)
ich suche 'hossen', und vergleiche mit "hosen" (Text erweitern)

Ich bin noch nicht ganz sicher, ob da eine unsymmetrische Behandlung evtl. Vorteile bringt, daher hab ich das mal offengelassen ;-)

_________________
We are, we were and will not be.
matze
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 4613
Erhaltene Danke: 24

XP home, prof
Delphi 2009 Prof,
BeitragVerfasst: Do 21.07.05 09:51 
wow. cool, dass du es auch ohne soundex kannst. der code sieht echt ut un brauchbar aus. wenn er nur ein wenig schneller wäre...

_________________
In the beginning was the word.
And the word was content-type: text/plain.
Gausi Threadstarter
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: Do 21.07.05 10:59 
Nur der Klarheit halber: Mein Code oben ist keine alternative Implementierung von SoundEx.

Ich ermittle im Wesentlichen die Anzahl der Schritte, die benötigt werden, um den Suchstring auf ein Teilwort des anderen Strings zu matchen. Ein Schritt ist dabei entweder Löschen eines Buchstaben, Einfügen eines Buchstaben, oder das Ändern eines Buchstaben. Bestimmte Chars dürfen dabei beliebig gelöscht werden (z.B. ./' ;:-_), ohne als Schritt gezählt zu werden.
Da dieser Algorithmus prinzipiell mehr leistet, ist er auch langsamer als SoundEx.

Ich werde gleich nochmal eine Alternative mit SoundEx testen. Habe jetzt genauer verstanden, was SoundEx eigentlich berechnet und habe somit eine bessere Basis für vernünftiges experimentieren ;-)

_________________
We are, we were and will not be.
paddes19
Hält's aus hier
Beiträge: 4

Win 2000, XP
D5 Prof
BeitragVerfasst: Di 13.09.05 08:30 
Möchte das Thema nochmal aufwärmen.
Ich hab das Problem das meine strings nicht aus
Buchstaben bestehen sondern hauptsächlich aus
Zahlen, somit ist der soundex algorithmus völlig
nutzlos, oder?
Jetzt ist die Frage gibt es ausser dem Editierabstand
noch andere Möglichkeiten eine "unscharfe" Suche durch-
zuführen?
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Di 13.09.05 08:58 
Ja, es gibt diverse Versuche, zwei Strings auf Ähnlichkeit zu vergleichen.
Zwei Möglichkeiten wurden hier gezeigt: Soundex (von D.Knuth) und die Transformation in Phoneme. Letzteres führt bei geeigneten Phonemen zu erstaunlich genauen Ergebnissen (vom 'gesunden Menschenverstand' her).

Du benötigst vermutlich einen statischen Vergleich zweier Strings.

Googel mal nach 'fuzzy string match delphi' oder 'unsharp compare' oder 'fuzzy string pattern matching' oder so.

Das hier stach (Autsch!) ins Auge

delphi.helli.de/Tips...unsharp_compare.html

K.A. Ob das was für dich ist.
paddes19
Hält's aus hier
Beiträge: 4

Win 2000, XP
D5 Prof
BeitragVerfasst: Di 13.09.05 09:59 
Super, danke für die schnelle Antwort. :D
Ich denke das lässt sich verwenden.
Hab ein ähnliche Funktion geschrieben mit Hilfe
der Levenshtein-Distanz.
Werd jetzt mal die beiden vergleichen.

Ich würde gerne eine Tabelle (ca. 20000) einer Datenbank
durchsuchen. Die Einträge bestehen eben aus Zahlenreihen.
Hat jemand einen Lösungsansatz wie ich vorgehen soll?

Die Tabelle ist momentan nicht sortiert, d.h es wäre
wahrscheinlich segr zeitaufwendig jeden Eintrag zu
vergleichen.

Oder gibt es da ganz andere Möglichkeiten (fertige
Datenbankfunktion).Das Problem ist Soundex lässt
sich nicht verwenden, weil es sich auf Buchstaben
bezieht.
ManuelGS
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 173

Win XP HE, Suse Linux
D6, D7, D2005 Personal
BeitragVerfasst: Di 13.09.05 12:48 
@paddes19
Was genau willst du denn machen? Nur 20.000 Zahlen sortieren? Da würden nämlich Standardsuchalgorithmen wahrscheinlich ausreichen. 20.000 ist ja auch keine SO riesige Menge.

_________________
"Leben ist gänzlich Bühne und Spiel; so lerne denn spielen
und entsage dem Ernst - oder erdulde das Leid." - Palladas von Alexandria
paddes19
Hält's aus hier
Beiträge: 4

Win 2000, XP
D5 Prof
BeitragVerfasst: Di 13.09.05 12:59 
Ob sortiert oder unsortiert kommt darauf an was
für die Suche besser ist.
Das Hauptziel soll sein das die Suche unscharf ist,
d.h. wenn man Zahlendreher drin hat, sollen
trotzdem Ergebnisse geliefert werden.

Beispiel: Ich hab irgendeine EAN (4-003630-022530)
und möchte in einer Tabelle alle ähnlichen EAN's
finden.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Di 13.09.05 21:25 
Mein Vorschlag:
20.000 Datensätze einlesen, dann mit fuzzy search oder Levenshtein-Distanz o.ä. rumexperimentieren. Wir haben sowas für Adressen, das ist auch lustig, weil die Disponenten manchmal 'Dipl Ing. Hubert Kassupke', 'Hubbard Kasupque', 'Ing Kasubke' etc. schreiben. Wir verwenden phoneme. Anschließend wird daraus ein 'Key' erzeugt der definiert, das die Adresse eindeutig ist. Also: Zwei Adressen sind dann eindeutig, wenn Sie einen identische 'Key' besitzen... Man, haben wir rum probiert (war lecker) und danach rumprobiert.

Bei deinen EAN-Codes solltest Du Dir überlegen, welche Fehler auftreten können.
Do solltest eine Funktion schreiben, die zwei Codes anhand der von Dir analysierten Fehler vergleicht, also
-Zahlendreher
-ähnlich klingende Ziffern (bei akustischer Übermittlung bei der Erfassung)
-ähnlich aussehende Ziffern (bei visuellem Abtippen)
-nebeneinanderliegende Ziffern (bei Eingabe über Nummerkblock)
etc
unterschiedlich bewertet. Herauskommen sollte -wie bei der Levenshtein-Distanz- ein Wert, der die Ähnlichkeit bezeichnet.
Dann erstellst Du für jeden Eintrag I alle Distanzen der Eintrage I+1.... N und zeigst die an, die unter/oberhalb eines bestimmten Schwellenwertes liegen, an.

Du wirst nicht umhin kommen, alle Codes miteinander zu vergleichen, ausser, Du findest eine Ordnungsfunktion, die Dir EAN-Codes so sortiert, das ähnliche Codes nebeneinander liegen. Sowas trau ich mir nicht zu.

Las den PC doch die paar Minuten rechnen...
paddes19
Hält's aus hier
Beiträge: 4

Win 2000, XP
D5 Prof
BeitragVerfasst: Mi 14.09.05 07:30 
Vielen Dank für die Antwort, :D , da kann ich jetzt eine
Weile rumprobieren.Mal schauen ob das klappt.