Autor Beitrag
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: Do 26.07.07 16:09 
Ich habe eine Liste von Strings gegeben. Die Strings in dieser Liste sind entweder sehr verschieden oder sehr ähnlich. Ich möchte nun aus dieser Liste den String bestimmen, der die Liste am besten beschreibt. Ein paar Beispiele:

Liste1: Otto; Karl; Gustav; Heinz; Ludwig; Thorsten; Otto; Fritz;
Ergebnis: "Verschiedene"

Liste2: Otto; Otto; Otto; Otto; Otto und Gustav; Otto und Heinz; Otto
Ergebnis: Otto

Liste3: Otto; Otto; Otto; Otto; Heinz; Otto; Otto; Otto;
Ergbnis: Otto

Liste4: Ottos Bilder (Kiste 1 von 2); Ottos Bilder (Kiste 2 von 2); Ottos Bilder (Kiste 1 von 2); Ottos Bilder (Kiste 1 von 2); (...)
Ergebnis: Ottos Bilder

Das ganze soll angewendet werden auf Listen mit in der Regel 10-50 Elementen, das aber sehr oft (ca. 1000-5000mal, ggf. auch öfter) und soll nicht länger als 2-3 Sekunden dauern. Also 2-3 Sekunden insgesamt. Nicht pro Liste ;-).

Ich steh nur irgendwie auf dem Schlauch, wie ich das anstellen soll. Ein Ansatz wäre der "längste gemeinsame Start-String", aber das würde bei Liste4 "Ottos Bilder (Kiste " zurückliefern (wobei ich damit leben könnte, das könnte man durch Heuristiken wegfrickeln - da steht nämlich fast immer "Kiste" oder "Box" [nicht wirklich, aber so was ähnliches :mrgreen:]). Nur wie kriege ich dann den Fall 3 da noch einigermaßen mit rein? Und zwar einigermaßen sauber ohne viel Fallunterscheidungen, in die man sich nur verennt?

Oder, so zum Anfangen: Wie bestimme ich "den längsten gemeinsamen Anfangsstring" effizient? (d.h. wie bekomme ich die Ergebnisse für Liste 1, 2 und 4?)

_________________
We are, we were and will not be.
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Do 26.07.07 16:19 
Moin!

Schonmal hier geschaut? :nixweiss:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
Agawain
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 460

win xp
D5, MySQL, devxpress
BeitragVerfasst: Do 26.07.07 16:29 
Hi

Spontan fällt mir dazu eine phonetische Prüfung ein, wie Soundex oder Metaphone

en.wikipedia.org/wiki/Metaphone

Gruß

Aga
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Do 26.07.07 16:32 
Mein Ansatz wäre, die Listeneinträge in einzelne Wörter zu zerlegen und für jedes Wort die Häufigkeit zu zählen - ggf. vorher noch Füllwörter wie und, der, die, das usw. rausfiltern.
Dann kannst du Prüfen, ob die relative Häufigkeit des häufigsten Wortes über einem Schwellwert liegt, den du durch Experimentieren herausfindest. Bei deinen Beispielen ist es sehr eindeutig:

1. Otto - 28,57 %
2. Otto - 77,78 %
2. Otto - 87,5 %

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Do 26.07.07 16:35 
Ups, gar nicht gesehen, dass du auch nach Begriffen aus mehreren Wörtern suchst. Dann vergiss das wohl eher...

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
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 26.07.07 16:44 
Zum Wiki-Artikel: Das ist zuviel des Guten. Außerdem braucht man da schon beim Ermitteln der Gemeinsamkeit von zwei Strings N^2 Schritte, und das ist zuviel. Das muss linear bleiben. Ich suche ja auch nicht die gemeinsame Teilsequenz, sondern nur den längsten gemeinsamen Teilstring, der noch dazu am Anfang stehen soll (also eine weitere Vereinfachung). Allerdings möchte ich in der Liste ein oder zwei Ausreißer erlauben. (Was eine Verkomplizierung bedeutet, wo man wahrscheinlich nur mit Heuristiken rangehen kann, z.B. die Liste sortieren und versuchen, aufzuteilen oder so.)

SoundEx bringt mir auch nichts. Denn das liefert mir dann zwischen zwei Strings "Ähnlich" oder "nicht ähnlich", hilft aber hier nicht weiter.

@Stefan: So in die Richtung habe ich auch schon gedacht, aber dann kam ich auch mit den mehreren Worten durcheinander bzw. komme mit diversen Fallunterscheidungen durcheinander...

Als Mensch sieht man das auf einen Blick, das muss doch auch am Rechner irgendwie gehen... :?

Edit: Wenn ich den Links bei Wiki folge: Um den Suffix-Tree möchte ich eigentlich drumrum kommen ;-)

_________________
We are, we were and will not be.
Regan
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 2157
Erhaltene Danke: 72


Java (Eclipse), Python (Sublimetext 3)
BeitragVerfasst: Do 26.07.07 16:52 
Wenn die Strings unterteilt werden, also z.b. durch Semikolon, dann könntest du doch einfach die Variante von user profile iconStefan.Buchholtz nehmen. Nur würde ich dann erst die Teilstrings nochmals unterteilen und dann den dominierenden String suchen.
Beispiel: Ottos Bilder (Kiste 1 von 2); Ottos Bilder (Kiste 2 von 2); Ottos Bilder (Kiste 1 von 2); Ottos Bilder (Kiste 1 von 2);
1.: 1x Ottos, 1x Bilder, 1x (Kiste 1x 1 1x von 1x 2)
...
Wobei da wieder das problem besteht mit den anderen Teilstrings, die ja auch gleich sein können. :?

Für die ersten beiden würd ich es so wie user profile iconStefan.Buchholtz machen. Das ist die Beste Variante, wenn es darum geht einwörtige ( :?: :!: ) Strings zu seperatieren.
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Do 26.07.07 17:54 
Eine uralte Möglichkeit ist es, 4-Gramme zu bilden und deren Häufigkeit zu zählen. 4-Gramme (oder Tupel) sind einfach 4 aufeinanderfolgende Zeichen. Nachdem Du die Füllwörter (von, vom, es, der ,die, das, ab, auf etc.) entfernt hast (*), erstellst du einfach eine Liste der 4-Gramme und deren Häufigkeit. Die häufigsten (Wieviele? rumprobieren) merkst du Dir zusammen mit der relativen Häufigkeit und speicherst sie zusammen mit dem Text. Auf diese Weise kann man eine simple und sehr schnelle Volltextindexierung erstellen.

Zwei Texte sind dann vom Inhalt her ähnlich, wenn deren 4-Gramm Liste ähnlich ist. Und zwei kurze Listen vergleichst Du z.B. mit einem modifizierten Levenshtein oder einem heuristischen Ansatz. Vielleicht hilft auch ein DAWG.

Phonetische Vergleiche werden nicht funktionieren, denn

"Paul,Otto,Otto,Otto" hört sich nun mal komplett anders an als "Otto,Paul,Otto,Karl,Otto'...

(*) Nachtrag: Und natürlich die Sonderzeichen rausschmeissen, Umlaute vereinfachen etc.

Ach, ich sehe gerade, das Du den 'dominierenden String' im Klartext haben willst... Dafür eignet sich das nicht.

_________________
Na denn, dann. Bis dann, denn.
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 26.07.07 18:27 
Ich glaube, ich frickel mir das mal so zusammen:

Zuerst suche ich mir in der Liste den kürzesten String raus. (geht fix)
Den entferne ich aus der Liste und teste, ob der bei den anderen passt (d.h. die Zeichen stimmen bis auf höchstens eine Abweichung überein). Dabei lasse ich dann evtl. auch noch einen Fehler zu, d.h. einmal (oder auch zweimal) darf es nicht passen. (geht auch fix)
Wenn das nicht klappt (weil der kürzeste der Ausreißer war, den ich erlauben will), probiere ich dasselbe nochmal (also mit dem zweitkürzesten). Wenn das auch nicht klappt, dann sind mindestens zwei Strings dabei, die nicht in die Reihe passen, und dann ist das Ergebnis "Verschieden". (geht wieder fix)

Da das Problem eigentlich eh nur Fehlerbereinigung ist, könnte das ausreichen. Wenn der User dann nicht damit zufrieden ist, soll der gefälligst seine ID3-Tags aufräumen. :tongue: - Nur zur Zeit steht da noch etwas zuoft "Various Artist - Unknown Compilation".

Probier ich mal aus, ob das ausreichend vernünftige Ergebnisse liefert...

Vielleicht was konkreter: Ich habe eine Liste mit Liedern, und ich möchte herausfinden, was das für ein Album ist, und von wem. Und mich ärgern Dinge wie "feat. irgendwer sonst" und "Sampler CD1"/"Sampler CD2", "Sampler Disk 1 of 2"/"Sampler Disk 2 of 2", "Bonus Disk" etc.

_________________
We are, we were and will not be.
Jakob_Ullmann
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1747
Erhaltene Danke: 15

Win 7, *Ubuntu GNU/Linux*
*Anjuta* (C, C++, Python), Geany (Vala), Lazarus (Pascal), Eclipse (Java)
BeitragVerfasst: Do 26.07.07 19:22 
Ich hab gewusst, dass das für Nemp bestimmt war. :roll:
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 26.07.07 20:27 
Ja nu, das war doch fast klar, oder? :lol:

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



BeitragVerfasst: Do 26.07.07 20:54 
Kleine Zwischenfrage, damit ich mich ins Proböem einfinde:

Was soll hier rauskommen?: Otto, Heinz und Otto, Heinz

Soll da ur Heinz rauskommen oder beide?

@nemp: Warum suchst du nicht direkt nach feat etc.? Sind doch meistens die gleichen Wörter...
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 26.07.07 21:13 
Bei dem Beispiel wäre es mir fast egal, was bei rauskommt. So genau nehme ich es da nicht.

Nach "feat." direkt suchen: OK, aber dann müsste ich auch nach "ft.", "featuring", "vs." etc suchen und das sind dann ein bissel viel suchen, die man da durchführen müsste. Das würde ich gerne in einem Rutsch erledigen.

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



BeitragVerfasst: Do 26.07.07 21:23 
Die Suche nach solchen Substrings müsste aber effizienter sein, denn ansonsten musst du einen ganzen Baum oder so aufspannen, was aufwändiger sein dürfte.

Allerdings gebe ich dir Recht, dass deine Variante besser sein dürfte, denn ich habe auch paat Titel, die mit & getrennt sind...
MiChri
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 28

Win XP, Win 2000, Win 98, Linux
D4 Prof
BeitragVerfasst: Fr 27.07.07 10:43 
Hallo
Mach es doch so:

1. Aufteilen in Substrings (";")

2. Iteriere über Buchstaben (0<i<StrLen) von Anfang an und...
Iteriere über alle Substrings
bis 40% rausgeflogen sind (unterscheiden sich vom Durchschnitt)

Beispiel
ausblenden volle Höhe 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:
 Eingabe: Otto Karl;Otto Kalle;Ottomotor;Otmar;
 
 Iterationen:
 i=1
 (O)tto Karl
 (O)tto Kalle
 (O)ttomotor
 (O)tmar
 100% O

 i=2
 O(t)to Karl
 O(t)to Kalle
 O(t)tomotor
 O(t)mar
 100% t

 Ot(t)o Karl
 Ot(t)o Kalle
 Ot(t)omotor
 Ot(m)ar  
 75% t

 Ott(o) Karl
 Ott(o) Kalle
 Ott(o)motor
 //Otmar wird nicht mehr beachtet
 75% o

 Otto( )Karl
 Otto( )Kalle
 Otto(m)otor
 //Otmar
 50% " " => terminiert


Laufzeit ist linear in der Eingabelänge

mfg
Christian
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: Fr 27.07.07 14:38 
@MiChri: Dann bleibt immer noch das Problem, den dominierenden Char zu finden ;-) Das könnte man bei AnsiStrings zwar über ein Array[0..255] of Integer machen, aber besonders effizient wäre das wahrscheinlich nicht. Außerdem brauche ich das ganze auch für Widestrings.

Ich hab das jetzt wie folgt gelöst, und es scheint einigermaßen zu funktionieren.

Zuerst eine Funktion, die bestimmt, ob zwei Strings ähnlich sind. Ist ein String länger, als der andere, ist das egal. Als Distanz nehme ich die Hamming-Distanz. Zusätzlich wird zurückgeliefert, wo der erste Fehler auftritt.
ausblenden 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:
function SameString(string1, string2: WideString; tolerance: Integer; var Fehlstelle: Integer): Boolean;
var tmp: WideString;
    i, c: Integer;
begin
  // sicherstellen, dass string2 mindestens so lang ist wie 1
  if length(string1) > length(string2) then
  begin
    tmp := string1;
    string1 := string2;
    string2 := tmp;
  end;
  c := 0;
  for i := 1 to length(string1) do
  begin
    if WideUpperCase(string1[i]) <> WideUpperCase(string2[i]) then
    begin
      inc(c);
      // erste Fehlerstelle zurückmelden!
      if Fehlstelle = 0 then Fehlstelle := i;
    end;
    if c > tolerance then break; // Abbruch. Zu viele Fehler
  end;
  result := c <= tolerance;
end;


Diese Funktion verwende ich nun in GetCommonString. Als Vergleichstring nehme ich den kürzesten. Dabei lasse ich einen Fehltritt zu. Wenn das fehlschlägt, probiere ich es nochmal mit dem zweitkürzesten String (die Anzahl der Versuche hängt von der Zahl der Strings ab). Sind auch im letzten Durchlauf zuviele "falsche Strings" gefunden worden, dann gebe ich als Ergebnis den String bis dahin zurück, wo der Fehler auftritt, wenn das weit hinten passiert (Das brauche ich z.B. bei Maxis, wo die Album-Information "EinTollesLied (Radio Edit)", "EinTollesLied (Extended)" etc. ist). Ansonsten gibts keinen gemeinsamen String.
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:
function GetCommonString(Strings: TTntStringlist; tolerance: Integer; var Fehlstelle: Integer): WideString;
var minlength, minidx, i: Integer;
    checkStr: WideString;
    durchlauf, durchlaufmax, errorCount: Integer;
begin
  durchlauf := 0;
  errorCount := 0;
  Fehlstelle := 0;
  CheckStr := '';
  case Strings.Count of
      0..1: durchlaufmax := 0;
      2..4: durchlaufmax := 1;
      5..9: durchlaufmax := 2
  else
      durchlaufmax := 3;
  end;

  if Strings.Count > 0 then
  repeat
        // Den String mit minimaler Länge bestimmen
        minlength := length(Strings[0]);
        minidx := 0;
        for i := 1 to Strings.Count - 1 do
        begin
          if length(Strings[i]) < minlength then
          begin
            minlength := length(Strings[i]);
            minidx := i;
          end;
        end;
        checkStr := Strings[minidx];
        // diesen String aus der Liste entfernen, damit er beim nächsten Durchlauf nicht wieder gefunden wird
        Strings.Delete(minidx);

        // Den Rest der Stringliste überprüfen, ob dieser String "passt"
        // ErrorCount zählt dabei die Strings, die nicht passen
        errorCount := 0;
        Fehlstelle := 0;
        for i := 0 to Strings.Count - 1 do
        begin
          if not SameString(checkStr, Strings[i], tolerance, Fehlstelle) then
            inc(errorCount);
          if errorCount > 1 then break;
        end;

        // Situation hier:
        // Wenn errorCount <= 1, dann ist CheckStr ein guter String für die Liste
        // Wenn nicht, dann probieren wir das nochmal.
        inc(durchlauf);
  until (durchlauf > durchlaufmax) or (errorCount <= 1or (strings.Count = 0);

  if ErrorCount <= 1 then
    result := CheckStr
  else
  begin
    if fehlstelle <= length(CheckStr) Div 2 + 1 then
      result := ''
    else
    begin
      result := copy(CheckStr, 1, fehlstelle - 1) + ' ... ';
      fehlstelle := 0;
    end;
  end;
end;


Das ganze läuft sehr fix durch und liefert recht brauchbare Ergebnisse. Das Hinzufügen von "..." bewirkt bei den Mehr-CD-Alben dann etwas wie "Ein Album CD..." anstelle von "Ein Album CD1 von 2".

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

Win XP, Win 2000, Win 98, Linux
D4 Prof
BeitragVerfasst: Fr 27.07.07 14:49 
Hi
Hast natürlich recht, das mit dem dominierenden Zeichen pro Schleifendurchlauf bin ich dir schuldig geblieben.
Mit Ascii/Ansi ist aber wie du schon sagtest das mit nem Array erledigt (O(1)), das geht zwar bei WideString auch, wäre aber dämlich, also nimmt man da ne Hashtable mit average
O(log(n)) oder gleich nen Baum (O(log(n)))
Also bleibt man auf jedenfall unter deinen O(n^2) ;-)

Sorry, war nur ne Anmerkung, und in deinem Anwendungsfall wird ja n nicht groß, sondern
vor allem die Anzahl der Ausführungen des ganzen Algos. Da kann deine
Lösung u.U. sogar schneller sein (bei kleiner Anzahl von Substrings).

mfg
Christian
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Fr 27.07.07 15:24 
user profile iconMiChri hat folgendes geschrieben:
H...also nimmt man da ne Hashtable mit average
O(log(n)) oder gleich nen Baum (O(log(n)))
Also bleibt man auf jedenfall unter deinen O(n^2) ;-).

Nope... Hashtable = O(1), Skiplist auch., Baum ist dann wieder O(log n).

_________________
Na denn, dann. Bis dann, denn.
MiChri
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 28

Win XP, Win 2000, Win 98, Linux
D4 Prof
BeitragVerfasst: Sa 28.07.07 19:37 
Moin
Das Einfügen in eine Hashtable ist O(1) das Suchen nicht (worstcase ist MöglicheWerte/FächerInDerTabelle).
In diesem Speziellen Fall wird man eine average Zeit von O(1) aber hinbekommen.
mfg
Christian