Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Stackoverflow - Stringvergleich


xsus - Di 06.08.13 23:19
Titel: Stackoverflow - Stringvergleich
Hey,

ich bin an einem kleinen Testprogramm am schreiben (soll später in ein größeres Projekt integriert werden). Zum jetzigen Zeitpunkt soll das Programm bei Eingabe ins Edit1-Feld (+Buttonclick) eine Reihe von ListItems durchgehen und vergleichen. Anschließend soll die Prozedur mit dem jeweiligen Namen (also der eingegebene) ausgeführt werden.
Testweise sieht das wiefolgt aus: Die Begriffe : Photosynthese, DNA und Adhäsion sollen eingegeben werden können und die jeweilige Prozedur markiert einen RadioButton...
Leider kommt es regelmäßig zu Stackoverflows und ich habe keine Ahnung wieso...bei Photosynthese und DNA als Begriff scheint es zu klappen...liegt es evtl. an Ä;Ü;Ö? Wie kann ich auch diese Buchstaben vergleichen lassen?

Wieso ändert sich der Fenstertitel und wie kann ich das verhindern?

//Die Eingabe von den 3 Begriffen (Photosynthese,..) habe ich manuell über die Eigenschaften in die ListBox gesetz, .., tauchen also nicht im Quellcode auf

Quellcode:

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:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    RadioButton1: TRadioButton;
    RadioButton2: TRadioButton;
    RadioButton3: TRadioButton;
    Button1: TButton;
    ListBox1: TListBox;
    procedure Photosynthese;
    procedure DNA;
    procedure Adhaesion;
    procedure ItemsWeiter;
    procedure start;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  TEXT:String;
  Zahl:integer;

implementation

{$R *.dfm}

procedure TForm1.Photosynthese;
begin
RadioButton1.Checked:=true;
end;

procedure TForm1.DNA;
begin
RadioButton2.Checked:=true;
end;

procedure TForm1.Adhaesion;
begin
RadioButton3.Checked:=true;
end;

function vergleicheStringsN(s1, s2:string; n:Word): Boolean;
  function delLZ(s: string): string;
  var ss: string;
      i,j: Word;
  begin
   j:=0; ss:='';
   for i:=1 to Length(s) do
   begin
    if (s[i] <> ' 'and (s[i] <> '-'then
    begin
     Inc(j);
     ss:= ss + s[i]
    end;
    if j=n then Break
   end;
   Result := UpperCase(ss)
  end;
 begin
  if delLZ(s1)=delLZ(s2) then Result:=True else Result:=False ;
 end;

procedure TForm1.ItemsWeiter;
begin
    if zahl=ListBox1.Items.Count -1 then begin end else
    begin
    Text := ListBox1.Items.Strings[zahl];
    zahl:=zahl+1;
    end;
 end;

procedure TForm1.start;
begin
ItemsWeiter;
if vergleicheStringsN(Edit1.Text, Text, 8)=True then
   case zahl of
   1: Photosynthese;
   2: DNA;
   3: Adhaesion;
   end
else
start;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
start;
end;
end.


Z.Zt. steht mir leider nur Delphi 7 zur Verfügung..

Grüße und Dank von
xsus


Mathematiker - Di 06.08.13 23:33

Hallo,
user profile iconxsus hat folgendes geschrieben Zum zitierten Posting springen:
Wieso ändert sich der Fenstertitel und wie kann ich das verhindern?
...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.ItemsWeiter;
begin
    if zahl=ListBox1.Items.Count -1 then begin end else
    begin
    Text := ListBox1.Items.Strings[zahl];   // <-- hier ist das Problem
    zahl:=zahl+1;
    end;
 end;

"Text" als globale Variable ist sehr ungünstig, da in dieser Methode die Variable Text = Form1.Text ist, d.h. also der Fenstertitel. Globale Variablen sind an sich nicht so prickelnd.
Hier würde ich einfach Umbenennen, z.B. "Suchtext" oder so, empfehlen.
Eine Idee für die anderen Problemen braucht noch etwas Zeit.

Beste Grüße
Mathematiker

Nachtrag: In

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TForm1.start;
begin
ItemsWeiter;
if vergleicheStringsN(Edit1.Text, Text, 8)=True then
   case zahl of
   1: Photosynthese;
   2: DNA;
   3: Adhaesion;
   end
else
start;
end;

rufst Du die Methode start rekursiv auf. Wenn ich das richtig sehe, wird diese damit nur dann beendet, wenn der letzte String der Listbox Deinem "Text" (Ändern!) entspricht.
Das führt in der Regel zum Stacküberlauf.


jasocul - Mi 07.08.13 07:57


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
function vergleicheStringsN(s1, s2:string; n:Word): Boolean;
  function delLZ(s: string): string;
  var ss: string;
      i,j: Word;
  begin
   j:=0; ss:='';
   for i:=1 to Length(s) do
   begin
    if (s[i] <> ' 'and (s[i] <> '-'then
    begin
     Inc(j);
     ss:= ss + s[i]
    end;
    if j=n then Break
   end;
//   Result := UpperCase(ss) Lässt die Umlaute unverändert. Deswegen:
   Result := AnsiUpperCase(ss)
  end;
 begin
  if delLZ(s1)=delLZ(s2) then Result:=True else Result:=False ;
 end;


UpperCase berücksichtigt die (deutschen) Sondezeichen nicht. Daher musst du AnsiUpperCase verwenden.


xsus - Mi 07.08.13 13:01

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,
"Text" als globale Variable ist sehr ungünstig, da in dieser Methode die Variable Text = Form1.Text ist, d.h. also der Fenstertitel. Globale Variablen sind an sich nicht so prickelnd.
Hier würde ich einfach Umbenennen, z.B. "Suchtext" oder so, empfehlen.
Eine Idee für die anderen Problemen braucht noch etwas Zeit.


Ja, das hat Super geklappt! Danke :)

Zu dem Anderem - sowohl Uppercase als auch AnsiUpperCase machen keinen unterschied. :/

Wenn ich das Recht sehe, muss die ItemsWeiter und die Start-Procedure verändert werden. Ich habe jetzt div. Versucht, aber ohne Erfolg. Im Prinzip soll ja nur bei jeder Suchanfrage die ItemsListe durchgegangen werden und jedes Item jeweils verglichen werden. Anschließend die Nummer übergeben um die entsprechende Procedure auszuführen. Keiner eine Idee? :)

Beste Grüße!


Mathematiker - Mi 07.08.13 13:36

Hallo,
so ähnlich dürfte es funktionieren:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure TForm1.start;
var i:integer;
    s:string;
begin
    i:=0;
    repeat
      s:=listbox1.items[i];
      if vergleicheStringsN(Edit1.Text, s, 8)=True then
        case i+1 of
         1: Photosynthese;
         ...
        end
      inc(i);
    until i>listbox1.items.count-1;
end;

Du benötigst keine globale Variable und keinen rekursiven Aufruf mehr.

Beste Grüße
Mathematiker


Horst_H - Mi 07.08.13 13:38

Gelöscht,

ich habe in der Eile nicht erkannt, dass Mathematiker schon zuvor gepostet hat, welches auch eine bessere Version ist.

Gruß Horst
P.S.
Wie wird man den Dank wieder los?


FinnO - Mi 07.08.13 17:21

Bitte keine Vergleiche auf True durchführen.

Besser:


Delphi-Quelltext
1:
2:
if vergleicheStringsN(Edit1.Text, Text, 8then 
...


WasWeißDennIch - Do 08.08.13 12:18

Auch wenn ich mich unbeliebt machen sollte: ein paar Dinge stören mich an der Lösung. Zum Einen knallt es aufgrund der Fußschleife, sobald die ListBox keine Einträge enthält, da ungeprüft auf den String an Index 0 zugegriffen wird. Zum Anderen ist das Ganze durch die Vermischung von Logik und Darstellung unflexibel, man will doch eigentlich keine VCL-Controls vergleichen, sondern deren Inhalt, also Strings. Schöner fände ich persönlich es daher, wenn man eine allgemeinverwendbare Lösung anstreben würde. So etwas könnte z.B. so aussehen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
type
  TMatchFunc = function(String1, String2: string): Boolean;

function SpecialIndexOf(SearchString: string; List: TStrings;
  MatchFunc: TMatchFunc): integer;
var
  idx: integer;
begin
  Assert(Assigned(List), 'Vergleichsliste nicht übergeben');
  Assert(Assigned(MatchFunc), 'Vergleichsroutine nicht übergeben');
  Result := -1;
  for idx := 0 to List.Count - 1 do
    if MatchFunc(SearchString, List[idx]) then
      begin
        Result := idx;
        break;
      end;
end;

Dadurch, dass man die Vergleichsroutine selbst schreiben muss, ist man in eben dieser völlig frei und kann mit den übergebenen Strings anstellen, was man will/muss. 2 Beispiele dazu:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
function MatchFunc1(String1, String2: string): Boolean;
begin
  Result := AnsiLowerCase(String1) = AnsiLowerCase(String2);
end;

procedure TFormTest.Button1Click(Sender: TObject);
var
  List: TStringlist;
begin
  List := TStringList.Create;
  try
    List.Add('Dies');
    List.Add('ist');
    List.Add('ein');
    List.Add('Test');
    ShowMessage(IntToStr(SpecialIndexOf('EIN', List, MatchFunc1)));
  finally
    List.Free;
  end;
end;

Das war jetzt noch keine große Kunst, daher hier ein wenig komplexer:

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:
function MatchFunc2(String1, String2: string): Boolean;

  function FilterNumbers(const s: string): string;
  var
    Src, Dest, Current: PChar;
  begin
    SetLength(Result, Length(s));
    Src := PChar(s);
    Dest := PChar(Result);
    Current := Dest;
    while Src^ <> #0 do
      begin
        if Src^ in ['0'..'9'then
          begin
            Current^ := Src^;
            Current := CharNext(Current);
          end;
        Src := CharNext(Src);
      end;
    SetString(Result, Dest, Current - Dest);
  end;

begin
  Result := FilterNumbers(String1) = FilterNumbers(String2);
end;

procedure TFormTest.Button2Click(Sender: TObject);
var
  List: TStringlist;
begin
  List := TStringList.Create;
  try
    List.Add('Dies');
    List.Add('ist123');
    List.Add('ein');
    List.Add('Test');
    ShowMessage(IntToStr(SpecialIndexOf('Didel123dum', List, MatchFunc2)));
  finally
    List.Free;
  end;
end;

Man ist bei Verwendung von SpecialIndexOf an keine VCL-Controls mehr gebunden und kann die Vergleichsroutine (den Callback) völlig frei gestalten. Evtl. ist noch der eine oder andere Fehler enthalten, mir ging es aber vorrangig um das Prinzip.


Horst_H - Do 08.08.13 14:12

Hallo,

xsus ist ja noch Schüler, da kann man noch etwas bewirken, entschuldige...

wie bekommt man das hin

Delphi-Quelltext
1:
  Assert(Assigned(MatchFunc), 'Vergleichsroutine nicht übergeben');                    

ohne das der Compiler nicht hier schon vorher meckert?

Delphi-Quelltext
1:
showMessage(IntToStr(SpecialIndexOf('EIN', List, MatchFunc1)));                    

Oder stellst Du Dir die Verwendung eines array of TMatchfunc vor, indem dann NIL steht

Delphi-Quelltext
1:
showMessage(IntToStr(SpecialIndexOf('EIN', List, MatchFuncArr[1])));                    


Ein großer Vorteil bei der Verwendung einer seperaten Liste besteht in der Möglichkeit, die Daten schon dort in das gewünschte Format zu bringen, ohne sie ständig umwandeln zu müssen.

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:
  function delLZ(s: string,MaxLen : word): string;
  var 
      i,j: Word;
  begin
   j:=length(s);;
   IF maxLen > j then
     maxLen := j;
     
   j := 1;
   setlength(result,maxLen);
   for i:=1 to Length(s) do
   begin
    if (s[i] <> ' 'and (s[i] <> '-'then
    begin
     result[j] := s[i];
     Inc(j)
    end;
    if j>MaxLen then 
      Break
   end;
   setlength(result,j-1);
   Result := AnsiUpperCase(result)
  end;
...

procedure TFormTest.Button1Click(Sender: TObject);
var
  List: TStringlist;
  i : integer; 
begin
  Assert(Assigned(ListBox1), 'Keine ListBox1 vorhanden');
  i := ListBox1.Items.Count;
  IF i = 0 then
    Assert(false, 'Nichts vorhanden');    
  List := TStringList.Create;
  try
    List.capacity := i;
    For i := i-1 downto 0 do
      List[i] := delLZ(ListBox1.Items.Strings[i], 8);
    ShowMessage(IntToStr(SpecialIndexOf(delLZ('EIN'), List, MatchFunc1)));
  finally
    List.Free;
  end;
end;


Gruß Horst


WasWeißDennIch - Do 08.08.13 17:05

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
wie bekommt man das hin

Delphi-Quelltext
1:
  Assert(Assigned(MatchFunc), 'Vergleichsroutine nicht übergeben');                    

ohne das der Compiler nicht hier schon vorher meckert?

Delphi-Quelltext
1:
showMessage(IntToStr(SpecialIndexOf('EIN', List, MatchFunc1)));                    

Indem man statt MatchFunc1 einfach nil übergibt? Und damit man das eben nicht tut, wird die Assertion aktiv.