你如何计算字符串中字符串(实际上是字符)的出现次数?

ins*_*ite 820 c# string

我正在做一些事情,我意识到我想要计算/我能在字符串中找到多少,然后它让我感到震惊,有几种方法可以做到,但无法决定最好的(或最简单的)是什么.

目前我正在做的事情如下:

string source = "/once/upon/a/time/";
int count = source.Length - source.Replace("/", "").Length;
Run Code Online (Sandbox Code Playgroud)

但我完全不喜欢它,任何接受者?

我真的不想挖掘RegEx这个,是吗?

我知道我的字符串将会有我正在搜索的术语,所以你可以认为......

当然,对于字符串,其中 长度> 1,

string haystack = "/once/upon/a/time";
string needle = "/";
int needleCount = ( haystack.Length - haystack.Replace(needle,"").Length ) / needle.Length;
Run Code Online (Sandbox Code Playgroud)

Luk*_*keH 955

如果您使用的是.NET 3.5,则可以使用LINQ在单行中执行此操作:

int count = source.Count(f => f == '/');
Run Code Online (Sandbox Code Playgroud)

如果您不想使用LINQ,可以使用以下命令:

int count = source.Split('/').Length - 1;
Run Code Online (Sandbox Code Playgroud)

您可能会惊讶地发现,您的原始技术似乎比其中任何一种快约30%!我刚用"/ once/on/a/time /"做了快速基准测试,结果如下:

你的原始= 12s source.Count
= 19s
source.Split = 17s
foreach(来自bobwienholt的回答)= 10s

(时间是50,000,000次迭代,因此您不太可能注意到现实世界中的差异.)

  • 听到我的代码比任何东西都快! (27认同)
  • 请注意,Count和Split解决方案仅在您对字符进行计数时才有效.他们不会使用字符串,就像OP的解决方案那样. (26认同)
  • 这种行为可能是因为VS2010在新的类文件中自动包含System.Linq,VS2008可能没有.命名空间需要用于智能感知才能工作. (10认同)
  • 这似乎是一个不同问题的答案:"你如何计算字符串中char的出现?" (9认同)
  • 是的,VS隐藏了字符串类的LINQ扩展方法.我猜他们认为开发人员不希望所有这些扩展方法出现在字符串类中.可能是一个明智的决定. (6认同)
  • `f =='\'`是关于字符串中的字符,而不是字符串中的字符串 (5认同)
  • 另外值得注意的是,如果使用System.GC.GetTotalMemory(false)监视内存使用情况.重复5000万次迭代测试,我看到在LINQ之后准备好垃圾收集大约2,000,000个字节.随着foreach循环......零.如果您处于高重复代码区域,LINQ可能看起来很光滑,但是要上学. (2认同)

bob*_*olt 171

string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source) 
  if (c == '/') count++;
Run Code Online (Sandbox Code Playgroud)

必须比source.Replace()自己快.

  • 通过切换到for而不是foreach,你可以获得微小的改进,但只是微小的一点点. (15认同)
  • 不.问题要求计算字符串的出现,而不是字符. (15认同)
  • 这是计算字符串中的字符.标题是关于计算字符串中的字符串 (3认同)
  • @Mark刚刚使用for循环对其进行了测试,它实际上比使用foreach慢。可能是因为边界检查?(时间为1.65秒,而500万次迭代为2.05。) (2认同)
  • 虽然问题是要求字符串中的字符串,但是发布的示例问题OP实际上只是一个字符,在这种情况下我会称这个答案仍然是一个有效的解决方案,因为它显示了一种更好的方法(字符搜索而不是字符串搜索)解决手头的问题. (2认同)

Yet*_*ker 130

int count = new Regex(Regex.Escape(needle)).Matches(haystack).Count;
Run Code Online (Sandbox Code Playgroud)

  • +1 - 在某些情况下,您可能想要添加`RegexOptions.IgnoreCase`. (6认同)
  • 我选择这个是因为它可以搜索字符串,而不仅仅是字符。 (4认同)
  • 这不是很低吗? (3认同)
  • 正则表达式的开销并不理想,加上"我真的不想为此挖出RegEx,是吗?" (3认同)

mqp*_*mqp 84

如果您希望能够搜索整个字符串,而不仅仅是字符:

src.Select((c, i) => src.Substring(i))
    .Count(sub => sub.StartsWith(target))
Run Code Online (Sandbox Code Playgroud)

读作"对于字符串中的每个字符,将该字符的其余部分从该字符开始作为子字符串;如果它以目标字符串开头,则计算它."

  • **SUPER SLOW!***在html页面上尝试过,与此页面上的其他方法相比花费了大约2分钟花了2秒钟.答案是对的; 它太慢而无法使用.* (55认同)
  • 为我的210000个字符串字符抛出OutOfMemoryException. (6认同)
  • 请注意,这个速度很慢的原因是它创建了n个字符串,因此分配大约n ^ 2/2个字节. (5认同)
  • 同意,太慢了.我是linq风格解决方案的忠实粉丝,但这一点并不可行. (2认同)
  • 现在,借助新的 `Span` API,您可以_效率较低_地执行此操作 m̶o̶r̶e̶ ̶e̶f̶f̶i̶c̶i̶e̶n̶t̶l̶y̵。:) 首先准备两个变量,`srcSpan = src.AsSpan()`和`targetSpan = target.AsSpan()`。然后将 src.Substring(i) 替换为 srcSpan.Slice(i) ,并将 sub.StartsWith(target) 替换为 sub.StartsWith(targetSpan) 。这避免了荒谬的堆分配次数,但不能避免 O(N^2) 时间复杂度。 (2认同)

tsi*_*nyx 62

我做了一些研究,发现Richard Watson的解决方案在大多数情况下都是最快的.这是包含帖子中每个解决方案结果的表(除了那些使用正则表达式,因为它在解析字符串时抛出异常,如"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|
Run Code Online (Sandbox Code Playgroud)

您可以看到,如果在短字符串(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 只是为了指出其他人,如果空的话需要检查搜索值,否则你将进入无限循环. (2认同)
  • 也许它只是我,但是对于`source ="aaa"substring ="aa"`我希望得到2,而不是1.要"修复"这个,将`n + = substring.Length`改为`n ++` (2认同)

Jud*_*ngo 53

LINQ适用于所有集合,因为字符串只是一个字符集合,所以这个漂亮的小单行如何:

var count = source.Count(c => c == '/');
Run Code Online (Sandbox Code Playgroud)

确保您using System.Linq;位于代码文件的顶部,这.Count是来自该命名空间的扩展方法.

  • @Whatsit:你可以用左手输入'var'而'int'需要双手;) (67认同)
  • `int`字母全部驻留在home键中,而`var`则不存在.呃..等等,我正在使用Dvorak (7认同)
  • 在那里使用var真的值得吗?有没有机会将Count替换为不返回int的东西? (5认同)
  • @JudahGabrielHimango我会争辩说,当变量类型很明显时(为了简洁和一致),应该_特别是使用var (3认同)
  • @BDotA确保你有'使用System.Linq;' 在您的文件的顶部.此外,intellisense可能会隐藏.Count调用,因为它是一个字符串.即使这样,它也会编译并运行得很好. (2认同)

Ric*_*son 48

string source = "/once/upon/a/time/";
int count = 0;
int n = 0;

while ((n = source.IndexOf('/', n)) != -1)
{
   n++;
   count++;
}
Run Code Online (Sandbox Code Playgroud)

在我的计算机上,它比5000万次迭代的每个角色解决方案快约2秒.

2013年修订:

将字符串更改为char []并迭代它.将总时间缩短一两秒,进行50米迭代!

char[] testchars = source.ToCharArray();
foreach (char c in testchars)
{
     if (c == '/')
         count++;
}
Run Code Online (Sandbox Code Playgroud)

这更快:

char[] testchars = source.ToCharArray();
int length = testchars.Length;
for (int n = 0; n < length; n++)
{
    if (testchars[n] == '/')
        count++;
}
Run Code Online (Sandbox Code Playgroud)

为了更好地衡量,从数组末尾迭代到0似乎是最快的,大约5%.

int length = testchars.Length;
for (int n = length-1; n >= 0; n--)
{
    if (testchars[n] == '/')
        count++;
}
Run Code Online (Sandbox Code Playgroud)

我想知道为什么会这样,并且谷歌搜索(我记得有关反向迭代更快的事情),并且发现了这个烦人地使用字符串char []技术的问题.不过,我认为逆转技巧在这方面是新的.

在C#中迭代字符串中单个字符的最快方法是什么?

  • 您可以放置​​ `source.IndexOf('/', n + 1)` 并丢失 `n++` 和 while 的括号:) 另外,放置一个变量 `string word = "/"` 而不是字符。 (2认同)
  • 我在某处读到,向后迭代速度更快,因为将值与 0 进行比较更快 (2认同)

Zom*_*eep 45

这些都只适用于单字符搜索术语......

countOccurences("the", "the answer is the answer");

int countOccurences(string needle, string haystack)
{
    return (haystack.Length - haystack.Replace(needle,"").Length) / needle.Length;
}
Run Code Online (Sandbox Code Playgroud)

对于更长的针头可能会变得更好......

但必须有一种更优雅的方式.:)


Bri*_*lph 19

编辑:

source.Split('/').Length-1
Run Code Online (Sandbox Code Playgroud)

  • 这将在堆上执行至少n个字符串分配,加上(可能)少量阵列重新调整大小 - 所有这些只是为了得到计数?非常低效,不能很好地扩展,不应该在任何重要的代码中使用. (4认同)
  • 这就是我的工作.和`source.Split(new [] {"//"},StringSplitOptions.None).Count - 1`用于多字符分隔符. (2认同)

Dav*_*ave 15

在C#中,一个不错的String SubString计数器就是这个意想不到的棘手问题:

public static int CCount(String haystack, String needle)
{
    return haystack.Split(new[] { needle }, StringSplitOptions.None).Length - 1;
}
Run Code Online (Sandbox Code Playgroud)

  • -1 因为:您知道 Count() 和 Count 或 Length 之间的区别吗?如果有人使用 Count() 而不是 Count 或 Length 我会被触发。Count() 创建 IEnumerator 然后遍历 IEnumerable 的所有出现,而 Count 或 Length 已经设置了对象的属性,这些属性已经保存了您想要的计数,而无需遍历所有元素。 (2认同)

ced*_*lof 14

Regex.Matches(input,  Regex.Escape("stringToMatch")).Count
Run Code Online (Sandbox Code Playgroud)


小智 12

private int CountWords(string text, string word) {
    int count = (text.Length - text.Replace(word, "").Length) / word.Length;
    return count;
}
Run Code Online (Sandbox Code Playgroud)

因为原始解决方案对于字符来说是最快的,我想它也适用于字符串.所以这是我的贡献.

对于上下文:我在日志文件中寻找"失败"和"成功"之类的单词.

Gr,Ben

  • 只是不要为“ word”变量传递一个空字符串(除以零误差)。 (2认同)

小智 11

string s = "65 fght 6565 4665 hjk";
int count = 0;
foreach (Match m in Regex.Matches(s, "65"))
  count++;
Run Code Online (Sandbox Code Playgroud)

  • 或Regex.Matches(s,"65").计数^ _ ^ (20认同)

bmi*_*ler 9

从 .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++;
}
Run Code Online (Sandbox Code Playgroud)

我的测试显示比直接 foreach 快 62%。我还与 Span<T>[i] 上的 for() 循环以及此处发布的其他一些循环进行了比较。请注意,字符串上的反向 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%
Run Code Online (Sandbox Code Playgroud)

更新: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%
Run Code Online (Sandbox Code Playgroud)

我添加了更多循环并放入了正则表达式,这样我们就可以看到在大量迭代中使用它是多么的灾难。我认为 for(++) 循环比较可能已在 .NET 6 中进行了优化,以便在内部使用 Span - 因为它的速度几乎与 foreach span 相同。

代码链接


Tim*_*imo 8

从 .NET 7 开始,我们拥有免分配(且高度优化)的正则表达式 API。计数特别简单、高效。

    var input = "abcd abcabc ababc";
    var result = Regex.Count(input: input, pattern: "abc"); // 4
Run Code Online (Sandbox Code Playgroud)

当匹配动态模式时,记得转义它们:

public static int CountOccurences(string input, string pattern)
{
    pattern = Regex.Escape(pattern); // Aww, no way to avoid heap allocations here

    var result = Regex.Count(input: input, pattern: pattern);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

而且,作为固定模式的奖励,.NET 7 引入了分析器,可以帮助将正则表达式字符串转换为源生成的代码。这不仅避免了正则表达式的运行时编译开销,而且还提供了非常可读的代码来显示它是如何实现的。事实上,该代码通常至少与您手动编写的任何替代代码一样高效。

如果您的正则表达式调用符合条件,分析器将给出提示。只需选择“转换为‘GenelatedRegexAttribute’”即可享受结果:

[GeneratedRegex("abc")]
private static partial Regex MyRegex(); // Go To Definition to see the generated code
Run Code Online (Sandbox Code Playgroud)


use*_*847 7

public static int GetNumSubstringOccurrences(string text, string search)
{
    int num = 0;
    int pos = 0;

    if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(search))
    {
        while ((pos = text.IndexOf(search, pos)) > -1)
        {
            num ++;
            pos += search.Length;
        }
    }
    return num;
}
Run Code Online (Sandbox Code Playgroud)


Who*_*ich 7

对于任何想要使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为最简单的方法是使用正则表达式.这样,您可以获得与使用myVar.Split('x')相同的拆分计数,但是在多字符设置中.

string myVar = "do this to count the number of words in my wording so that I can word it up!";
int count = Regex.Split(myVar, "word").Length;
Run Code Online (Sandbox Code Playgroud)