开发者

How can I sort an STL map by value?

How can I implement STL map sorting by value?

For example, I have a map m:

map<int, int> m;
m[1] = 10;
m[2] = 5;
m[4] = 6;
m[6] = 1;

I'd like to sort that map by m's value. So, if I print the map, I'd like to get the result as follows:

m[6] = 1
m[2] = 5
m[4] = 6
m[1] = 10

How can I sort the map in this way? Is开发者_如何学C there any way that I can deal with the key and value with sorted values?


Dump out all the key-value pairs into a set<pair<K, V> > first, where the set is constructed with a less-than functor that compares the pair's second value only. That way, your code still works even if your values aren't all distinct.

Or dump the key-value pairs into a vector<pair<K, V> >, then sort that vector with the same less-than functor afterwards.


You can build a second map, with the first map's values as keys and the first map's keys as values.

This works only if all values are distinct. If you cannot assume this, then you need to build a multimap instead of a map.


I wonder how can I implement the STL map sorting by value.

You can’t, by definition. A map is a data structure that sorts its element by key.


You should use Boost.Bimap for this sort of thing.


Based on @swegi's idea, I implemented a solution in c++11 using a multimap:

map<int, int> m = {{1, 10}, {2, 5}, {4, 6}, {6, 1}};
multimap<int, int> mm;

for(auto const &kv : m)
    mm.insert(make_pair(kv.second, kv.first));  // Flip the pairs.

for(auto const &kv : mm)
    cout << "m[" << kv.second << "] = " << kv.first << endl;  // Flip the pairs again.

Code on Ideone

I also implemented a C++11 solution based on @Chris' idea using a vector of pairs. For correct sorting, I provide a lambda expression as comparison functor:

map<int, int> m = {{1, 10}, {2, 5}, {4, 6}, {6, 1}};
using mypair = pair<int, int>;

vector<mypair> v(begin(m), end(m));

sort(begin(v), end(v), [](const mypair& a, const mypair& b) { return a.second < b.second; });

for(auto const &p : v)
    cout << "m[" << p.first << "] = " << p.second << endl;

Code on Ideone

The first solution is more compact, but both solutions should have roughly the same performance. Inserting into a multimap is of O(log n), but this has to be done for n entries, resulting in O(n log n). Sorting the vector in the second solution also results in O(n log n).

I also gave a try to @Chris' idea on using a set of pairs. However, it won't work if the values aren't all distinct. Using a functor that compares only the pair's second element doesn't help. If you first insert make_pair(1, 1) into the set and then try to insert make_pair(2, 1), then the second pair won't be inserted, because both pairs are seen as identical by that set. You can see that effect here on Ideone.


I've just done a similar question in my c++ book. The answer I came up with might not be very efficient though:

int main()
{
    string s;
    map<string, int> counters;

    while(cin >> s)
        ++counters[s];

    //Get the largest and smallest values from map
    int beginPos = smallest_map_value(counters);
    int endPos = largest_map_value(counters);

    //Increment through smallest value to largest values found
    for(int i = beginPos; i <= endPos; ++i)
    {
        //For each increment, go through the map...
        for(map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
        {
            //...and print out any pairs with matching values
            if(it->second == i)
            {
                cout << it->first << "\t" << it->second << endl;
            }
        }
    }
    return 0;
}

//Find the smallest value for a map<string, int>
int smallest_map_value(const map<string, int>& m)
{
    map<string, int>::const_iterator it = m.begin();
    int lowest = it->second;
    for(map<string, int>::const_iterator it = m.begin(); it != m.end(); ++it)
    {
        if(it->second < lowest)
            lowest = it->second;
    }
    return lowest;
}

//Find the largest value for a map<string, int>
int largest_map_value(const map<string, int>& m)
{
    map<string, int>::const_iterator it = m.begin();
    int highest = it->second;
    for(map<string, int>::const_iterator it = m.begin(); it != m.end(); ++it)
    {
        if(it->second > highest)
            highest = it->second;
    }
    return highest;
}


Create another map, provide a less() function based on the value not key, AND the function should return true if the value1 <= value2 (not strictly < ). In this case, elements with non-distinct values can be sorted as well.


I have found this in thispointer. The example sorts a std::map< std::string,int> by all the int values.

#include <map>
#include <set>
#include <algorithm>
#include <functional>

int main() {

    // Creating & Initializing a map of String & Ints
    std::map<std::string, int> mapOfWordCount = { { "aaa", 10 }, { "ddd", 41 },
            { "bbb", 62 }, { "ccc", 13 } };

    // Declaring the type of Predicate that accepts 2 pairs and return a bool
    typedef std::function<bool(std::pair<std::string, int>, std::pair<std::string, int>)> Comparator;

    // Defining a lambda function to compare two pairs. It will compare two pairs using second field
    Comparator compFunctor =
            [](std::pair<std::string, int> elem1 ,std::pair<std::string, int> elem2)
            {
                return elem1.second < elem2.second;
            };

    // Declaring a set that will store the pairs using above comparision logic
    std::set<std::pair<std::string, int>, Comparator> setOfWords(
            mapOfWordCount.begin(), mapOfWordCount.end(), compFunctor);

    // Iterate over a set using range base for loop
    // It will display the items in sorted order of values
    for (std::pair<std::string, int> element : setOfWords)
        std::cout << element.first << " :: " << element.second << std::endl;

    return 0;
}


One thing that could be done in some scenarios, is using a vector<pair<int, int>> rather than using maps<int, int>. In this way you lose the benefits of using map, such as the less lookup time but you can directly use comparator function with vector<pair<int, int>>

bool compare(pair<int, int> a, pair<int, int> b)
{
    return (a.second < b.second);
}


Recently had to do this. I ended up using pointers...

Quick Benchmark Results

#include <iostream>
#include <type_traits>
#include <algorithm>
#include <map>
#include <vector>

using map_t = std::map<int,int>;
const map_t m
{
    { 5, 20 },
    { -18, 28 },
    { 24, 49 },
    { 17, 27 },
    { 23, 46 },
    { 8, 16 },
    { -13, 11 },
    { -22, 32 },
    { 12, 45 },
    { -2, 19 },
    { 21, 11 },
    { -12, 25 },
    { -20, 8 },
    { 0, 29 },
    { -5, 20 },
    { 13, 26 },
    { 1, 27 },
    { -14, 3 },
    { 19, 47 },
    { -15, 17 },
    { 16, 1 },
    { -17, 50 },
    { -6, 40 },
    { 15, 24 },
    { 9, 10 }
};

template<typename T>
void sort_values_using_vector(T const& m)
{
    using map_t = T;

    using sort_t = std::vector<std::pair<typename map_t::key_type,
        typename map_t::mapped_type>>;
    sort_t sorted{ m.begin(), m.end() };
    
    std::sort(sorted.begin(), sorted.end(),
        [](auto const& lhs, auto const& rhs)
        {
            return lhs.second < rhs.second;
        });
}

template<typename T>
void sort_values_using_multimap(T const& m)
{
    using map_t = T;

    using sort_t = std::multimap<typename map_t::mapped_type,
        typename map_t::key_type>;
    sort_t sorted;

    for (auto const& kv : m)
    {
        sorted.insert(std::make_pair(kv.second, kv.first));
    }
}

template<typename T>
void sort_values_using_ptrs(T const& m)
{
    using map_t = T;

    using ptr_t = std::add_pointer_t
        <std::add_const_t<typename map_t::value_type>>;
    using sort_t = std::vector<ptr_t>;
    sort_t sorted;
    sorted.reserve(m.size());

    for (auto const& kv : m)
    {
        sorted.push_back(std::addressof(kv));
    }

    std::sort(sorted.begin(), sorted.end(),
        [](auto const& lhs, auto const& rhs)
        {
            return lhs->second < rhs->second;
        });
}

template<typename T>
void sort_values_using_refs(T const& m)
{
    using map_t = T;

    using ref_t = std::reference_wrapper
        <std::add_const_t<typename map_t::value_type>>;
    using sort_t = std::vector<ref_t>;
    sort_t sorted{ m.begin(), m.end() };

    std::sort(sorted.begin(), sorted.end(),
        [](auto const& lhs, auto const& rhs)
        {
            return lhs.get().second < rhs.get().second;
        });
}

static void copy_to_vector(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    sort_values_using_vector(m);
  }
}
BENCHMARK(copy_to_vector);

static void copy_flipped_to_multimap(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    sort_values_using_multimap(m);
  }
}
BENCHMARK(copy_flipped_to_multimap);

static void copy_ptrs_to_vector(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    sort_values_using_ptrs(m);
  }
}
BENCHMARK(copy_ptrs_to_vector);

static void use_refs_in_vector(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    sort_values_using_refs(m);
  }
}
BENCHMARK(use_refs_in_vector);


This code uses custom sorting function to sort map by values

// Comparator function to sort pairs
// according to value
bool comp(pair<int, int>& a,
        pair<int, int>& b)
{
    return a.second < b.second;
}

// Function to sort the map according
// to value in a (key-value) pair
void customSort(map<int, int>& m)
{

    vector<pair<int, int>> a;

    for(auto x:m)
        a.push_back(make_pair(x.first,x.second));

    sort(a.begin(), a.end(), comp);

    for (auto x:a) {
        cout << x.first<<" "<<x.second<<endl;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜