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几乎肯定是最快的方法。如果前缀的长度相同,则可以通过对前缀进行散列以获得