我正在做一些事情,我意识到我想要在一个字符串中找到多少个/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;
从。Net 5 (Net core 2.1+和NetStandard 2.1)开始,我们有了一个新的迭代速度之王。
“跨度<T>”https://learn.microsoft.com/en-us/dotnet/api/system.span-1?view=net-5.0
String有一个内置成员,返回Span<Char>
int count = 0;
foreach( var c in source.AsSpan())
{
if (c == '/')
count++;
}
我的测试显示比直刺快62%我还比较了Span<T>[I]上的for()循环,以及这里发布的其他一些内容。注意,String上的反向for()迭代现在似乎比直接foreach运行得慢。
Starting test, 10000000 iterations
(base) foreach = 673 ms
fastest to slowest
foreach Span = 252 ms 62.6%
Span [i--] = 282 ms 58.1%
Span [i++] = 402 ms 40.3%
for [i++] = 454 ms 32.5%
for [i--] = 867 ms -28.8%
Replace = 1905 ms -183.1%
Split = 2109 ms -213.4%
Linq.Count = 3797 ms -464.2%
更新:2021年12月,Visual Studio 2022, .NET 5和6
.NET 5
Starting test, 100000000 iterations set
(base) foreach = 7658 ms
fastest to slowest
foreach Span = 3710 ms 51.6%
Span [i--] = 3745 ms 51.1%
Span [i++] = 3932 ms 48.7%
for [i++] = 4593 ms 40.0%
for [i--] = 7042 ms 8.0%
(base) foreach = 7658 ms 0.0%
Replace = 18641 ms -143.4%
Split = 21469 ms -180.3%
Linq = 39726 ms -418.8%
Regex Compiled = 128422 ms -1,577.0%
Regex = 179603 ms -2,245.3%
.NET 6
Starting test, 100000000 iterations set
(base) foreach = 7343 ms
fastest to slowest
foreach Span = 2918 ms 60.3%
for [i++] = 2945 ms 59.9%
Span [i++] = 3105 ms 57.7%
Span [i--] = 5076 ms 30.9%
(base) foreach = 7343 ms 0.0%
for [i--] = 8645 ms -17.7%
Replace = 18307 ms -149.3%
Split = 21440 ms -192.0%
Linq = 39354 ms -435.9%
Regex Compiled = 114178 ms -1,454.9%
Regex = 186493 ms -2,439.7%
我添加了更多的循环,并加入了RegEx,这样我们就可以看到在大量迭代中使用它是一场灾难。
我认为for(++)循环比较可能已经在。net 6中进行了优化,以便在内部使用Span -因为它与foreach Span的速度几乎相同。
代码链接
对于任何想要使用String扩展方法的人,
以下是我使用的基于张贴的最好的答案:
public static class StringExtension
{
/// <summary> Returns the number of occurences of a string within a string, optional comparison allows case and culture control. </summary>
public static int Occurrences(this System.String input, string value, StringComparison stringComparisonType = StringComparison.Ordinal)
{
if (String.IsNullOrEmpty(value)) return 0;
int count = 0;
int position = 0;
while ((position = input.IndexOf(value, position, stringComparisonType)) != -1)
{
position += value.Length;
count += 1;
}
return count;
}
/// <summary> Returns the number of occurences of a single character within a string. </summary>
public static int Occurrences(this System.String input, char value)
{
int count = 0;
foreach (char c in input) if (c == value) count += 1;
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;
}
}
查找字符计数与查找字符串计数有很大不同。另外,这也取决于你是否想要检查不止一个。如果你想检查各种不同的字符计数,像这样的东西可以工作:
var charCounts =
haystack
.GroupBy(c => c)
.ToDictionary(g => g.Key, g => g.Count());
var needleCount = charCounts.ContainsKey(needle) ? charCounts[needle] : 0;
注1:分组到字典中非常有用,因此为它编写GroupToDictionary扩展方法非常有意义。
注意2:拥有自己的字典实现也很有用,它允许默认值,然后您可以自动为不存在的键获取0。