我正在做一些事情,我意识到我想要在一个字符串中找到多少个/s,然后我突然想到,有几种方法可以做到这一点,但不能决定哪种是最好的(或最简单的)。
目前我想说的是:
string source = "/once/upon/a/time/";
int count = source.Length - source.Replace("/", "").Length;
但我一点都不喜欢,有人愿意吗?
我并不想为此挖掘出正则表达式,对吧?
我知道我的字符串将包含我要搜索的项,所以你可以假设…
当然对于长度为> 1的字符串,
string haystack = "/once/upon/a/time";
string needle = "/";
int needleCount = ( haystack.Length - haystack.Replace(needle,"").Length ) / needle.Length;
我最初的想法是这样的:
public static int CountOccurrences(string original, string substring)
{
if (string.IsNullOrEmpty(substring))
return 0;
if (substring.Length == 1)
return CountOccurrences(original, substring[0]);
if (string.IsNullOrEmpty(original) ||
substring.Length > original.Length)
return 0;
int substringCount = 0;
for (int charIndex = 0; charIndex < original.Length; charIndex++)
{
for (int subCharIndex = 0, secondaryCharIndex = charIndex; subCharIndex < substring.Length && secondaryCharIndex < original.Length; subCharIndex++, secondaryCharIndex++)
{
if (substring[subCharIndex] != original[secondaryCharIndex])
goto continueOuter;
}
if (charIndex + substring.Length > original.Length)
break;
charIndex += substring.Length - 1;
substringCount++;
continueOuter:
;
}
return substringCount;
}
public static int CountOccurrences(string original, char @char)
{
if (string.IsNullOrEmpty(original))
return 0;
int substringCount = 0;
for (int charIndex = 0; charIndex < original.Length; charIndex++)
if (@char == original[charIndex])
substringCount++;
return substringCount;
}
使用替换和除法的大海捞针方法产生21秒以上,而这需要大约15.2秒。
在添加位后进行编辑,这将添加子字符串。长度- 1到charIndex(就像它应该的那样),它在11.6秒。
编辑2:我使用了一个有26个双字符字符串的字符串,这里是更新到相同示例文本的时间:
大海捞针(OP版本):7.8秒
建议的机制:4.6秒。
编辑3:添加单个字符的大小写,它变成了1.2秒。
编辑4:作为上下文:使用了5000万次迭代。
var conditionalStatement = conditionSetting.Value;
//order of replace matters, remove == before =, incase of ===
conditionalStatement = conditionalStatement.Replace("==", "~").Replace("!=", "~").Replace('=', '~').Replace('!', '~').Replace('>', '~').Replace('<', '~').Replace(">=", "~").Replace("<=", "~");
var listOfValidConditions = new List<string>() { "!=", "==", ">", "<", ">=", "<=" };
if (conditionalStatement.Count(x => x == '~') != 1)
{
result.InvalidFieldList.Add(new KeyFieldData(batch.DECurrentField, "The IsDoubleKeyCondition does not contain a supported conditional statement. Contact System Administrator."));
result.Status = ValidatorStatus.Fail;
return result;
}
需要做一些类似于从字符串测试条件语句的事情。
用单个字符替换我正在寻找的内容,并计算单个字符的实例数。
显然,在发生这种情况之前,您需要检查您正在使用的单个字符是否存在于字符串中,以避免错误计数。
Split (may)胜过IndexOf(用于字符串)。
上面的基准测试似乎表明Richard Watson是最快的字符串,这是错误的(可能差异来自我们的测试数据,但由于下面的原因,它看起来很奇怪)。
如果我们更深入地研究这些方法在.NET中的实现(对于Luke H, Richard Watson方法),
IndexOf取决于区域性,它将尝试检索/创建ReadOnlySpan,检查是否必须忽略大小写等。最后执行不安全/本机调用。
Split能够处理多个分隔符,并有一些StringSplitOptions
并且必须创建字符串[]数组并用分割结果填充它(所以做一些子字符串)。根据字符串出现的数量,Split可能比IndexOf更快。
顺便说一下,我做了一个简化版本的IndexOf(它可以更快,如果我使用指针和不安全,但不勾选应该是ok的大多数),它至少快了4个数量级。
基准测试(来源GitHub)
通过搜索一个常见的单词(the)或一个小句子
莎士比亚,理查三世。
Method |
Mean |
Error |
StdDev |
Ratio |
Richard_LongInLong |
67.721 us |
1.0278 us |
0.9614 us |
1.00 |
Luke_LongInLong |
1.960 us |
0.0381 us |
0.0637 us |
0.03 |
Fab_LongInLong |
1.198 us |
0.0160 us |
0.0142 us |
0.02 |
-------------------- |
-----------: |
----------: |
----------: |
------: |
Richard_ShortInLong |
104.771 us |
2.8117 us |
7.9304 us |
1.00 |
Luke_ShortInLong |
2.971 us |
0.0594 us |
0.0813 us |
0.03 |
Fab_ShortInLong |
2.206 us |
0.0419 us |
0.0411 us |
0.02 |
--------------------- |
----------: |
---------: |
---------: |
------: |
Richard_ShortInShort |
115.53 ns |
1.359 ns |
1.135 ns |
1.00 |
Luke_ShortInShort |
52.46 ns |
0.970 ns |
0.908 ns |
0.45 |
Fab_ShortInShort |
28.47 ns |
0.552 ns |
0.542 ns |
0.25 |
public int GetOccurrences(string input, string needle)
{
int count = 0;
unchecked
{
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(needle))
{
return 0;
}
for (var i = 0; i < input.Length - needle.Length + 1; i++)
{
var c = input[i];
if (c == needle[0])
{
for (var index = 0; index < needle.Length; index++)
{
c = input[i + index];
var n = needle[index];
if (c != n)
{
break;
}
else if (index == needle.Length - 1)
{
count++;
}
}
}
}
}
return count;
}
我做了一些研究,发现理查德·沃森的解决方案在大多数情况下是最快的。这是文章中每个解决方案的结果表(除了那些使用Regex的,因为它在解析“test{test”这样的字符串时抛出异常)
Name | Short/char | Long/char | Short/short| Long/short | Long/long |
Inspite | 134| 1853| 95| 1146| 671|
LukeH_1 | 346| 4490| N/A| N/A| N/A|
LukeH_2 | 152| 1569| 197| 2425| 2171|
Bobwienholt | 230| 3269| N/A| N/A| N/A|
Richard Watson| 33| 298| 146| 737| 543|
StefanosKargas| N/A| N/A| 681| 11884| 12486|
可以看到,在短字符串(10-50个字符)中查找短子字符串(1-5个字符)的出现次数时,首选原算法。
同样,对于多字符子字符串,您应该使用以下代码(基于Richard Watson的解决方案)
int count = 0, n = 0;
if(substring != "")
{
while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
{
n += substring.Length;
++count;
}
}