【问题标题】:How to display k least and most elements of a map?如何显示地图的 k 最少和最多元素?
【发布时间】:2021-12-16 15:34:58
【问题描述】:

假设我有一个包含字符串和整数的地图。将文本文件读入其中后,字符串包含所有不同的单词,整数包含每个单词的频率。我想在这个文本文件中显示 k 个最常见/最不常见的单词,但我的地图是通过字符串而不是整数排序的。这是我的一些代码:

typedef map<string, int> words;
ifstream file("input.txt");
string word;

while (file >> word)
{
    words[word]++; 
}

for (WordMap::iterator w = words.begin(); w != words.end(); w++)
{
    cout << w->first << " = " << w->second << endl;
}

此代码正在获取单词列表及其所有频率;但是我的输出看起来像这样:

apple = 34 
cat = 4
dog = 12
...

如何在这张地图中打印 k 个最频繁/最不频繁的单词?是因为我有一个字符串作为第一个成员,一个整数作为第二个成员吗?如果我必须颠倒这两个的位置,那么我将如何为每个字符串添加频率计数?

【问题讨论】:

  • 您不能根据任意标准“对地图进行排序”。根据规范,地图按其键排序。最简单的方法可能是将每个计数和单词作为pair&lt;int, string&gt; 值添加到std::vector 等容器中并对其进行排序,然后检查第一个/最后一个k 元素。
  • 不要调查那个。请参阅我的评论。
  • @paddy 好吧我会检查你的建议。谢谢。
  • @paddy 哦,对不起。我打算把所有这些都放到std::vector

标签: c++ dictionary


【解决方案1】:

您可以创建一个比较函数,使用容器传递所有地图的值并对其进行排序,然后比较最后一个 k 元素。

bool compare (std::pair<std::string, int>& lhs, std::pair<std::string, int>& rhs)
{
    return lhs.second < rhs.second;
}

int main()
{
    std::vector<std::pair<std::string, int>> vec;

    for(const auto& pr: words)
        vec.push_back(make_pair(pr.first, pr.second));

    std::sort(vec.begin(), vec.end(), compare);
    
   for(const auto& pr : vec)
   {
       // print all the values out
       std::cout << pr.first << ' ' << pr.second << '\n';
   }
}

【讨论】:

  • 定义自定义谓词的不必要的复杂性,当您可以使用std::pair 使用std::pair&lt;int, std::string&gt; 的默认谓词时。
  • @justANewbie 用于打印地图,我看到的所有代码都打印了整个内容,有没有办法打印它的前 10 个元素?
  • @yepp 是的,你可以。它只是涉及一些迭代器
【解决方案2】:

有一种或多或少的标准方法可以计算容器中的某物,然后获取并显示其排名。

对于计数部分,我们可以使用关联容器,例如 std::mapstd::unordered_map。在这里,我们将一个“键”(在本例中为单词)与一个计数与一个值(在本例中为特定单词的计数)相关联。

幸运的是,选择这种方法的基本原因是地图有一个非常好的索引operator[]。这将查找给定的键,如果找到,则返回对该值的引用。如果没有找到,那么它将使用密钥创建一个新条目并返回对新条目的引用。因此,在这两种情况下,我们都会获得对用于计数的值的引用。然后我们可以简单地写:

std::unordered_map<char,int> counter{};
counter[word]++;

这看起来非常直观。

经过这个操作,你已经有了频率表。按键(单词)排序,使用std::map 或未排序,但使用std::unordered_map 可以更快地访问。

在您的情况下,如果您只对排序计数感兴趣,建议使用std::unordered_map,因为无需按其键对std::map 中的数据进行排序,以后不再使用此排序。

然后,您想根据频率/计数进行排序。不幸的是,这在地图上是不可能的。因为 ma​​p - 容器系列的主要属性是它们对 key 的引用,而不是值或计数。

因此,我们需要使用第二个容器,例如 std::vector 等,然后我们可以使用 std::sort 对任何给定的谓词进行排序,或者,我们可以将值复制到容器中,例如 @987654332 @ 隐含地对其元素进行排序。而且因为这只是一个班轮,所以这是推荐的解决方案。

此外,由于为 std 容器编写了所有这些长名称,我们使用 using 关键字创建别名。

在我们得到单词的排名之后,按照计数排序的单词列表,我们可以使用迭代器和循环来访问数据并输出它们。

我们需要注意带有排序值的容器包含足够的值,然后使用简单的 for 循环,或者像 std::copy_n 这样的标准算法。

热限制值?有 3 种可能的解决方案。

  • 针对if (elementsToPrint &gt; container.size()) elementsToPrint = container.size()大小的手写比较功能
  • std::min,它基本上完成了上述工作。所以,elementsToPrint = std::min(elementsToPrint, container.size());
  • std::clamp。为此目的定义的专用函数。请阅读here 了解此函数。结果将是:````elementsToPrint = std::clamp(elementsToPrint,1, container.size());

很好。现在,我们要显示最低的 k 等级和最高的 k 等级。我们可以通过使用指向开头的迭代器来做到这一点,例如begin(),或者,谈论结尾,std::rbegin()

在这一切之后,我们现在编写超紧凑的代码,只需几行代码即可完成任务:

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <utility>
#include <set>
#include <unordered_map>
#include <type_traits>
#include <iomanip>
#include <algorithm>

// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<std::string, unsigned int>;

// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;

// Sorted values will be stored in a multiset
struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; } };
using Rank = std::multiset<Pair, Comp>;
// ------------------------------------------------------------

int main() {

    // Open file and check, if it can be opened.
    if (std::ifstream inputFileStream{ "input.txt" }; inputFileStream) {

        // Definition of our counter
        Counter counter;


        // Count -------------------------
        for (std::string word{}; inputFileStream >> word; counter[word]++);

        // Sort
        Rank rank(counter.begin(), counter.end());


        // Output ------------------------
        size_t elementsToShow = 3;

        // Show 3 biggest counts
        auto fromTop = rank.begin();
        for (size_t i{}; i < std::clamp(elementsToShow, 1u, rank.size()); ++i)
            std::cout << fromTop->first << '\t' << fromTop++->second << '\n';

        // Show 3 smallest counts
        auto fromBottom = rank.rbegin();
        for (size_t i{}; i < std::clamp(elementsToShow, 1u, rank.size()); ++i)
            std::cout << fromBottom->first << '\t' << fromBottom++->second << '\n';
    }
    else std::cerr << "\n*** Error. Could not open input file\n\n";
}

通常情况下,您需要继续使用收集的 count 值。那么建议使用std::partial_sort_copy。基本上,我们此时会将所有计数放入结果向量中。即topbottom

排序标准也可以在复制函数的 lambda 中调整。

请参阅下面的一种可能的解决方案:

#include <iostream>
#include <fstream>
#include <string>
#include <utility>
#include <unordered_map>
#include <type_traits>
#include <algorithm>
#include <vector>

// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<std::string, unsigned int>;

// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;

// Resulting data
using Result = std::vector<Pair>;
// ------------------------------------------------------------

int main() {

    // Open file and check, if it can be opened.
    if (std::ifstream inputFileStream{ "input.txt" }; inputFileStream) {

        // Definition of our counter
        Counter counter;

        // Count -------------------------
        for (std::string word{}; inputFileStream >> word; counter[word]++);

        // Build result ------------------------
        unsigned int three = std::min(3u, counter.size());

        Result top(three);
        Result bottom(three);

        // Get 3 biggest counts
        std::partial_sort_copy(counter.begin(), counter.end(), top.begin(), top.end(),
            [](const Pair& p1, const Pair& p2) { return p1.second > p2.second; });
        
        // get 3 smallest counts
        std::partial_sort_copy(std::next(counter.begin(), counter.size() - three), counter.end(), bottom.begin(), bottom.end(),
            [](const Pair& p1, const Pair& p2) { return p1.second > p2.second; });

        // Output  ------------------------
        for (const auto& [word, count] : top) std::cout << word << '\t' << count << '\n';
        for (const auto& [word, count] : bottom) std::cout << word << '\t' << count << '\n';
    }
    else std::cerr << "\n*** Error. Could not open input file\n\n";
}

【讨论】:

    猜你喜欢
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多