C# 确定字符串是否以一组令牌中的令牌开头的更有效方法?

C# 确定字符串是否以一组令牌中的令牌开头的更有效方法?,c#,optimization,C#,Optimization,我目前正在编写的一些代码中执行类似的操作: public CommandType GetCommandTypeFromCommandString(String command) { if(command.StartsWith(CommandConstants.Acknowledge)) return CommandType.Acknowledge; else if (command.StartsWith(CommandConstants.Status)) re

我目前正在编写的一些代码中执行类似的操作:

public CommandType GetCommandTypeFromCommandString(String command)
{
   if(command.StartsWith(CommandConstants.Acknowledge))
      return CommandType.Acknowledge;
   else if (command.StartsWith(CommandConstants.Status))
      return CommandType.Status;
   else if (command.StartsWith(CommandConstants.Echo))
      return CommandType.Echo;
   else if (command.StartsWith(CommandConstants.Warning))
     return CommandType.Warning;
     // and so on

   return CommandType.None;
}

我想知道在C#中是否有更有效的方法来实现这一点。这段代码需要每秒执行很多次,而且我对执行所有这些字符串比较所需的时间不太满意。有什么建议吗?:)

这是一项相当多的工作,但是您可以基于每个令牌构造一个。FSM将逐个接受命令字符串中的字符;每个令牌都有一个最终状态,当命令没有以任何令牌启动时,它将有一个附加的最终状态

IDictionary<string,CommandType> 
IDictionary
并用值填充它

你不需要比较每件事…只要在表格里查一下就行了

您还需要更好地定义命令语法。在命令和行的其余部分之间需要一个空格,例如…

多少次是“多少次”?我严重怀疑这部分代码是否是瓶颈。您可以根据每个命令的第一个字母使用switch语句对其进行一点点优化,假设它们都不同


但话说回来,它真的有用吗?我不会在这上面下太多赌注。

与Vojislav的FSM答案在概念上类似,您可以尝试将常数放入一个表中。然后,您可以将比较序列替换为trie的单个遍历


这里描述了一个C#trie实现。

注意:我不是在演示如何使用异常处理来控制程序流。如果枚举的字符串名称不存在,Parse将引发异常。Catch子句只返回默认的CommandType,与提问者的示例代码一样

如果对象只是返回实际的枚举对象,那么给定的字符串名称不能使用:

try
{
    return (CommandType)Enum.Parse(typeof(CommandType), strCmdName, true);
}
catch (Exception)
{
    return CommandType.None;
}

我认为你应该更注重可读性而不是效率。这些行动相当快。我支持Serge,这部分代码不太可能是瓶颈。我会这样做:

public CommandType GetCommandTypeFromCommandString(String command)
{
   for(String currentCommand : allCommands) {
       if(command.StartsWith(currentCommand))
           return currentCommand;
   }
   return CommandType.None;
}
编辑:
事后想一想,如果您知道哪些命令使用最频繁,您可以对数组进行排序,使这些命令位于开始位置。。。如果保留if语句,也可以使用它们来执行此操作。

一种优化方法是使用StringComparison枚举指定只需要顺序比较。像这样:

if(command.StartsWith(CommandConstants.Acknowledge, StringComparison.Ordinal))
    return CommandType.Acknowledge;
如果不指定字符串比较方法,则将使用当前区域性进行比较,这会稍微减慢速度

我做了一些(非常天真的)基准测试:

var a = "foo bar foo";
var b = "foo";

int numTimes = 1000000;

Benchmark.Time(() => a.StartsWith(b, StringComparison.Ordinal), "ordinal", numTimes);
Benchmark.Time(() => a.StartsWith(b), "culture sensitive", numTimes);
这产生了以下结果:

ordinal ran 1000000 times in 35.6033 ms culture sensitive ran 1000000 times in 175.5859 ms ordinal在35.6033毫秒内运行1000000次 文化敏感型在175.5859毫秒内运行1000000次 您还应该对比较进行排序,以便首先比较最可能的标记(快乐路径)


这些优化是使当前实现的性能更好的一种简单方法,但如果性能真的很关键(我的意思是非常关键),您应该考虑实现某种状态机。

我认为使用正则表达式和字典可以做得更好:

static Regex reCommands = new Regex("^(cmd1|cmd2|cmd3|cmd4)", RegexOptions.Compiled);
static Dictionary<string, CommandType> Commands = new Dictionary<string, CommandType>();
private static InitDictionary()
{
    Commands.Add("cmd1", cmdType1);
    Commands.Add("cmd2", cmdType2);
    Commands.Add("cmd3", cmdType3);
    Commands.Add("cmd4", cmdType4);
}
public CommandType GetCommandTypeFromCommandString(String command)
{
    Match m = reCommands.Match(command);
    if (m.Success)
    {
        return Commands[m.Groups[1].Value];
    }
    return CommandType.None; // no command
}
static Regex reCommands=new Regex(“^(cmd1 | cmd2 | cmd3 | cmd4)”,RegexOptions.Compiled);
静态字典命令=新建字典();
私有静态InitDictionary()
{
命令。添加(“cmd1”,cmdType1);
命令。添加(“cmd2”,cmdType2);
命令。添加(“cmd3”,cmdType3);
命令。添加(“cmd4”,cmdType4);
}
公共命令类型GetCommandTypeFromCommandString(字符串命令)
{
Match m=推荐。Match(命令);
如果(m.成功)
{
返回命令[m.Groups[1].Value];
}
返回CommandType.None;//无命令
}

我做了一些类似的扩展方法:

public static bool StartsWith(this string s, params string[] candidate)
{
    string match = candidate.FirstOrDefault(t => s.StartsWith(t));
    return match != default(string);
}
现在,这只是一个谓词,返回数组中的字符串是否以给定字符串开头,但您可以对其进行一些修改:

public static int PrefixIndex(this string s, params string[] candidate)
{
    int index = -1;
    string match = candidate.FirstOrDefault(t => { index++; return s.StartsWith(t); });
    return match == default(string) ? -1 : index;
}
在使用中,它将是:

int index = command.PrefixIndex(tokenStrings);

if (index >= 0) {
    // convert to your enum
}

在一篇评论中,我看到您希望在1/40秒内进行30次字符串比较。我的朋友,你应该能在1MHz的机器上做到这一点。在1/40秒内进行数千次字符串比较应该不费吹灰之力。

编辑:鉴于对秒表的一个警告的误解,我最初的答案在结合StringComparison.Ordinal时表现不如StartsWith。即使您使用所有正确的选项编译正则表达式,它也会稍微慢一点,其性能与使用StartsWith而不使用任何StringComparison设置相当。然而,正则表达式路径确实为您提供了更大的灵活性来匹配模式,而StartsWith则没有,因此我将我的原始答案留给了后代

原始答案:

我必须承认,我不确定你到底在寻找什么——然而,在我看来,这类代码通常是在解析某种描述的日志文件,以提取有用的信息。为了避免长期进行所有字符串比较,您可以生成枚举中值的正则表达式,然后使用匹配项解析正确的枚举项:

public enum CommandType
{
    Acknowledge,
    Status,
    Echo,
    Warning
}
static public CommandType? GetCommandTypeFromString(String command)
{
    var CommandTypes = String.Join("|", Enum.GetNames(typeof(CommandType)));
    var PassedConstant = Regex.Match(command, String.Format("(?i:^({0}))", CommandTypes)).Value;
    if (PassedConstant != String.Empty)
        return (CommandType)Enum.Parse(typeof(CommandType), PassedConstant, true);
    return null;
}

static void Main(string[] args)
{
    Console.WriteLine(GetCommandTypeFromString("Acknowledge that I am great!").ToString());
}
这将从字符串的开头拉出CommandType.Acknowledge,但前提是它存在于字符串的开头。。。它还将拉出其他正确的命令类型

通过与公认答案进行类似的基准测试,我获得了大约40%的性能提升。我用10421ms的时间在一百万次迭代中运行了接受的代码,但我的代码只运行了6459ms


当然,虽然您正在使用的if语句看起来可能没有您想要的那么有效,但它仍然比使用正则表达式更容易阅读…

trie几乎肯定是最快的方法。如果前缀的长度相同,则可以通过对前缀进行散列以获得