【问题标题】:Sorting std::map using value使用值对 std::map 进行排序
【发布时间】:2018-09-05 19:15:40
【问题描述】:

我需要按值而不是按键对std::map 进行排序。有什么简单的方法吗?

我从以下线程中得到了一个解决方案:
std::map sort by data?
有更好的解决方案吗?

map<long, double> testMap;
// some code to generate the values in the map.

sort(testMap.begin(), testMap.end());  // is there any function like this to sort the map?

【问题讨论】:

  • 为什么需要这样排序地图?您是想缩短查找时间,还是想以某种方式遍历它?
  • 你可以用值切换键。
  • STL map--> sort by value?的可能重复

标签: c++ dictionary std


【解决方案1】:

只需将值放入向量中,然后根据每个映射键的值对向量进行排序。

#include <bits/stdc++.h>
using namespace std;

int main()
{

    std::map<std::string, int> mymap;
    mymap.insert(std::make_pair("earth", 5));
    mymap.insert(std::make_pair("moon", 33));
    mymap.insert(std::make_pair("sun", 2));

    vector<std::pair<std::string, int>> myvector {mymap.begin(), mymap.end()};


    sort(myvector.begin(), myvector.end(), [](std::pair<std::string, int> l, std::pair<std::string, int> r)
    {
        return l.second < r.second;
    });
  
    return 0;
}

【讨论】:

    【解决方案2】:

    在不进行任何额外复制或转换的情况下对std::map 进行排序的另一种方法是使用不同的Compare 类型重新定义std::map:

    namespace nonstd {
    template <class Key,
              class T,
              class Compare = std::greater<T>,
              class Allocator = std::allocator<std::pair<Key const, T>>
              >
    using map = std::map<Key, T, Compare, Allocator>;
    }
    
    int main() {
      nonstd::map<char, std::size_t> const values = {
        {'A', 3}, {'B', 2}, {'C', 5}
      };
    
      for (auto const& value : values) {
        std::clog << value.first << " : " << value.second << std::endl;
      }
    }
    

    【讨论】:

    • 这似乎对我不起作用。我看到以下输出:C : 5 B : 2 A : 3 我希望解决方案会产生以下输出:C : 5 A : 3 B : 2
    【解决方案3】:

    另一种解决方案是使用 std::make_move_iterator 构建新向量 (C++11)

        int main(){
    
          std::map<std::string, int> map;
           //Populate map
    
          std::vector<std::pair<std::string, int>> v {std::make_move_iterator(begin(map)),
                                          std::make_move_iterator(end(map))};
           // Create a vector with the map parameters
    
           sort(begin(v), end(v),
                 [](auto p1, auto p2){return p1.second > p2.second;});
           // Using sort + lambda function to return an ordered vector 
           // in respect to the int value that is now the 2nd parameter 
           // of our newly created vector v
      }
    

    【讨论】:

      【解决方案4】:

      在这种情况下,我们应该将地图转换为多地图。我认为将地图转换为集合并不好,因为如果原始地图中有很多重复值,我们会丢失很多信息。这是我的解决方案,我定义了按值排序的小于比较器(cmp 函数)。我们可以根据需要自定义 cmp 功能。

      std::map<int, double> testMap = { {1,9.1}, {2, 8.0}, {3, 7.0}, {4,10.5} };
      auto cmp = [](const double &lhs,
                    const double &rhs)->bool
      {
          return lhs < rhs;
      };
      std::multimap<double, int, decltype(cmp)> mmap(cmp);
      for (auto item : testMap)
          mmap.insert(make_pair(item.second, item.first));
      

      【讨论】:

        【解决方案5】:

        要使用多图构建 Oli 的解决方案 (https://stackoverflow.com/a/5056797/2472351),您可以将他使用的两个模板函数替换为以下内容:

        template <typename A, typename B>
        multimap<B, A> flip_map(map<A,B> & src) {
        
            multimap<B,A> dst;
        
            for(map<A, B>::const_iterator it = src.begin(); it != src.end(); ++it)
                dst.insert(pair<B, A>(it -> second, it -> first));
        
            return dst;
        }
        

        这是一个示例程序,显示了执行翻转后保留的所有键值对。

        #include <iostream>
        #include <map>
        #include <string>
        #include <algorithm>
        
        using namespace std;
        
        template <typename A, typename B>
        multimap<B, A> flip_map(map<A,B> & src) {
        
            multimap<B,A> dst;
        
            for(typename map<A, B>::const_iterator it = src.begin(); it != src.end(); ++it)
                dst.insert(pair<B, A>(it -> second, it -> first));
        
            return dst;
        }
        
        int main() {
        
            map<string, int> test;
            test["word"] = 1;
            test["spark"] = 15;
            test["the"] = 2;
            test["mail"] = 3;
            test["info"] = 3;
            test["sandwich"] = 15;
        
            cout << "Contents of original map:\n" << endl;
            for(map<string, int>::const_iterator it = test.begin(); it != test.end(); ++it)
                cout << it -> first << " " << it -> second << endl; 
        
            multimap<int, string> reverseTest = flip_map(test);
        
            cout << "\nContents of flipped map in descending order:\n" << endl;
            for(multimap<int, string>::const_reverse_iterator it = reverseTest.rbegin(); it != reverseTest.rend(); ++it)
                cout << it -> first << " " << it -> second << endl; 
        
            cout << endl;
        }
        

        结果:

        【讨论】:

        • 便携式解决方案 - 也适用于较旧的 vs。代码中的指纹也更小。
        【解决方案6】:

        我需要类似的东西,但翻转的地图不适合我。我只是将我的地图(下面的频率)复制到一个成对的向量中,然后按照我想要的方式对这些对进行排序。

        std::vector<std::pair<int, int>> pairs;
        for (auto itr = freq.begin(); itr != freq.end(); ++itr)
            pairs.push_back(*itr);
        
        sort(pairs.begin(), pairs.end(), [=](std::pair<int, int>& a, std::pair<int, int>& b)
        {
            return a.second < b.second;
        }
        );
        

        【讨论】:

        • [=] 部分是什么意思
        • @TannerSummers 它是 lambda 表达式的一部分。 en.cppreference.com/w/cpp/language/lambda
        • 这是一个绝妙的答案。通过为所需的排序逻辑传入具有适当 operator() 重载的 Function 对象,可以使排序函数更加模块化。不过很好地使用了 lambda 逻辑。
        • 我正在使用此代码,它在 gcc 4.9.x 中编译良好,但在 4.8.x 上无法编译 - 但是,在 lambda 的两个参数前指定 const 可以修复它。这可能是 gcc 4.8.x 的错误,还是它们使 gcc 4.9.x 比标准要求更宽松?
        • @Keiji 好问题。在 C++ 0x(现在是 11)中添加了 Lambda 支持。如果添加 C++ 11 标志,它应该可以工作。 stackoverflow.com/questions/16886591/how-do-i-enable-c11-in-gcc
        【解决方案7】:

        即使已经发布了正确的答案,我还是想添加一个演示,说明如何干净利落地做到这一点:

        template<typename A, typename B>
        std::pair<B,A> flip_pair(const std::pair<A,B> &p)
        {
            return std::pair<B,A>(p.second, p.first);
        }
        
        template<typename A, typename B>
        std::multimap<B,A> flip_map(const std::map<A,B> &src)
        {
            std::multimap<B,A> dst;
            std::transform(src.begin(), src.end(), std::inserter(dst, dst.begin()), 
                           flip_pair<A,B>);
            return dst;
        }
        
        int main(void)
        {
            std::map<int, double> src;
        
            ...    
        
            std::multimap<double, int> dst = flip_map(src);
            // dst is now sorted by what used to be the value in src!
        }
        

        通用关联源(需要 C++11)

        如果您将 std::map 的替代项用于源关联容器(例如 std::unordered_map),则可以编写单独的重载,但最终操作仍然相同,因此是广义关联容器使用可变参数模板可用于 either 映射构造:

        // flips an associative container of A,B pairs to B,A pairs
        template<typename A, typename B, template<class,class,class...> class M, class... Args>
        std::multimap<B,A> flip_map(const M<A,B,Args...> &src)
        {
            std::multimap<B,A> dst;
            std::transform(src.begin(), src.end(),
                           std::inserter(dst, dst.begin()),
                           flip_pair<A,B>);
            return dst;
        }
        

        这对于作为翻转源的std::map 和std::unordered_map 都有效。

        【讨论】:

        • 感谢示例代码。它帮助我概括了解决方案。现在我可以轻松地翻转任何地图了。
        • +1 用于清洁解决方案。但是,如果目标映射是多映射会更好,以避免与相同的“值”键发生冲突。
        • 如果多个值具有相同的值,则此解决方案不起作用,因为反向映射不能有两个具有相同键的对。对于原始地图中的独特价值,此解决方案有效!
        • 编辑了这个,所以结果是一个多图,正如theosem所建议的那样。
        • 添加了附录以包括对通用关联容器的支持,而不仅仅是 std::map。不错的答案,顺便说一句,奥利弗。
        【解决方案8】:

        翻转结构可能不再是映射,而是多映射,因此在上面的翻转映射示例中,并非 B 中的所有元素都必然出现在结果数据结构中。

        【讨论】:

          【解决方案9】:

          在下面的示例代码中,我编写了一个简单的方法来输出 word_map 映射中的顶部单词,其中键是字符串(单词),值是 unsigned int(单词出现)。

          思路很简单,找到当前最上面的词,然后从地图中删除。没有优化,但是在地图不大的情况下效果很好,我们只需要输出前N个单词,而不是对整个地图进行排序。

          const int NUMBER_OF_TOP_WORDS = 300;
          for (int i = 1; i <= NUMBER_OF_TOP_WORDS; i++) {
            if (word_map.empty())
              break;
            // Go through the map and find the max item.
            int max_value = 0;
            string max_word = "";
            for (const auto& kv : word_map) {
              if (kv.second > max_value) {
                max_value = kv.second;
                max_word = kv.first;
              }
            }
            // Erase this entry and print.
            word_map.erase(max_word);
            cout << "Top:" << i << " Count:" << max_value << " Word:<" << max_word << ">" <<     endl;
          }
          

          【讨论】:

            【解决方案10】:

            我喜欢 Oli 的回答(翻转地图),但似乎有一个问题:容器地图不允许两个元素具有相同的键。

            一种解决方案是使 dst 类型为 multimap。另一种是将 src 转储到向量中并对向量进行排序。前者需要对 Oli 的回答稍作修改,后者可以简洁地使用 STL 拷贝实现

            #include <iostream>
            #include <utility>
            #include <map>
            #include <vector>
            #include <algorithm>
            
            using namespace std;
            
            int main() {
              map<int, int> m;
              m[11] = 1;
              m[22] = 2;
              m[33] = 3;
            
              vector<pair<int, int> > v;
              copy(m.begin(),
                   m.end(),
                   back_inserter<vector<pair<int, int> > >(v));
            
              for (size_t i = 0; i < v.size(); ++i) {
                cout << v[i].first << " , " << v[i].second << "\n";
              }
            
              return 0;
            };
            

            【讨论】:

              【解决方案11】:

              你可以考虑使用 boost::bimap,它可能会给你一种 map 是同时按 key 和 value 排序的感觉(但这并不是真正发生的)

              【讨论】:

                【解决方案12】:

                您不能以这种方式对std::map 进行排序,因为映射中的条目是按键排序的。如果要按值排序,则需要使用交换的键和值创建一个新的std::map。

                map<long, double> testMap;
                map<double, long> testMap2;
                
                // Insert values from testMap to testMap2
                // The values in testMap2 are sorted by the double value
                

                记住,testMap2 中的双键必须是唯一的,或者使用 std::multimap。

                【讨论】:

                • 谢谢。但我知道这个解决方案。除了这个还有其他解决办法吗?
                【解决方案13】:

                按值排序的std::map 本质上是std::set。到目前为止,最简单的方法是将映射中的所有条目复制到一个集合中(取自here)

                template <typename M, typename S> 
                void MapToSet( const  M & m, S & s )
                {
                    typename M::const_iterator end = m.end();
                    for( typename M::const_iterator it = m.begin(); it != end ; ++it )
                    {
                        s.insert( it->second );
                    }
                }
                

                一个警告:如果映射包含具有相同值的不同键,它们将不会插入到集合中并丢失。

                【讨论】:

                  【解决方案14】:

                  如果要按排序顺序显示映射中的值,则将映射中的值复制到向量并对向量进行排序。

                  【讨论】:

                  • +1:这个(或其变体,例如创建“反向”地图)是正确答案。
                  • 如果我这样做。然后我松开键和值之间的关系。比如说,我有 map id2Score。它包含所有 id(其中 id 不是 0..n,它可能像 1、5、13 等)和分数。那么如果我创建一个带有分数的向量并排序,那么我将丢失哪个 id 与哪个分数相关联的信息。
                  • @user619237:不是真的。您可以对向量进行排序。获取最大值或最小值或您需要的任何值。然后遍历原始地图并在地图的值中寻找与该值的匹配(“it->second”。)
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-12-24
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-10-29
                  • 2018-10-01
                  相关资源
                  最近更新 更多