55

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 there any way that I can deal with the key and value with sorted values?

honk
  • 9,137
  • 11
  • 75
  • 83
Charlie Epps
  • 3,595
  • 9
  • 33
  • 38

12 Answers12

64

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.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • 2
    One question, wouldn't you be using double memory now? – atoMerz Dec 27 '11 at 04:00
  • 1
    @AtoMerZ If that's an issue, you can make the set hold references. If they're already references or small types, then it doesn't matter. – David Schwartz Dec 27 '11 at 04:04
  • 5
    To sort the vector of pairs: http://stackoverflow.com/questions/279854/how-do-i-sort-a-vector-of-pairs-based-on-the-second-element-of-the-pair – juanmirocks Jan 24 '13 at 17:03
  • 6
    To 'dump' the map to a vector: `vector> v(m.begin(), m.end());` http://stackoverflow.com/questions/684475/c-how-to-copy-a-map-to-a-vector – juanmirocks Jan 24 '13 at 17:07
  • But then how would one perform look up for key in this set? This might be naive question, please clarify. – Vishal Sahu Sep 20 '16 at 21:47
  • 1
    @Vishal You'd have to use linear-time lookup (`std::find`) unless you retain the original map. – C. K. Young Sep 20 '16 at 21:51
  • 3
    IMHO, the `set` idea won't work if the values aren't all distinct. If you do `myset.insert(make_pair(1, 1));` and `myset.insert(make_pair(2, 1))`, then the second pair won't be inserted if the functor compares only the pair's second value, because for that set both items are identical. – honk Feb 05 '18 at 15:55
36

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.

swegi
  • 4,046
  • 1
  • 26
  • 45
  • 5
    if all the values are unique, it is ok. How to deal with multiple keys with same value? So this solution is not good! – diwatu Apr 15 '13 at 05:50
  • 2
    This is not even a solution. What if the values are all the same? Then you second map will only have 1 element! – derek Jun 18 '15 at 18:29
17

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
4

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

rlbond
  • 65,341
  • 56
  • 178
  • 228
2

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.

honk
  • 9,137
  • 11
  • 75
  • 83
  • The second solution also has the advantage that it generalises to sorting on non-trivial data types for the map values – j b Jun 07 '18 at 15:38
1

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

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.

0

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;
}
Pat. ANDRIA
  • 2,330
  • 1
  • 13
  • 27
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);
}
Saurabh Mahra
  • 330
  • 5
  • 10
0

map is already sorded based on the first key, if you want to sort it based on the second value make second value as key. otherwies use another container like vector<pair<int,int>>.

Vishal choudhary
  • 315
  • 4
  • 10
-1

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);
Mercury Dime
  • 1,141
  • 2
  • 10
  • 10
-1

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;
    }
}
code_10
  • 155
  • 1
  • 2
  • 10
  • 1
    ***Never*** recommend the use of `#include ` in an answer on Stack Overflow. Also, best not to recommend `using namespace std;`, either. See: [Why should I not #include ?](https://stackoverflow.com/q/31816095/10871073) – Adrian Mole Jun 10 '21 at 15:03