【问题标题】:Can I reorder elements in map by value? C++我可以按值重新排序地图中的元素吗? C++
【发布时间】:2021-11-14 00:51:12
【问题描述】:

我想做一个字母频率表,就像this

所以,我有变量str,其中包含一些文本,我有map<char, int> m,其中包含每个字母的出现次数。

所以我有类似的东西:

vector<char> get_rank(const string& str)
{
    map<char, int> m;

    for (char i = 'a'; i <= 'z'; i++)
        m[i] = 0;

    for (char ch : str)
        m[ch]++;

    for (auto& it : m)
        v.push_back(it.first);
    return v;
}

但是 map 是按键排序的,而不是按值排序的。有没有办法“重载” map 的比较器以按值对每个元素进行排序?如果答案是否定的,我怎样才能以同样的优雅来实现相同的想法。因为我的每一个想法都显得有些枯燥和粗糙。

【问题讨论】:

  • 你所要求的毫无意义,首先std::maporders/finds 元素通过使用不可变的键,而值可以更改,这意味着每次更改值都需要整个std::map 被重新平衡。比较器也只比较键,它对值一无所知。也许您只是想使用std::vector 并在需要时对其进行排序?
  • 不,您不能按值对地图进行排序。它是按键排序的。为什么不将值设为键,然后按照您的意愿对其进行排序,不是吗?
  • @463035818_is_not_a_number 我想过这个想法。但它不会因为碰撞而工作。想象一下,如果在我的str 中没有字符ab,因此m[0] = am[0] = b 同时出现。
  • 当您“重新排序”地图时,您认为如何解决冲突?
  • 请在下面查看我的更新答案。它展示了许多关于如何实现目标的不同解决方案。

标签: c++ dictionary stl comparator


【解决方案1】:

无法根据映射值对映射进行排序。地图元素的顺序由地图根据其仅考虑键的比较器进行管理。

但是,当键是连续的且范围有限时,您不需要映射来计算频率。

使用std::vector&lt;letter_and_count&gt; 代替地图

struct letter_and_count {
     char letter;
     int count = 0;
};

letter 初始化为 a 直到 z 的元素初始化向量,然后在字符串中遇到字母 i 时增加 v[ i - 'a']。然后使用std::sort根据count成员对向量进行排序。

PS:请注意下面的 cmets。 az 之间的字母不一定是连续的。但是(也来自 eerorika 的想法)您可以使用一个简单的查找表:

std::string alphabet{"abcdefghijklmnopqrstuvwxyz"};

然后首先找到那个alphabet中的字母,并使用它在字母表中的索引进行计数。

【讨论】:

  • 请注意,C++ 标准不保证字符编码连续映射 a-z,因此i - 'a' 在这种情况下不起作用。在这种情况下存在旧编码 (EBCDIC)。
  • @eerorika 嗯。然后我记错了。我虽然保证它们是连续的。您知道检查它们是否存在的简单方法吗?
  • 这是个好主意。我也想过。但是我的母语有一个问题。我的语言(俄语)中的所有字母在 ASCII 表中都不连续。从aя 代码是从-32 到-1,但我的语言中有33 个字母。那是一个字母ё,代码为-72。我怀疑这是因为字母ё 出现在不同的语言中。但是我该如何解决这个问题呢?
  • @RussianStranger 你应该在问题中提到这些事情。实际上,我的回答只做出与您的问题相同的假设,for (char i = 'a'; i &lt;= 'z'; i++) 假设它们是连续的
  • @4 一个循环可以检查它。一个可移植的解决方案是从"abcdefghijklmnopqrstuvwxyz" 生成查找表。
【解决方案2】:

有没有办法“重载” map 的比较器以按值对每个元素进行排序?

没有。

如果答案是否定的,我怎样才能以同样的优雅实现同样的想法。

取决于用例。该示例的一个优雅解决方案是对向量进行排序。

【讨论】:

    【解决方案3】:

    与其尝试使用显然不是为此而设计的 STL 容器(订购地图),不如使用不同的容器。

    您始终可以使用std::pair&lt;char, int&gt; 作为std::vector 的类型。

    这些可以排序。

    我更喜欢 std::vector&lt;std::pair&lt;char,int&gt;&gt; 只是因为我更习惯于使用向量,但这取决于你。

    【讨论】:

    • std::ordered_set ?您不能重新排列 set 中的元素顺序
    • Whoopsie... 懒得检查参考。我现在已经纠正了我的错误 :) 感谢您的提醒!
    【解决方案4】:

    我发现使用专用容器的方法非常好。

    要计算 std::map 或在您的情况下应使用 std::unordered_map。之后,可以将其复制到std::vector 并根据需要进行排序。示例:

    #include <iostream>
    #include <string>
    #include <utility>
    #include <vector>
    #include <unordered_map>
    #include <algorithm>
    
    // Function to get the rank of letters in a string
    std::vector<std::pair<char,int>>  getRank(const std::string& str) {
    
        // Count letters
        std::unordered_map<char, int> counter;
        for (const char c : str) counter[c]++;
    
        // Copy to vector and sort
        std::vector<std::pair<char, int>> rank(counter.begin(), counter.end());
        std::sort(rank.begin(), rank.end(), [](const std::pair<char, int>& p1, const std::pair<char, int>& p2) { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; });
    
        return rank;
    }
    
    // Test / Driver Code
    int main() {
    
        std::string testString{ "122333444455555666666777777788888888" };
    
        for (const auto& [letter, count] : getRank(testString))
            std::cout << letter << ' ' << count << '\n';
    }
    

    这将以一种简单的方式工作。

    使用std::multiset 会使代码更加紧凑,因为它会自动为您排序值:

    #include <iostream>
    #include <string>
    #include <utility>
    #include <set>
    #include <unordered_map>
    #include <type_traits>
    
    // ------------------------------------------------------------
    // Create aliases. Save typing work and make code more readable
    using Pair = std::pair<const char, unsigned int>;
    
    // Standard approach for counter
    using Counter = std::unordered_map<std::remove_const<Pair::first_type>::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>;
    // ------------------------------------------------------------
    
    // Function to get the rank of letters in a string
    Rank getRank(const std::string& str) {
    
        Counter counter;
        for (const char c : str) counter[c]++;
    
        return { counter.begin(), counter.end() };
    }
    
    // Test / Driver Code
    int main() {
    
        std::string testString{ "122333444455555666666777777788888888" };
    
        for (const auto& [letter, count] : getRank(testString)) 
            std::cout << letter << ' ' << count << '\n';
    }
    

    也可以使用最大堆找到最常用的元素。

    或者,在下面的示例中,我们可以获得 x 最上面的元素。这只需几行代码。下面将根据值计算几乎所有的东西并排序:

    #include <iostream>
    #include <utility>
    #include <unordered_map>
    #include <algorithm>
    #include <vector>
    #include <iterator>
    #include <type_traits>
    
    // Helper for type trait We want to identify an iterable container ----------------------------------------------------
    template <typename Container>
    auto isIterableHelper(int) -> decltype (
        std::begin(std::declval<Container&>()) != std::end(std::declval<Container&>()),     // begin/end and operator !=
        ++std::declval<decltype(std::begin(std::declval<Container&>()))&>(),                // operator ++
        void(*std::begin(std::declval<Container&>())),                                      // operator*
        void(),                                                                             // Handle potential operator ,
        std::true_type{});
    template <typename T>
    std::false_type isIterableHelper(...);
    
    // The type trait -----------------------------------------------------------------------------------------------------
    template <typename Container>
    using is_iterable = decltype(isIterableHelper<Container>(0));
    
    // Some Alias names for later easier reading --------------------------------------------------------------------------
    template <typename Container>
    using ValueType = std::decay_t<decltype(*std::begin(std::declval<Container&>()))>;
    template <typename Container>
    using Pair = std::pair<ValueType<Container>, size_t>;
    template <typename Container>
    using Counter = std::unordered_map<ValueType<Container>, size_t>;
    
    // Function to get the k most frequent elements used  in any Container ------------------------------------------------
    template <class Container>
    auto topKFrequent(const Container& data, size_t k) {
    
        if constexpr (is_iterable<Container>::value) {
    
            // Count all occurences of data
            Counter<Container> counter{};
            for (const auto& d : data) counter[d]++;
    
            // For storing the top k
            std::vector<Pair<Container>> top(k);
    
            // Get top k
            std::partial_sort_copy(counter.begin(), counter.end(), top.begin(), top.end(),
                [](const std::pair<int, size_t >& p1, const std::pair<int, size_t>& p2) { return p1.second > p2.second; });
    
            return top;
        }
        else
            return data;
    }
    int main() {
        std::vector testVector{ 1,2,2,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,6,7 };
        for (const auto& p : topKFrequent(testVector, 2)) std::cout << "Value: " << p.first << " \t Count: " << p.second << '\n';
        std::cout << '\n';
    
        double cStyleArray[] = { 1.1, 2.2, 2.2, 3.3, 3.3, 3.3 };
        for (const auto& p : topKFrequent(cStyleArray, 2)) std::cout << "Value: " << p.first << " \t Count: " << p.second << '\n';
        std::cout << '\n';
    
        std::string s{ "abbcccddddeeeeeffffffggggggg" };
        for (const auto& p : topKFrequent(s, 2)) std::cout << "Value: " << p.first << " \t Count: " << p.second << '\n';
        std::cout << '\n';
    
        double value = 12.34;
        std::cout << topKFrequent(value, 2) << "\n";
    
        return 0;
    }
    

    以上代码适用于C++17


    下面是一个在任何容器中查找最频繁元素的示例。

    但这是一个 C++20 解决方案:

    #include <iostream>
    #include <utility>
    #include <unordered_map>
    #include <queue>
    #include <vector>
    #include <iterator>
    #include <type_traits>
    #include <string>
    
    // Some Alias names for later easier reading --------------------------------------------------------------------------
    template <typename Container>
    using ValueType = std::ranges::range_value_t<Container>;
    
    template <typename Container>
    using Pair = std::pair<ValueType<Container>, size_t>;
    
    template <typename Container>
    using Counter = std::unordered_map<ValueType<Container>, size_t>;
    
    template <typename Container>
    using UnderlyingContainer = std::vector<Pair<Container>>;
    
    // Predicate Functor
    template <class Container> struct LessForSecondOfPair {
        bool operator ()(const Pair<Container>& p1, const Pair<Container>& p2) { return p1.second < p2.second; }
    };
    
    template <typename Container>
    using MaxHeap = std::priority_queue<Pair<Container>, UnderlyingContainer<Container>, LessForSecondOfPair<Container>>;
    
    // Calculate max element ---------------------------------------------------------------------------------------------
    template <class Container>
    auto topFrequent(const Container& data) {
    
        if constexpr (std::ranges::range<Container>) {
            // Count all occurences of data
            Counter<Container> counter{};
            for (const auto& d : data) counter[d]++;
    
            // Build a Max-Heap
            MaxHeap<Container> maxHeap(counter.begin(), counter.end());
    
            // Return most frequent number
            return maxHeap.top().first;
        }
        else
            return data;
    }
    
    // Test
    int main() {
        std::vector testVector{ 1,2,2,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,6,7 };
        std::cout << "Most frequent is: " << topFrequent(testVector) << "\n";
    
        double cStyleArray[] = { 1.1, 2.2, 2.2, 3.3, 3.3, 3.3 };
        std::cout << "Most frequent is: " << topFrequent(cStyleArray) << "\n";
    
        std::string s{ "abbcccddddeeeeeffffffggggggg" };
        std::cout << "Most frequent is: " << topFrequent(s) << "\n";
    
        double value = 12.34;
        std::cout << "Most frequent is: " << topFrequent(value) << "\n";
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-29
      • 1970-01-01
      • 2019-12-05
      • 2018-07-08
      • 1970-01-01
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多