【问题标题】:Counting the amount of repetitions a letter has in a string计算一个字母在字符串中的重复次数
【发布时间】:2019-12-09 02:24:50
【问题描述】:

我正在编写 CodeWars 上的 Kata,我必须计算每个字母在字符串中的重复次数。重复次数应存储在 int 数组中。

我编写的算法似乎几乎可以工作,但是我得到了一个我无法解释的奇怪输出。我可能在代码中遗漏了一些东西。

static void Main(string[] args)
{
    string str = "abcdef";
    string input = str.ToLower();
    int count = 0;

    string[] arrayInput = Regex.Split(input, string.Empty);
    string[] alphabet = Regex.Split("abcdefghijklmnopqrstuvwxyz", string.Empty);
    int[] amounts = new int[input.Length];

    foreach (string letter in alphabet)
    {
        for (int x = 0; x < input.Length; x++)
        {
            if (arrayInput[x] == letter)
            {
               amounts[x]++;
            }
        }
    }

    foreach (int amount in amounts)
    {
        Console.Write(amount + ", ");
    }
    Console.ReadKey();
}

输出:

"2, 1, 1, 1, 1, 1,"

预期:

"1, 1, 1, 1, 1, 1,"

因为每个字母在字符串中只出现一次。

【问题讨论】:

  • 我建议使用调试器单步执行代码;没有什么比准确地看到你在代码中混淆的地方更好了。
  • 我可以建议的一件事是将行数量[x]++ 更改为数量[x] = 数量[x] + 1
  • Console.Write(string.Join(", ", str.ToLower().GroupBy(c =&gt; c).Select(group =&gt; group.Count()));

标签: c# arrays for-loop


【解决方案1】:

查询时,Linq往往是不错的选择:

  using System.Linq;

  ...

  string str = "abcdef";

  // {1, 1, 1, 1, 1, 1} - each letter appears once 
  int[] result = str
    .ToLower()
  //.Where(c => c >= 'a' && c <= 'z') // uncomment, if we want 'a'..'z' range only 
    .GroupBy(c => c)
    .Select(group => group.Count())
    .ToArray();

  Console.Write(string.Join(", ", result));

【讨论】:

    【解决方案2】:

    我认为你犯了一个错误:

    int[] amounts = new int[input.Length];
    

    应该是

    int[] amounts = new int[26];
    

    而且你的循环也不完全正确。

    您不需要将字符串拆分为字符串数组。您可以只使用字符串迭代器来获取每个字符。此外,如果您在非常大的字符串上执行此操作,那么您的解决方案将效率低下,因为对于您正在遍历整个不需要的整个字母表的每个字符。

    你可以大大简化你写的东西:

    string input = "abcdef";
    int[] counts = new int[26]; 
    foreach (var ch in input)
    {
        var c = char.ToLower(ch);
        if (c >= 'a' && c <= 'z')
            counts[c - 'a']++;
    }
    

    【讨论】:

      【解决方案3】:

      有很多不同的方法,但在计算有限数量的项目时,就性能而言,字典几乎总是最佳选择。如果与使用 LINQ 的解决方案相比,下面的代码是相当低级的,但这就是我喜欢的地方:你总是可以控制那里发生的事情。

      string str = "abcdef";
      string input = str.ToLower();
      
      var dict = "abcdefghijklmnopqrstuvwxyz".ToDictionary(k => k, v => 0);
      
      foreach (char c in input)
      {
          dict[c]++;
      }
      
      var output = new int[dict.Count];   
      var index = 0;
      
      foreach (var key in dict.Keys.OrderBy(k => k))
      {
          output[index++] = dict[key];
      }
      

      如果您想可视化带有计数的字典的外观,可以添加以下输出:

      foreach (var key in dict.Keys)
      {
          Console.WriteLine($"Key {key} Value {dict[key]}");
      }
      

      【讨论】:

      • 以上是一个简洁且如您所说的有效解决方案。一个小问题是,如果你的 str 包含非小写字母字符,你的 dict[c] 会抛出 KeyNotFoundException。您需要在每个字符上都有一个 ToLower 并检查密钥是否存在
      • @TimRutter 感谢您发现这一点!我在想使用 StringComparer.OrdinalIgnoreCase 会让事情变得更容易,但事实并非如此。我有一个字符数组,而不是要操作的字符串。因此,越来越多地考虑这种变化,我意识到它只会增加复杂性。
      • 或者,@TimRutter,将StringComparer.OrdinalIgnoreCase 传递给ToDictionary 调用。
      • 另一条评论是您不需要执行 ToCharArray。 string.ToDictionary(...) 创建 Dictionary 因为字符串是 IEnumerable
      • 但是你是对的,使用 StringComparer.OrdinalIgnoreCase 你不会得到带有大写字母的 KeyNotFoundException。但是,如果有非字母字符,您仍然可以这样做。
      【解决方案4】:

      您的代码中有几个问题来实现您正在寻找的内容,例如您将 stralphabetby empty string 拆分,这将始终在您的数组中为您提供两个额外的空字符串! 无论如何,我认为您可以像这样更有效地使用Dictionary 来做到这一点:

      string str = "abcdef";
      Dictionary<char, int> count_letters = new Dictionary<char, int>();
      foreach (var alphabet in str)
      {
          if (count_letters.ContainsKey(alphabet))
              count_letters[alphabet] ++;
          else
              count_letters.Add(alphabet, 1);
      }
      
      foreach (var result in count_letters)
          Console.WriteLine("{0} - {1}", result.Key, result.Value);
      

      【讨论】:

        【解决方案5】:

        您的解决方案似乎过于复杂 - 正则表达式、数组分配和嵌套循环的组合使得很难看到发生了什么。

        您基本上可以将解决方案实现为 map/reduce 变体。首先通过映射到单个字符(映射)的单个字符对字符进行分组,然后通过计数来减少它们:

        var input = "abcdef";
        var groups = input.GroupBy(c => c);
        var counts = groups.Select(g => g.Count());
        Console.WriteLine(string.Join(", ", counts));
        

        【讨论】:

          【解决方案6】:

          当您在拆分后打印值时,您将观察到额外的空间。这似乎导致了问题。

          using System;
          using System.Text.RegularExpressions;					
          public class Program
          {
          	public static void Main()
          	{
          		  string str = "abcdef";
              string input = str.ToLower();
              int count = 0;
          
              char[] arrayInput = input.ToCharArray();
              char[] alphabet = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
              int[] amounts = new int[input.Length];
              
          	foreach (char letter in alphabet)
          	Console.Write(letter + ", ");
          	// observe the first letter here when you use regx.split
          	//, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,	
          	
          	foreach (char inputWord in arrayInput)
          	Console.Write(inputWord + ", ");
          	// you get extra space in the start hence there is issue when you use regx.split
          	//, a, b, c, d, e, f,
          		
              foreach (var letter in alphabet)
              {
                  for (int x = 0; x < input.Length; x++)
                  {
                      if (arrayInput[x] == letter)
                      {
                         amounts[x] = amounts[x] + 1;
                      }
                  }
              }
          
              foreach (int amount in amounts)
              {
                  Console.Write(amount + ", ");
              }
             
          	}
          }

          坚持基本:使用 string.ToCharArray()

          【讨论】:

            【解决方案7】:

            您的 regex.split 将额外的插槽放入您的数组中。试试这个:

            string[] arrayInput = input.Select(c => c.ToString()).ToArray();
            string[] alphabet = "abcdefghijklmnopqrstuvwxyz".Select(c => c.ToString()).ToArray();
            

            【讨论】:

            • 为什么需要创建一个字符串数组呢?只需使用索引器遍历字符串。
            • 是的。有很多方法可以改进代码,但我只想指出代码存在的特定“错误”。听起来他不是在要求批评,只是对明显异常的解释。真的,@Heretic Monkey 的评论才是真正的答案。
            【解决方案8】:

            这是您的代码,稍作修正,现在可以正常工作了。

              static void Main(string[] args)
                    {
                        string str = "abbbcdef";
                        str = str.ToLower();
                        char[] arrayInput = str.ToCharArray();
                        char[] alphabet = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
                        int[] amounts = new int[str.Length];
            
                        foreach (char letter in arrayInput)
                        {
                            for (int x = 0; x < alphabet.Length; x++)
                            {
                                if (letter.ToString() == alphabet[x].ToString())
                                {
                                    amounts[x]++;
                                }
                            }
                        }
                        int numToRemove = 0;
                        amounts = amounts.Where(val => val != numToRemove).ToArray();
                        foreach (int amount in amounts)
                        {
                            Console.Write(amount + ", ");
                        }
                        Console.ReadKey();
                    }
            

            【讨论】:

              猜你喜欢
              • 2013-11-04
              • 2011-06-01
              • 2016-06-27
              • 2016-01-01
              • 2021-08-04
              • 2020-07-23
              • 2016-01-23
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多