在Delphi中检查关键字列表中关键字的最快方法是什么?

时间:2010-01-23 23:54:16

标签: delphi optimization lookup case-statement

我有一小部分关键字。我真正想做的是类似于:

case MyKeyword of
  'CHIL': (code for CHIL);
  'HUSB': (code for HUSB);
  'WIFE': (code for WIFE);
  'SEX': (code for SEX);
  else (code for everything else);
end;

不幸的是CASE语句不能像字符串一样使用。

我可以使用直接的IF THEN ELSE IF结构,例如:

if MyKeyword = 'CHIL' then (code for CHIL)
else if MyKeyword = 'HUSB' then (code for HUSB)
else if MyKeyword = 'WIFE' then (code for WIFE)
else if MyKeyword = 'SEX' then (code for SEX)
else (code for everything else);

但我听说这个效率相对较低。

我一直在做的是:

P := pos(' ' + MyKeyword + ' ', ' CHIL HUSB WIFE SEX ');
case P of
  1: (code for CHIL);
  6: (code for HUSB);
  11: (code for WIFE);
  17: (code for SEX);
  else (code for everything else);
end;

这当然不是最好的编程风格,但它对我来说效果很好,到现在为止没有什么区别。

那么在Delphi中重写这个的最佳方法是什么呢?它既简单又易懂,而且速度快?

(作为参考,我使用带有Unicode字符串的Delphi 2009。)


跟进:

托比建议我只使用If Then Else构造。回顾我使用CASE语句的示例,我可以看到这是一个可行的答案。不幸的是,我无意中收录了CASE隐藏了我真实的问题。

我实际上并不关心它是哪个关键字。如果特定方法可以像POS方法那样识别它,那么这只是一个奖励。我需要知道关键字是否在关键字集中。

所以我真的想知道是否有更好的东西:

if pos(' ' + MyKeyword + ' ', ' CHIL HUSB WIFE SEX ') > 0 then

在这种情况下,If Then Else等效似乎不是更好:

if (MyKeyword = 'CHIL') or (MyKeyword = 'HUSB') or (MyKeyword = 'WIFE') 
      or (MyKeyword = 'SEX') then

在Barry对Kornel的问题的评论中,他提到了TDictionary Generic。我还没有接受新的Generic系列,看起来我应该深入研究它们。我在这里的问题是它们是否是为提高效率而建立的,以及如何使用TDictionary在外观和速度上与上述两行进行比较?


在后来的分析中,我发现字符串的连接如:(''+ MyKeyword +'')在时间上非常昂贵,应该尽可能避免。几乎任何其他解决方案都比这样做更好。

8 个答案:

答案 0 :(得分:7)

type TKeyword = ( ECHIL, EHUSB, EWIFE, ESEX )
const TKeyNames : array[TKeyword] of string = ( 'CHIL', 'HUSB', 'WIFE', 'SEX' );

Key : TKeyword

case Key of 
  ECHIL : (code for CHIL);
  EHUSB : (code for HUSB);
  EWIFE : (code for WIFE);
  ESEX  : (code for SEX);
  else (code for everything else);
end;

一般情况下,不要使用字符串作为“键”,使用枚举 - 它们更安全,并且您可以大大提高速度。

不幸的是,Delphi(据我所知)没有标准的哈希表实现,它易于使用,但是你可以自己卷起来。

BTW,code for SEX听起来比“啤酒代码”更有趣:P

答案 1 :(得分:5)

您可以使用const表(必须进行alpha排序)和快速二进制排序。它非常高效,不需要任何散列。

以下是使用的功能:

function IsKeyWord(const KeyWords: array of string; const aToken: String): Boolean;
// aToken must be already uppercase
var First, Last, I, Compare: Integer;
begin
  First := Low(Keywords);
  Last := High(Keywords);
  Result := False;
  while First <= Last do
  begin
    I := (First + Last) shr 1;
    Compare := CompareStr(Keywords[i],aToken);
    if Compare = 0 then
      begin
        Result := True;
        break;
      end
    else
    if Compare < 0  then
      First := I + 1 else
      Last := I - 1;
  end;
end;

这里有一些关键字的例子:

const
  PASCALKEYWORDS: array[0..100] of string =
  ('ABSOLUTE', 'ABSTRACT', 'AND', 'ARRAY', 'AS', 'ASM', 'ASSEMBLER',
   'AUTOMATED', 'BEGIN', 'CASE', 'CDECL', 'CLASS', 'CONST', 'CONSTRUCTOR',
   'DEFAULT', 'DESTRUCTOR', 'DISPID', 'DISPINTERFACE', 'DIV', 'DO',
   'DOWNTO', 'DYNAMIC', 'ELSE', 'END', 'EXCEPT', 'EXPORT', 'EXPORTS',
   'EXTERNAL', 'FAR', 'FILE', 'FINALIZATION', 'FINALLY', 'FOR', 'FORWARD',
   'FUNCTION', 'GOTO', 'IF', 'IMPLEMENTATION', 'IN', 'INDEX', 'INHERITED',
   'INITIALIZATION', 'INLINE', 'INTERFACE', 'IS', 'LABEL', 'LIBRARY',
   'MESSAGE', 'MOD', 'NAME', 'NEAR', 'NIL', 'NODEFAULT', 'NOT', 'OBJECT',
   'OF', 'OR', 'OUT', 'OVERRIDE', 'PACKED', 'PASCAL', 'PRIVATE', 'PROCEDURE',
   'PROGRAM', 'PROPERTY', 'PROTECTED', 'PUBLIC', 'PUBLISHED', 'RAISE',
   'READ', 'READONLY', 'RECORD', 'REGISTER', 'REINTRODUCE', 'REPEAT', 'RESIDENT',
   'RESOURCESTRING', 'SAFECALL', 'SET', 'SHL', 'SHR', 'STDCALL', 'STORED',
   'STRING', 'STRINGRESOURCE', 'THEN', 'THREADVAR', 'TO', 'TRY', 'TYPE',
   'UNIT', 'UNTIL', 'USES', 'VAR', 'VARIANT', 'VIRTUAL', 'WHILE', 'WITH', 'WRITE',
   'WRITEONLY', 'XOR');

  DFMKEYWORDS: array[0..4] of string = (
    'END', 'FALSE', 'ITEM', 'OBJECT', 'TRUE');

  CKEYWORDS: array[0..47] of string = (
  'ASM', 'AUTO', 'BREAK', 'CASE', 'CATCH', 'CHAR', 'CLASS', 'CONST', 'CONTINUE',
  'DEFAULT', 'DELETE', 'DO', 'DOUBLE', 'ELSE', 'ENUM', 'EXTERN', 'FLOAT', 'FOR',
  'FRIEND', 'GOTO', 'IF', 'INLINE', 'INT', 'LONG', 'NEW', 'OPERATOR', 'PRIVATE',
  'PROTECTED', 'PUBLIC', 'REGISTER', 'RETURN', 'SHORT', 'SIGNED', 'SIZEOF',
  'STATIC', 'STRUCT', 'SWITCH', 'TEMPLATE', 'THIS', 'THROW', 'TRY', 'TYPEDEF',
  'UNION', 'UNSIGNED', 'VIRTUAL', 'VOID', 'VOLATILE', 'WHILE');

  CSHARPKEYWORDS : array[0..86] of string = (
  'ABSTRACT', 'AS', 'BASE', 'BOOL', 'BREAK', 'BY3', 'BYTE', 'CASE', 'CATCH', 'CHAR',
  'CHECKED', 'CLASS', 'CONST', 'CONTINUE', 'DECIMAL', 'DEFAULT', 'DELEGATE', 'DESCENDING',
  'DO', 'DOUBLE', 'ELSE', 'ENUM', 'EVENT', 'EXPLICIT', 'EXTERN', 'FALSE', 'FINALLY',
  'FIXED', 'FLOAT', 'FOR', 'FOREACH', 'FROM', 'GOTO', 'GROUP', 'IF', 'IMPLICIT',
  'IN', 'INT', 'INTERFACE', 'INTERNAL', 'INTO', 'IS', 'LOCK', 'LONG', 'NAMESPACE',
  'NEW', 'NULL', 'OBJECT', 'OPERATOR', 'ORDERBY', 'OUT', 'OVERRIDE', 'PARAMS',
  'PRIVATE', 'PROTECTED', 'PUBLIC', 'READONLY', 'REF', 'RETURN', 'SBYTE',
  'SEALED', 'SELECT', 'SHORT', 'SIZEOF', 'STACKALLOC', 'STATIC', 'STRING',
  'STRUCT', 'SWITCH', 'THIS', 'THROW', 'TRUE', 'TRY', 'TYPEOF', 'UINT', 'ULONG',
  'UNCHECKED', 'UNSAFE', 'USHORT', 'USING', 'VAR', 'VIRTUAL', 'VOID', 'VOLATILE',
  'WHERE', 'WHILE', 'YIELD');

它非常易于使用:

  if IsKeyWord(PASCALKEYWORDS,UpperCase('BEGIN')) then
   ....

如果需要,您可以轻松修改IsKeyWord()函数以返回令牌的索引。

function KeyWordIndex(const KeyWords: array of string; const aToken: String): integer;
// aToken must be already uppercase
var First, Last, Compare: Integer;
begin
  First := Low(Keywords);
  Last := High(Keywords);
  while First <= Last do
  begin
    result := (First + Last) shr 1;
    Compare := CompareStr(Keywords[result],aToken);
    if Compare = 0 then
      exit else
    if Compare < 0  then
      First := result + 1 else
      Last := result - 1;
  end;
  result := -1; // not found
end;

答案 2 :(得分:3)

通过检查单个字符,可以缩短检查输入是否为任何给定关键字的系列if语句,以便尽快挽救。你的例子

if MyKeyword = 'CHIL' then (code for CHIL)
else if MyKeyword = 'HUSB' then (code for HUSB)
else if MyKeyword = 'WIFE' then (code for WIFE)
else if MyKeyword = 'SEX' then (code for SEX)
else (code for everything else);

可以替换为

KW := kwOther;
if MyKeyword <> '' then begin
  case MyKeyword[1] of
    'C': if MyKeyword = 'CHIL' then KW := kwCHIL;
    'H': if MyKeyword = 'HUSB' then KW := kwHUSB;
    'S': if MyKeyword = 'SEX' then KW := kwSEX;
    'W': if MyKeyword = 'WIFE' then KW := kwWIFE;
  end;
end;
case KW of
  kwCHIL: {code for CHIL};
  kwHUSB: {code for HUSB};
  kwSEX: {code for SEX};
  kwWIFE: {code for WIFE};
else
  {code for everything else};
end;

对于不区分大小写的关键字,case会检查大写和小写,并且比较将使用AnsiCompareText()。如果你有几个关键字具有相同的第一个字母,你可以嵌套这些case语句,但可读性可能会很快受到影响很小的速度。

将此值设置为最大值可以实现一个使用PChar来计算下一个状态的状态机,一旦找到第一个不匹配的字符,该状态就会转移到else个案例。它会比涉及哈希的任何解决方案都快。

答案 3 :(得分:3)

我主要使用StrUtils的IndexText函数来达到这个目的。它类似于你的pos方法,但返回值与字符串的单独长度无关。作为一个噱头,它也不区分大小写(如果你不想要这个,请使用IndexStr。)

function IndexText(const AText: string; const AValues: array of string): Integer; overload;

对这些函数的评论实际上提到了案例构造:

  

{AnsiMatchText&amp; AnsiIndexText   为交易提供类似功能的案例   用字符串}

答案 4 :(得分:2)

我认为

if x then

else

是迄今为止最好的解决方案。你自己的解决方案非常不优雅,而且效率的微小改进不值得。 if then else构造非常适合你想要的东西。

答案 5 :(得分:2)

对于最干净的代码,最好使用带枚举的case,或者if..else带字符串,就像其他人建议的那样。但是,如果你真的想去那里,有一些解决方案可以解决这个问题。

一种是使用字符串哈希映射,它类似于由字符串“索引”的列表。列表中的值将是您希望为每个字符串执行的代码的过程指针。所有程序都必须具有相同的确切参数 - 您必须自己编写哈希映射,或者找到一个可以使用的哈希映射,例如在JVCL。

// prepare the hash map
myHashMap[ 'CHIL' ] := @procToCallForChil;
myHashMap[ 'HUSB' ] := @procToCallForHusb;

// use the hash map
type
  TPrototypeProc = procedure( your-parameters-here );
var
  procToCall : TPrototypeProc;
begin
  @procToCall := myHashMap[ 'CHIL' ];
  procToCall( your-parameters-here );
end;

两个,这是我曾尝试过的一些奇怪之处:当且仅当你的标识符字符串符合4个字符时(如你的所有例子中),并且它们是ansi字符串(不是unicode字符串),你可以映射字符串直接整数。 32位整数的大小与4字节的字符串相同,因此您可以这样做:

function FourCharStringToInteger( const s : ansistring ) : integer;
begin
  assert(( length( s )  = 4 ), 'String to convert must be exactly 4 characters long!');
  move( s[1], result, 4 );
end;

任何短于4个字符的字符串标识符都必须用空格填充,字符串区分大小写('chil'和'CHIL'产生不同的值)。要将其与case语句一起使用,您必须预先计算值,这些值可能适用于您的目的,也可能不适合您的目的:

id_CHIL := FourCharStringToInteger( 'CHIL' );

最后你可以得到你的案例陈述:

case id of
  id_CHIL : ...
  id_HUSB : ...
end;

这是“特殊护理”代码,如果您有数百个或更多的标识符字符串可能只会产生影响 - 它实际上根本不应该完成:)它肯定很容易破解。我同意,虽然案件陈述比......其他......块的无休止的游行更整洁。

答案 6 :(得分:1)

免责声明:答案基于更新的问题陈述,即只是检查字符串是否匹配。

如果确实想要获得最后一点性能,那么有关数据集的一些额外信息可能有所帮助。

  • 我们谈论了多少关键字? (数量级)
  • 是否修复了一组关键字?
  • 输入集中有很多重复吗? (即相同的X关键词经常重复)
  • 预期命中/失败率是多少?您是否希望每千个输入单词匹配一个关键字,或者您是否希望找到几乎每个单词?

例如,

  • 对于少量关键字(假设大约20个,取决于实现),散列的开销将变得很重要。
  • 如果你得到了一个完美的哈希方案(参见here中的一个例子),你可以摆脱任何链接或类似的方案,削减一些重要的周期。然后,这将需要预先知道您的关键字和输入集,这不太可能。
  • 如果关键字中存在大量重复(以及要匹配的大型哈希集合),则最后X个字的小型本地缓存可能会有所帮助。
  • 如果你期望很多明显的错失(或者你的哈希算法非常低效; P),trie可能比哈希表更有效。

其中大多数对于常见的性能调优任务来说有点极端。我可能首先介绍标准的“哈希集”实现(delphi泛型,jcl等),看看哪一个在你的问题集上表现最好。

答案 7 :(得分:0)

您还可以切换到更加面向对象的方法,并使用类似

的方法
TCommand = class
public
  procedure Execute; virtual; abstract;
end;
TCommandClass = class of TCommand;

让工厂为您创建命令

TCommandFactory = class
public
  procedure RegisterKeyWord (const Keyword : String; CmdClass : TCommandClass);
  function  CreateCommand (const Keyword : String) : TCommand;
end;

调用代码看起来很简单:

CommandFactory.CreateCommand (Keyword).Execute;

通过这种方式,您可以在一个简单的工厂类中对所有关键字字符串进行本地化和封装,这使得以后对输入语言的更改非常容易。使用这种基于命令的方法具有其他优点,例如易于扩展。

我知道这可能被解释为不是你问题的答案,因为它不是你能做多快。但这是另一种值得思考的方法。