Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Variable mit mehreren Werten vergleichen ohne "OR" ? (https://www.delphipraxis.net/81480-variable-mit-mehreren-werten-vergleichen-ohne.html)

Karstadt 28. Nov 2006 07:21


Variable mit mehreren Werten vergleichen ohne "OR"
 
Guten morgen.

Delphi-Quellcode:
Procedure Gegenstand(Bezeichnung:String);
begin
...
  IF (Bezeichnung= 'rohr') or (Bezeichnung= 'schraube') or (Bezeichnung= 'schraubendreher') Then ....
...
end;
kann man das einfacher machen das ich einfach auf OR verzichente kann ? MFG

RavenIV 28. Nov 2006 07:34

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
ich sehe da keine andere "schöne" Lösung.

Dies hier geht nicht, weil String keine ordinaler Typ ist:
Delphi-Quellcode:
if x in ['aaa', 'bbb', 'ccc'] then
begin
  ...
end;
was Du noch machen kannst: mehrere if verschachteln
Delphi-Quellcode:
if x = 'aaa' then
begin
  if x = 'bbb' then
  begin
    if x = 'ccc' then
    begin
      ...
    end;
  end;
end;
was besseres fällt mir nicht dazu ein.

Karstadt 28. Nov 2006 07:36

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Dann ist ja die erste Lösung (meine) schöner ;)

Denn wenn es um 10 vergleichweten kommen soll, dann werden das 10x or´s :(

SirThornberry 28. Nov 2006 07:37

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Das verschachteln wäre nicht der Nachbei mit "or" sonder das was der compiler intern bei "and" macht.

volkerw 28. Nov 2006 08:30

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Hallo,
bei 10 Vergleichswerten ist folgende Lösung wahrscheinlich übersichtlicher und flexibler:
Delphi-Quellcode:
Procedure Gegenstand(Bezeichnung:String);
var
  sl : TStringList;
begin
  sl := TStringList.Create;
  sl.CaseSensitive := true;
  sl.DelimitedText := 'rohr, schraube, schraubendreher';
  IF sl.IndexOf(Bezeichnung) > -1 then
    ShowMessage(Bezeichnung + ' ist enthalten.');
  sl.Free;
end;
Gruß, Volker

negaH 28. Nov 2006 08:48

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
also bevor du mit der langsammen TStringList arbeitest dann besser so:

Delphi-Quellcode:
if Pos('-' + Bezeichnung + '-', '-rohr-schraube-schraubendreher-') > 0 then
Ist auch nicht sonderlich effizient, da Pos() benutzt wird, drüfte aber schneller sein als eine Schleife, TStringList oä.
Falls Pos() intern nach dem Boyer-Moore Algo. arbeitet ist die Stringsuche sogar sehr effizient.

Die schnellste Lösung dürfte ein Tree sein. In einem solchen Baum sind deine Suchwörter auf spezielle Weise gespeichert so das man darin sehr sehr schnell überprüfen kann ob ein gesuchtes Wort existiert. Je nach Baum-Typus und Codierung würde dieser sogar weniger Speicherplatz verwenden als die Strings wie in deinem Beispiel zu speichern. ZB. Schraube und Schraubedreher würde nur einmal Schraube und einmal "dreher" im Tree speichern. Eine sehr effiziente Tree Struktur im Source kannst du hier saugen: http://www.michael-puff.de/Developer...agen_Reddmann/ Datei DWAG.zip

Das ist ein DAWG und so könntest du ihn benutzen:
Delphi-Quellcode:
var
  List: TDawg = nil;

procedure InitList;
begin
  List := TDawg.Create;
  List.Insert('Schraube'#0'Schraubendreher'#0'Rohr');
  List.Pack;
end;

procedure Search;
begin
  if List.Exists(Suchstring) then
end;
Gruß Hagen

uwewo 28. Nov 2006 08:57

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Wie wäre es wenn Du die einzelnen Worte an ein Array übergibst, und dann per Schleife prüfst ob es existiert.

RavenIV 28. Nov 2006 08:59

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Zitat von uwewo
Wie wäre es wenn Du die einzelnen Worte an ein Array übergibst, und dann per Schleife prüfst ob es existiert.

das ist nicht besser als die Stringlist, aber dafür vermutlich noch langsamer.

MaBuSE 28. Nov 2006 09:02

Re: Variable mit mehreren Werten vergleichen ohne "OR&q
 
Hi,
das hier sieht warscheinlich ein bischen blöd aus, funktioniert aber ;-)

Delphi-Quellcode:
Procedure Gegenstand(Bezeichnung:String);
begin
...
  // statt '#' kann natürlich auch alles andere genommen werden ;-)
  if pos('#'+Bezeichnung+'#', '#rohr#schraube#schraubendreher#')>0 then ....
...
  // oder auch:                 1    6        15 
  case pos('#'+Bezeichnung+'#', '#rohr#schraube#schraubendreher#') of
     1: machwas; // rohr
     6: machwas; // schraube
    15: machwas; // schraubendreher
  else
    machwas; // nix
  end;
...
end;
Das folgende ist etwas eleganter ;-)

Delphi-Quellcode:
function getArrayIndex(s: string; a: array of string): integer;
var
  i: Integer;
begin
  Result := -1;
  for i := low(a) to high(a) do
  begin
    if s = a[i] then
    begin
      Result := i;
      exit;
    end;
  end;
end;

procedure TestIt(Bezeichnung:String);;
begin
...
  if getArrayIndex(Bezeichnung, ['rohr', 'schraube', 'schraubendreher']) > -1 then
...
  case getArrayIndex(Bezeichnung, ['rohr', 'schraube', 'schraubendreher']) of
     0: machwas; // 1. String (rohr)
     1: machwas; // 2. String (schraube)
     2: machwas; // 3. String (schraubendreher)
    -1: machwas; // nicht gefunden
  end;
...
end;
mfg
MaBuSE

MaBuSE 28. Nov 2006 09:04

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Zitat von negaH
also bevor du mit der langsammen TStringList arbeitest dann besser so:

Delphi-Quellcode:
if Pos('-' + Bezeichnung + '-', '-rohr-schraube-schraubendreher-') > 0 then
...
Gruß Hagen

Mist, Hagen war schneller ;-)

Tyrael Y. 28. Nov 2006 09:07

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Wie wäre es, wenn du die Strings in einem TStringHash verwaltest und dann nur noch innerhalb des Hashes suchst?

Guck dir mal folgendes an.

Hashes

negaH 28. Nov 2006 09:13

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
@Mabuse:

Delphi-Quellcode:
// statt '#' kann natürlich auch alles andere genommen werden ;-)
  if pos('#'+Bezeichnung+'#', '#rohr#schraube#schraubendreher#')>0 then ....
ja könnte man, zb.

Delphi-Quellcode:
  if pos('-' + Bezeichnung + '-', '-rohr-schraube-schraubendreher-')>0 then ....
ups das hatte ich doch schon 4 Threads vorher gepostet ;)

[edit]
Mist, hat Mabuse auch schon erkannt.
Liest überhaupt einer die blöde rote Box bevor man postet, ich nicht ;)
[/edit]


Die beste Lösung ist die 2. von Mabuse, aus Sicht des besten Codes, allerdings mit kleinen Änderungen:

Delphi-Quellcode:
function IndexOf(const Value: String; const List: array of String): Integer;
var
  I: Integer;
begin
  Result := -1;
  for I := Low(List) to High(List) do
    if Value = List[I] then
    begin
      Result := I;
      Break;
    end;
end;

procedure TestIt(Bezeichnung: String);
begin
  case IndexOf(Bezeichnung, ['rohr', 'schraube', 'schraubendreher']) of
     0: machwas; // 1. String (rohr)
     1: machwas; // 2. String (schraube)
     2: machwas; // 3. String (schraubendreher)
    -1: machnichts; // nicht gefunden
  end;
end;
1.) const Parameter machen die Sache effizienter vom erzeugten Code. Besonders beim const List: array of String ist das enorm wichtig. Denn nun wird unser Array[] im Source das wir übergeben -> ['rohr', 'schraube', 'schraubendreher'] durch den Compiler hardcoded als fertiges Array[] im Code abgelegt. Also auch wenn man vermuten würde das zur Laufzeit unser dynamisches Array[] erst erzeugt wird und die 3 Strings darin eingefügt werden, so passiert eben dies nicht. Aber nur wenn wir konstante Arrays[] benutzen. Denn wenn nicht so wird die Funktion IndexOf() uU. eine komplette Kopie des übergebenen Atrrays anlegen.

2.) du musst nicht zweimal in der Liste Suchen, ein case of reicht aus, also ohne if then vorher.

Gruß Hagen

Karstadt 28. Nov 2006 09:14

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Hallo. Sehr viele Lösungsansätze. Sehr gut!

Wie kann ich erfahren wie schnell jeder einzelne Lösung ist? Sind das "gewaltige" unterschiede? Oder minimale?

das z.B. verwende ich sehr oft in meine Anwendung:

Delphi-Quellcode:
Procedure Gegenstand(Bezeichnung:String);
var
  sl : TStringList;
begin
  sl := TStringList.Create;
  sl.CaseSensitive := true;
  sl.DelimitedText := 'rohr, schraube, schraubendreher';
  IF sl.IndexOf(Bezeichnung) > -1 then
    ShowMessage(Bezeichnung + ' ist enthalten.');
  sl.Free;
end;
Wenn ich ein Eintrag in COMBOX Selektiren möchte.. Somit würde ja diese Funktion hervoragend passen (bessere Lesbarkeit, da ich solchen code (Struktur) sehr oft angewendet habe. Aber ist das SEHR langsam? Wie kann ich das in Zukunft selber "spüren" ? bei 1000 Einträgen?

negaH 28. Nov 2006 09:34

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Wie kann ich erfahren wie schnell jeder einzelne Lösung ist? Sind das "gewaltige" unterschiede? Oder minimale?
Je nach Datenmenge sind es gewaltige Unterschiede, vom schlechtesten zum besten sortiert

1.) TStringList
2.) IndexOf() ohne binärer Suche
3.) Pos()
4.) BoyerMoorePos()
5.) IndexOf() aber mit binärer Suche, statt wie oben einfach linear
6.) Trees wie mein DAWG
7.) Hash-Vergleich

Noch ein par Bemerkungen:

7.) Hash-Vergleich meint KEINE Hash-StringListe oder so ein Quatsch. Man sucht eine geeignetes Prüfsummenverfahren das für alle zu suchenden String in der Liste eine eindeutige Zahl liefert, zb. eine 32Bit Zahl. Diese Zahlen erzeugt man einmalig und kann dann so sehr sehr effizient vergleichen

Delphi-Quellcode:
case Pruefsumme(Bezeichnung) of
$12345678: ;
$AB450CDE: ;
$DFFFFF12: ;
// usw. usf.
end;
Dh. in dieser Methode wird nur ein String real bearbeitet, nämlich der Suchstring Bezeichnung wir byteweise durch Prüfsumme umgewandelt. Danach gibnt es KEINE weiteren realen Stringvergleiche mehr.

Eine Hash-Liste empfehle ich niemals, da diese immer nur auf bestimmte Probleme bezogen ihren Aufwand lohnen.
Trees benötigen fast immer mehr Speicher sind aber abgesehen von obiger Prüpfsummen-Methode die schnellste Lösung.

Pos() ist schnell implementiert aber schlecht zu warten und nicht sonderlich schnell. Aber weitaus sachneller als

eine TStringList jedesmal zu erzeugen, dann mit dem langsammen CommaText oä. die Liste zuzuweisen, dann vielleicht noch sortieren und dann erst darin suchen, und dann die Liste wieder freigeben. Uff, schon beim Schreiben dieses Satzes wird mir schlecht.

Die Beste Lösung bei nur wenigen Strings in der Liste dürfte die 2. von Mabuse sein -> IndexOf() habe ich si mal genannt. Hier erzeugt der Compiler schonmal eine schöne hardcoded Datenstruktur aller Strings im Codesegement. Also keinerlei Allokationen oder Deallokationen dieser "Liste" mehr notwendig. Sortiert man diese Strings noch von Hand und benutzt in IndexOf() eine binäre Suche so ergibt sich ein ziemlich effizientes Verfahren das auch bei zb. 1024 Strings nur 11 Vergleiche benötigt.

Aber es würden eben noch 11 * 2 Stringzugriff für diese Vergleiche notwendig sein, bei der TStringList wären dies ca. 512 pro Suche. Bei den Hash-Prüfsummen würde man denoch nur 1 String umwandeln in einen 32Bit Wert und diesen dann vergleichen in der CASE OF Abfrage, würde ja bei den anderen Verfahren ebenfalls notwendig sein.

Das Problem bei der Hash-Prüfsummen-Methode ist es das man erstmal eine geeignete Funktion finden muß die bei allen Strings eine wirklich eindeutige Prüfsumme erzeugt. Wenn man davon ausgehen kann das der Wert in Bezeichnung nur aus Werten bestehen kann die in der zu suchenden Liste vorkommen dann ist das noch effizient möglich, sonst aber nicht.

Wie gesagt: am elegantesten ist die const Array[] Methode, rein schon vom sauberen Eindruck den der Source macht und von der sich ergebenden Performance ist sie ein supere Kompromiss. Um längen besser als diese elende TStringList Rechnereien. (hab den Eindruck das es ausreichend ist einem Delphicoder die TStringList zu erklären, mehr benötigt er nicht ;) )

Gruß Hagen

MaBuSE 28. Nov 2006 09:39

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Zitat von negaH
@Mabuse:
Mist, hat Mabuse auch schon erkannt.
Liest überhaupt einer die blöde rote Box bevor man postet, ich nicht ;)

Ich habe keine rote Box bekommen. ;-)
Aber ich ärgere mich dann immer über mich selbst, da es zeigt, wie langsam man doch antwortet.
(Inkl. Delphi Start, e-Mail lesen, ...)

Zitat:

Zitat von negaH
Die beste Lösung ist die 2. von Mabuse, aus Sicht des besten Codes, allerdings mit kleinen Änderungen:

Danke, ein Lob von Hagen ist gleich doppelt so viel wert :mrgreen:

Zitat:

Zitat von negaH
2.) du musst nicht zweimal in der Liste Suchen, ein case of reicht aus, also ohne if then vorher.

Es war als entweder - oder gedacht. (ein Beispiel halt)

Da Karstdt im 1. Post ein if in seinem Code hatte,
ich aber die case Variante zeigen wollte ;-)

negaH 28. Nov 2006 10:02

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Danke, ein Lob von Hagen ist gleich doppelt so viel wert Mr. Green
Hm naja, sind wir doch mal ehrlich. Dein Vorschlag sieht super sauber aus, ist somit enorm leicht verständlich, und wenn man die Arbeitsweise des Compilers kennt so weiß man auch das der erzeugte Laufzeitcode auch relativ effizient ist im Vergleich zu TStringList oder allen anderen dynamisch zu erzeugenden Listenobjekten. Klar, natürlich nur aus Sicht wie in diesem Beispiel, denn bei zb. 1024 Wörtern in unserer Liste wird das Array[] im Source schon wieder ziemlich unübersichtlich und die meistens nachfolgenden CASE OF Abfrage wird durch den Compiler dann so ineffizient implementiert (als lineare Vergleichsliste mit Sprungmarken). Alleine die CASE OF Abfrage würde also bei 1024 Suchbegriffen im Durchschnitt 512 solcher bedingten Sprünge ausführen und morderne CPUs hassen quasi solche Branches und verlieren dabei enorm an Durchsatz/Performance.

Also: bei kleinen Listen mit > 3 aber oft < 32 Einträgen ist die Array[] Methode die eleganteste und stellt den besten Kompromiss dar. Wenn man jetzt noch die IndexOf() Funktion um drei Features erweiteren würde

1.) Suche mit und ohne Berücksichtigung der Groß/Kleinschreibung
2.) Suche per Binärer Suche
3.) Partielle Suche, dh. der erste Eintrag der die längste Übereinstimmung mit dem Suchmuster hat wird gefunden, statt keinem Resultat.

dann wäre sie ein idealer Beitrag für die CodeLib.

Gruß hagen

MaBuSE 28. Nov 2006 10:28

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Zitat von negaH
...und die meistens nachfolgenden CASE OF Abfrage wird durch den Compiler dann so ineffizient implementiert (als lineare Vergleichsliste mit Sprungmarken). Alleine die CASE OF Abfrage würde also bei 1024 Suchbegriffen im Durchschnitt 512 solcher bedingten Sprünge ausführen und morderne CPUs hassen quasi solche Branches und verlieren dabei enorm an Durchsatz/Performance.

Was hällst Du denn von folgender Idee:

Wenn der Vergleich (case) im Programm häufig vorkommt (z.B. in einer Schleife), könnte man ja statt dem Case ein array of procedure verwenden, das zuvor initialisiert wurde.
Das müsste doch funktionieren, oder?

Delphi-Quellcode:
var
  i: Integer;
  machWas: array of procedure;
...
begin
...
  while (...) do
  begin
...
    i := IndexOf(s, [...]); // [...] -> viele Einträge
    if i>-1 then machWas[i]
            else machNix;
...

MaBuSE 28. Nov 2006 10:42

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Zitat von MaBuSE
Das müsste doch funktionieren, oder?

Ich habe es mal getestet, es funktioniert ;-)

Jetzt nur noch die Frage ist es schnell?
Ich glaube schneller als ein Case schon (ohne Init)

Delphi-Quellcode:
var
  i: Integer;
  machWas: array of procedure;
...
procedure rohr;
...
procedure schraube;
...
procedure schraubendreher;
...
begin
  // ein mal init
  SetLength(machWas, 3)
  machwas[0] := rohr;
  machwas[1] := schraube;
  machwas[2] := schraubendreher;
...
  while (...) do
  begin
...
    // vielfacher Aufruf (z.B. in Schleife)
    i := IndexOf(s, ['rohr', 'schraube', 'schraubendreher']);
    if i>-1 then machWas[i]
            else machNix;
...
(Mit procedure of object könnte man natürlich auch Methoden in das Array legen ;-))

Hawkeye219 28. Nov 2006 10:57

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Hallo MaBuSE,

hier eine Erweiterung deiner Idee:

Delphi-Quellcode:
// uses StrUtils (AnsiIndexText)

procedure MachNix;
begin
end;

procedure rohr;
begin
end;

procedure schraube;
begin
end;

procedure schraubendreher;
begin
end;

const
  JumpTable: array [-1..2] of procedure = (MachNix, rohr, schraube, schraubendreher);

var
  s : string;
  i : Integer;
begin
  s := 'ScHrAuBe';
  i := AnsiIndexText(s, ['rohr', 'schraube', 'schraubendreher']);
  JumpTable[i];
end;
Gruß Hawkeye

negaH 28. Nov 2006 11:22

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Delphi-Quellcode:
JumpTable[AnsiIndexText(s, ['rohr', 'schraube', 'schraubendreher'])];
und ja ist sehr schnell da nur ein Lookup in einer festen Tabelle entsteht und dann ein call ausgeführt wird. Schneller als jede CASE OF Abfrage.

AnsiIndexText() kannte ich noch garnicht, in D5 gibts die noch nicht. Hast du mal in die Implementierung geschaut wie Borland die Suche implementiert hat ?

Jetzt müsste man noch eine Funktion schreiben die den CALL der durch den Compiler erzeugt wird ersetzt durch einen CALL zu einer nested Procedure. Dadurch ist es möglich die Procedures lokal und nested zu codieren und innerhalb dieser Procedures kann man auf den übergeordneten Stack zugreifen. Also wie gewohnt bei nested Funktionen. Das macht dann den Source noch übersichtlicher.

Gruß Hagen

MaBuSE 28. Nov 2006 12:26

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Zitat:

Zitat von Hawkeye219
hier eine Erweiterung deiner Idee:

Stimmt, dynamisches Array ist an der Stelle Schwachsinn ;-)

AnsiIdentText kannte ich auch noch nicht.
Man lernt ja nie aus.

Die Funktion gibts schon in D7 und sieht wie folgt aus:
Delphi-Quellcode:
{ *********************************************************************** }
{ Delphi Runtime Library                                                 }
{ Copyright (c) 1995-2001 Borland Software Corporation                   }
{ *********************************************************************** }
unit StrUtils;
...
function AnsiIndexText(const AText: string;
  const AValues: array of string): Integer;
var
  I: Integer;
begin
  Result := -1;
  for I := Low(AValues) to High(AValues) do
    if AnsiSameText(AText, AValues[I]) then
    begin
      Result := I;
      Break;
    end;
end;
...
Das ist also fast das gleiche wie unser Beispiel. :mrgreen:

In der AnsiSameText wird schliesslich noch eine Weitere Funktion aufgerufen, die dann noch eine aufruft und diese ruft dann folgende Win API auf:
Delphi-Quellcode:
function CompareString; external kernel32 name 'CompareStringA';
Die im Grunde aber auch nur ein aText = aValues[I] macht ;-)

In eigener Sache (Zitat aus Borland VCL / RTL)
Anmerkung zum Zitat:
Die Verwendung von Zitaten ist durch das Urheberrecht geregelt und unter bestimmten Voraussetzungen gestattet, ohne dass eine Erlaubnis des Urhebers eingeholt oder diesem eine Vergütung gezahlt werden müsste (§ 51 UrhG in Deutschland)
Es handelt sich hier um ein Kleinzitat.
Kleinzitate dürfen weiterreichend verwendet werden. Der Zitierzweck muss erkennbar sein. Das Zitat muss also in irgendeiner Beziehung zu der eigenen Leistung stehen, beispielsweise als Erörterungsgrundlage. Der Umfang des Zitats muss dem Zweck angemessen sein.
(frei zitiert aus Wikipedia: Zitat)

Vjay 28. Nov 2006 14:54

Re: Variable mit mehreren Werten vergleichen ohne "OR&a
 
Naja letzten Endes muss man ja den String vergleichen (wenn man nicht hashed).
Aber er spart sich die ganzen Jumps, sehr elegant, find das auf irgend eine komplizierte Sichtweise schön :)


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:21 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz