使用值对std::map进行排序

85

我需要按值而不是键对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?

3
你为什么需要按那种方式排序地图?是为了提高查找速度,还是想以特定方式遍历它? - Matt Curtis
你可以交换键和值。 - Pawel Zubrycki
4
可能是 STL map-->按值排序? 的重复问题。 - Ciro Santilli OurBigBook.com
14个回答

72

虽然已经有正确的答案被发布了,但我想添加一个演示,展示如何干净地完成这个任务:

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::unordered_map),您可以编写一个单独的重载,但最终的操作仍然相同,所以可以使用可变模板参数的通用关联容器来处理任何映射构造:

// 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::mapstd::unordered_map 作为翻转的源。


1
感谢提供示例代码。它帮助我推广解决方案。现在我可以轻松翻转任何地图。 - user619237
18
对于这个干净的解决方案,我给出加一的评价。然而,如果目标映射是一个multimap,将更好,以避免与相同“value”键发生冲突。 - theosem
4
如果多个值具有相同的值,则此解决方案无效,因为反转的映射不能具有具有相同键的两个键值对。对于原始映射中的唯一值,此解决方案有效! - Jim Huang
根据theosem的建议,将此结果编辑为multimap。 - ecotax
附注:添加了对通用关联容器的支持,而不仅仅是 std::map。顺便说一句,Oliver,你的回答很好。 - WhozCraig

59

我需要类似的东西,但是翻转的地图对我没有用。我只是将我的地图(下面的 freq)复制到一对向量中,然后按照我想要的方式对这些对进行排序。

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;
}
);

4
[=] 这个符号是什么意思? - Tanner Summers
@TannerSummers 这是Lambda表达式的一部分。http://en.cppreference.com/w/cpp/language/lambda - NielW
这是一个很棒的答案。通过传递一个带有适当 operator() 重载的 Function 对象,可以使 sort 函数更加模块化,以实现所需的排序逻辑。不过,lambda 逻辑的使用也很好。 - h0r53
@Keiji 很好的问题。Lambda支持是在C++ 0x(现在11)中添加的。如果您添加了C++ 11标志,它应该可以工作。 https://dev59.com/5mQn5IYBdhLWcg3wXGKr - NielW
使用assign复制map更好吗?例如:pairs.assign(freq.begin(), freq.end()); - jacknad
显示剩余2条评论

17
如果你想要按照排序顺序展示一个 map 中的值,那么可以将这些值从 map 复制到 vector 中并对 vector 进行排序。

1
+1:这(或其变体,例如创建一个“反向”映射)是正确的答案。 - Oliver Charlesworth
16
如果我这样做,就会失去键和值之间的关系。比如,我有一个map<long, double> id2Score,它包含了所有ID(其中ID不是0..n,可能是1、5、13等)和得分。如果我创建一个包含得分的向量并排序,那么我将失去哪个ID与哪个得分相关联的信息。 - user619237
@user619237:不完全是这样。你可以对向量进行排序,获取所需的最大值或最小值或其他值。然后遍历原始映射,并在映射的值("it->second")中查找与该值匹配的项。 - hAcKnRoCk

9

我喜欢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;
};

7

在 Oli 的解决方案 (https://dev59.com/pW445IYBdhLWcg3wE2Je#5056797) 的基础上,使用 multimap,你可以用以下函数替换他所使用的两个模板函数:

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;
}

结果:

在此输入图片描述


便携式解决方案 - 也适用于旧版本。代码中指纹更小。 - TarmoPikaro

6

您无法通过此方法对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


谢谢,但我知道这个解决方案。除此之外还有其他的解决方案吗? - user619237

4

按值排序的std::map本质上是一个std::set。目前最简单的方法是将地图中所有条目复制到一个集合中(参考自这里)。

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 );
    }
}

注意:如果映射包含具有相同值的不同键,则它们不会被插入到集合中并且将丢失。


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
  }

3

翻转结构可能不再是一个映射,而是一个 multimap,因此在上面的 flip_map 示例中,来自 B 的并非所有元素都会出现在结果数据结构中。


1
在以下示例代码中,我编写了一种简单的方法来输出单词映射(word_map)中出现次数最高的单词,其中键是字符串(单词),值是无符号整数(单词出现次数)。
思路很简单,找到当前的顶部单词并从映射中删除它。虽然不是最优化的,但它在映射不大且我们只需要输出前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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接