如何创建采用 char 或 string 的 map 或 unordered_map 模板?

问题描述 投票:0回答:2

我尝试编写将 .txt 文件作为参数的代码,读取所述文件,然后获取文本的所有字母(字符)和文本的所有单词(字符串),将它们中的每一个放入映射或无序映射中(由 pair 组成,对于每个字符/字符串,它出现的附加数字),按频率将它们排序为一个向量并打印该向量。当我将 chars 和 strings 的计数函数和 print 函数分开时,一切顺利,但现在我想将 print-and-sort 函数编写为模板,这样我就可以只对字符串和 chars 使用一个函数.主要问题似乎是我无法在以下代码中获得两种地图以适应打印和排序频率:

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

    std::string sanitize_word(const std::string& word)
    {
    //Whatever this does
    }

    bool compare(const auto& a, const auto& b)
    {
       //whatever this does        }


    std::unordered_map<char,int> get_letterfrequencies(const std::string& datei)
    {//this works, get_wordfrequencies is pretty much the same, i just had to shorten it
    }

    std::unordered_map<std::string,int> get_wordfrequencies(const std::string& datei)
    {
        std::unordered_map<std::string, int> frequencies;
        std::ifstream eingabe(datei);

        if (!eingabe.is_open())
        {//irrelevant
        }
        std::string word;

        while (eingabe >> word)
        {
            std::string sanitized_word = sanitize_word(word);

            if (sanitized_word.size() > 0)
            {
                if (frequencies.count(sanitized_word))
                {
                    frequencies[sanitized_word]++;
                }
                else
                {
                    frequencies[sanitized_word] = 1;
                }
            }
            else
            {//irrelevant                                   
            }           
        }
        eingabe.close();
        return frequencies;
    }  

    template <typename Map, typename typ>
    void print_frequencies_sorted(const Map& map)
    {   
        std::vector<typ, int> mapvector(map.begin(), map.end());

        //if (!std::is_same<typ, char>() && !std::is_same<typ, std::string>())
        std::sort(mapvector.begin(), mapvector.end(), compare);            
        long double gesamt = 0;

        for(const auto& p : mapvector)
        {
            gesamt += p.second;
            double Anteil = (p.second / gesamt) * 100;
            std::cout << "[ " << p.first << ": " << p.second << "x ; also " << Anteil << "% ]" << std::endl;
        }   
        std::cout <<  " " << std::endl;
    }
    int main(int argc, char* argv[])
    {
        print_frequencies_sorted(get_wordfrequencies(argv[1]));
  
        return 0;
    }

我尝试将 print_frequencies_sorted 函数移动到一个单独的函数中,该函数只执行这两行,但它没有用,我尝试给它另一个映射,但我得到“错误:没有匹配的函数调用'print_frequencies_sorted(std :: map&)' " - 每次我尝试编译时出错,只有不同的类型。错误在主函数中, 基本上我不知道如何使打印功能与它收到的地图相匹配。 即使没有地图模板,每次直接交一张参考地图,也没用。

c++ templates g++ c++20 stdmap
2个回答
0
投票

std::vector
的第二个模板参数是分配器。

使用

std::vector<...> mapvector(map.begin(), map.end());
初始化vector时,需要使用
map.begin()
返回的迭代器的值类型。

这个问题虽然是地图的元素有一个 const 限定的键(例如

std::pair<const std::string, int>
),这阻止了
std::sort
.

的重新分配

出于这个原因,您需要使用指向元素的指针(或者使用迭代器)。当然你需要一个合适的比较函数。您的版本将无法运行,因此请引入合适的模板或使用 lambda。

std::unordered_map<char, int> get_letterfrequencies()
{
    // simplified logic here...
    return { {'a', 7}, {'b', 3}, {'c', 4} };
}

std::unordered_map<std::string, int> get_wordfrequencies()
{
    // simplified logic here...
    return {
            {"foo", 10},
            {"bar", 3},
            {"baz", 1},
    };
}

template <typename Map>
void print_frequencies_sorted(const Map& map)
{
    // introduce a type alias for the elements of the vector we sort
    using SortedVectorElement = typename std::iterator_traits<decltype(map.begin())>::pointer;

    // alternative relying on type value_type type alias of standard library maps
    //using SortedVectorElement = const typename Map::value_type*;

    std::vector<SortedVectorElement> mapvector;
    mapvector.resize(map.size()); // resize to the number of elements needed

    // convert the map entries elements to pointers to map entries
    std::transform(map.begin(), map.end(), mapvector.begin(), [](auto& entry) { return &entry; });

    // sort in by frequency in ascending order
    std::sort(mapvector.begin(), mapvector.end(), [](SortedVectorElement v1, SortedVectorElement v2)
        {
            return v1->second < v2->second;
        });

    long double gesamt = 0;

    for (auto p : mapvector) // no need to use references for pointers...
    {
        gesamt += p->second;
        double Anteil = (p->second / gesamt) * 100;
        std::cout << "[ " << p->first << ": " << p->second << "x ; also " << Anteil << "% ]" << std::endl;
    }
    std::cout << " " << std::endl;
}

int main(int argc, char* argv[])
{
    print_frequencies_sorted(get_wordfrequencies());
    print_frequencies_sorted(get_letterfrequencies());

    return 0;
}

注意:我没有修复的一件事是

gesamt
不包含频率总和,而是到目前为止的频率总和。在所有情况下,它都会为第一个条目显示 100%,并且只为最后一个条目产生所需的输出,至少根据我的假设。


0
投票

你可以用一种相当通用的方式解决你的问题。

如果您查看 std::unordered_mapstd::map 的定义,您可以读到,这两个映射都有成员类型“key_type”和“mapped_type”。

所以,如果你创建一个模板,以映射类型作为参数,你可以找出键类型和映射类型。有了它,您可以创建一个非常通用的函数。它会吃和映射,

std::unordered_map
std::map
具有许多键类型和许多整数值类型。因此,无论键是
char
还是
string
都没有关系,计数器也可以是
int
unsigned_int
。或
long
或其他什么。

因为我们要排序、赋值和求和,所以类型需要满足特殊的要求。我们可以使用 C++20 概念来确保我们获得正确的类型。但我不会在这里解决这个问题。

我们将通过以下方式实施解决方案

  1. 创建一个类型别名对(一个“typedef”),这样我们就可以节省一些打字工作。
  2. 定义这个“对”的
    std::vector
    并使用
    std::vector
    s范围constructor(第5号)将地图中的所有日期复制到
    std::vector
  3. 根据“对”的第二部分(计数器),按降序对向量进行排序。为此使用一个简单的 lambda
  4. 使用数字库中的
    std::accumulate
    计算所有计数的总和。使用依赖数据类型
  5. 使用基于范围的 for 循环和 结构化绑定 迭代向量中的所有条目并打印出结果。

生成的函数将非常紧凑且易于理解。请看:

#include <iostream>
#include <string>
#include <map>
#include <unordered_map>
#include <algorithm>
#include <utility>
#include <numeric>
#include <iomanip>

std::map<char, int> mci{ {'a',1},{'b',2},{'c',3} };
std::map<std::string, int> msi{ {"aa",4},{"bb",5},{"cc",6}};
std::unordered_map<char, int> umci{ {'d',10},{'e',20},{'f',30} };
std::unordered_map<std::string, int> umsi{ {"dd",40},{"ee",50},{"ff",60} };

template <typename MapType>
void print_frequencies_sorted(const MapType& anyMap) {

    // Get the type of the pair from template element
    using Pair = std::pair < MapType::key_type, MapType::mapped_type>;

    // Copy data into map using the vectors range constructor
    std::vector<Pair> data{ anyMap.begin(), anyMap.end() };

    // Sort, descending, by frequency
    std::sort(data.begin(), data.end(), [](const Pair& p1, const Pair& p2) {return p1.second == p2.second ? p1.first < p2.first : p1.second > p2.second; });

    // Build the sum of all frequencies
    typename MapType::mapped_type sum = std::accumulate(data.begin(), data.end(), 0, [](const MapType::mapped_type sum, const Pair& p) { return sum + p.second; });

    // Print result
    for (const auto& [item,count] : data)
        std::cout << "[ " << item << ":\t" << count << "x ;\t also " << (double)count / (double)sum * 100.0 << "% ]" << std::endl;
}
int main() {
    print_frequencies_sorted(mci);
    print_frequencies_sorted(msi);
    print_frequencies_sorted(umci);
    print_frequencies_sorted(umsi);
}
© www.soinside.com 2019 - 2024. All rights reserved.