【问题标题】:C++ - How do I frequency count characters?C++ - 我如何频率计数字符?
【发布时间】:2021-05-27 21:14:58
【问题描述】:

我需要编写代码来将唯一字符及其频率存储在动态数组中。随着新数据的进入,我需要增加它的大小。在这种情况下,新数据将是遇到的新字符。我想到的算法是每次从给定字符串中读取时检查已知字符列表。如果它是一个新字符,我需要将数组大小增加 1。如果它不是一个新字符,我将增加它的频率。它是一个结构字母数组(在下面的代码中)。问题是,我花了很多时间在这上面,并且在实现它时遇到了问题。所以问题是我如何才能准确地实现它?感谢您花时间提供帮助。

#include <iostream>
#include <string>
#include <bitset>
#define ARR_LEN(arr) sizeof(arr)/sizeof(arr[0])

using namespace std;

struct unique_char {
    char character;
    int frequency;
};

int main() {
    int char_count;
    string str;
    getline(cin, str);

    struct unique_char* chars = new struct unique_char[100];

    system("PAUSE");
    exit(0);
}

【问题讨论】:

  • 使用std::map&lt;char, int&gt;std::unordered_map&lt;char, int&gt;
  • 这能回答你的问题吗? stackoverflow.com/questions/64049909/…
  • @463035818_is_not_a_number 问题的标题有误,并且引用的欺骗与 OP 的实际要求不匹配。
  • 旁注:std::size,在 C++17 中添加,是 ARR_LEN 宏的优雅且安全的替代品。请注意,如果数组为 decayed to a pointer,则两者都不会起作用
  • @ThomasMatthews 另一方面,除非有需要,否则从长远来看,干净的语义比效率更有用。

标签: c++ arrays


【解决方案1】:

正如 cmets 中所提到的,使用 std::map 使这变得相当简单。

关于 map 的“有趣”之一是索引运算符“按需”创建新值,其初始值为 0 用于整数。所以实际代码本质上是一行:chars[c] += 1;

#include <map>
#include <iostream>
#include <string>

using namespace std;

int main() {
  map<char, int> chars;

  string str;
  getline(cin, str);

  for(char c: str) {
    chars[c] += 1;
  }

  for(auto [character, frequency]: chars) {
      cout << character << " : " << frequency << "\n";
  }
}

注意这与@ThomasMatthews 的回答有一个主要区别:

地图将仅包含已看到的字符,而数组将包含0s 用于所有从未被击中的字符。您使用哪种方法应该基于这两种方法中的哪一种对您更有用。

【讨论】:

  • 很好的解决方案。顺便说一句,如果您使用“地图”,那么在输出中,字符将自动排序并按字母顺序显示。但是,如果您使用“unordered_map”,那么在输出中,字符将不会按字母顺序排序和显示。此外,对于搜索操作,“map”和“unordered_map”的时间复杂度不同。
  • @Job_September_2020 我个人不会使用using namespace,但我想尽可能地保留 OP 代码,以便他们可以更轻松地阅读答案。 (我同意在这种情况下,std:: 应该从 cout 中删除,这只是一种反射)。
  • @Job_September_2020 for (auto [character, frequency]: chars) 由于使用了结构化绑定,因此需要 C++17。对于 C++11 和 C++14,可以改用for (auto &amp;p: chars) { std::cout &lt;&lt; p.first &lt;&lt; " : " &lt;&lt; p.second &lt;&lt; "\n"; }
  • @Job_September_2020 地图在这些场景中使用value initialization(此页面涵盖了您的两个问题),如您所见here
  • @Job_September_2020 为了澄清你的第二个问题,从语言的 PoV 来看,未初始化变量的值不是“0”或“有时不是 0”。值根本不存在,试图读取它是未定义的行为。
【解决方案2】:

使用数组让事情变得简单:

unsigned int frequencies[256] = {0};
while (std::getline(std::cin, str))
{
   const size_t length = str.length();
   for (unsigned int i = 0; i < length; ++i)
   {
       const char c = str[i];
       ++frequencies[c];
   }
}

虽然,您可能希望提高效率:

const size_t BUFFER_SIZE = 1024u * 1024u;
//...
char buffer[BUFFER_SIZE] = {0};
while (std::cin.read(&buffer[0], BUFFER_SIZE)
{
    const size_t chars_read = cin.gcount();
    for (unsigned int i = 0; i < chars_read; ++i)
    {
         const char c = buffer[i];
         ++frequencies[c];
    }
}

以上代码使用块读取来提高输入性能。不扫描换行符,直接读入内存。根据内存中的字符确定频率。

编辑 1:unsigned char
从 cmets 来看,unsigned char 可能是比char 更安全的数据类型,因为char 可以签名。这在访问数组槽时可能是个问题,因为signed char 可能是负数,而负数索引通常是一件坏事。运行时,如果有问题,请将char类型替换为unsigned char

【讨论】:

  • 也许使用unsigned char
  • @doug:const c 应该是 const char c。编译器会优化掉临时赋值并与其他语句结合。
  • 同意@Dmitri char 是危险的,除非知道系统将其实现为无符号。 c char,如果它像 x86 中那样签名,则设置了高位位将访问数组外部。充其量是UB。
猜你喜欢
  • 1970-01-01
  • 2017-04-19
  • 2011-12-11
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 2017-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多