How can I sort a std::map first by value, then by key?
Solution 1
std::map
will sort its elements by keys
. It doesn't care about the values
when sorting.
You can use std::vector<std::pair<K,V>>
then sort it using std::sort
followed by std::stable_sort
:
std::vector<std::pair<K,V>> items;
//fill items
//sort by value using std::sort
std::sort(items.begin(), items.end(), value_comparer);
//sort by key using std::stable_sort
std::stable_sort(items.begin(), items.end(), key_comparer);
The first sort should use std::sort
since it is nlog(n)
, and then use std::stable_sort
which is n(log(n))^2
in the worst case.
Note that while std::sort
is chosen for performance reason, std::stable_sort
is needed for correct ordering, as you want the order-by-value to be preserved.
@gsf noted in the comment, you could use only std::sort
if you choose a comparer which compares values
first, and IF they're equal, sort the keys
.
auto cmp = [](std::pair<K,V> const & a, std::pair<K,V> const & b)
{
return a.second != b.second? a.second < b.second : a.first < b.first;
};
std::sort(items.begin(), items.end(), cmp);
That should be efficient.
But wait, there is a better approach: store std::pair<V,K>
instead of std::pair<K,V>
and then you don't need any comparer at all — the standard comparer for std::pair
would be enough, as it compares first
(which is V
) first then second
which is K
:
std::vector<std::pair<V,K>> items;
//...
std::sort(items.begin(), items.end());
That should work great.
Solution 2
You can use std::set
instead of std::map
.
You can store both key and value in std::pair
and the type of container will look like this:
std::set< std::pair<int, std::string> > items;
std::set
will sort it's values both by original keys and values that were stored in std::map
.
Solution 3
As explained in Nawaz's answer, you cannot sort your map by itself as you need it, because std::map
sorts its elements based on the keys only. So, you need a different container, but if you have to stick to your map, then you can still copy its content (temporarily) into another data structure.
I think, the best solution is to use a std::set
storing flipped key-value pairs as presented in ks1322's answer.
The std::set
is sorted by default and the order of the pairs is exactly as you need it:
3) If
lhs.first<rhs.first
, returnstrue
. Otherwise, ifrhs.first<lhs.first
, returnsfalse
. Otherwise, iflhs.second<rhs.second
, returnstrue
. Otherwise, returnsfalse
.
This way you don't need an additional sorting step and the resulting code is quite short:
std::map<std::string, int> m; // Your original map.
m["realistically"] = 1;
m["really"] = 8;
m["reason"] = 4;
m["reasonable"] = 3;
m["reasonably"] = 1;
m["reassemble"] = 1;
m["reassembled"] = 1;
m["recognize"] = 2;
m["record"] = 92;
m["records"] = 48;
m["recs"] = 7;
std::set<std::pair<int, std::string>> s; // The new (temporary) container.
for (auto const &kv : m)
s.emplace(kv.second, kv.first); // Flip the pairs.
for (auto const &vk : s)
std::cout << std::setw(3) << vk.first << std::setw(15) << vk.second << std::endl;
Output:
1 realistically
1 reasonably
1 reassemble
1 reassembled
2 recognize
3 reasonable
4 reason
7 recs
8 really
48 records
92 record
Note: Since C++17 you can use range-based for loops together with structured bindings for iterating over a map. As a result, the code for copying your map becomes even shorter and more readable:
for (auto const &[k, v] : m)
s.emplace(v, k); // Flip the pairs.
Solution 4
std::map
already sorts the values using a predicate you define or std::less
if you don't provide one. std::set
will also store items in order of the of a define comparator. However neither set nor map allow you to have multiple keys. I would suggest defining a std::map<int,std::set<string>
if you want to accomplish this using your data structure alone. You should also realize that std::less
for string will sort lexicographically not alphabetically.
Related videos on Youtube
Trevor Hutto
Updated on July 09, 2022Comments
-
Trevor Hutto almost 2 years
I need to sort a
std::map
by value, then by key. The map contains data like the following:1 realistically 8 really 4 reason 3 reasonable 1 reasonably 1 reassemble 1 reassembled 2 recognize 92 record 48 records 7 recs
I need to get the values in order, but the kicker is that the keys need to be in alphabetical order after the values are in order. How can I do this?
-
Raxvan over 10 yearsdo you use a std::map to store the data ?
-
Wilbert over 10 yearsPut the std::pair<int, std::string>s into a list, and sort it.
-
Trevor Hutto over 10 years@Raxvan yes, its stored in a map.
-
Trevor Hutto over 10 years@Wilbert I know that would put the values in order, but then would it keep the keys in alphabetical order after sorting the values?
-
Wilbert over 10 yearssort on pairs sorts on first, then second item. See here
-
Ciro Santilli OurBigBook.com over 7 years
-
BugShotGG about 6 years@TrevorHutto You can use
std::multimap
with yourvalues
askeys
since it allows duplicate keys and will work withstd::sort
-
-
Trevor Hutto over 10 yearsWould the efficiency of the sort be hurt if I add to the vector in alphabetical order like I have it now?
-
Trevor Hutto over 10 yearsOh I see, it wouldn't because you are sorting on value first?
-
Nawaz over 10 years@TrevorHutto: If one (either
key
orvalue
) is sorted, then you need onlystable_sort
to sort the other which is not sorted. -
gsf over 10 yearsThat is very bad way to do this. Instead use only one sort but provide compare predicate that compares the keys and in case they are equal compares the value.
-
Trevor Hutto over 10 yearsI don't think I can apply that in this situation. You may tell me differently. But I have functions that add to the map, but if that is already contained in the map it increases the value by one, how can I do that when I'm looking up by the frequency and trying to do something to the string?
-
Trevor Hutto over 10 yearsIn other words, I don't think switching the key and value will help me much.
-
Trevor Hutto over 10 yearsI got it working with the last solution in your answer, works like a charm. Thanks.
-
Vishal Sahu over 6 yearsThis isn't the case here: the input is map. That might be for various reasons, e.g., values are occurrence of strings in database etc. We can't use set in such cases because set::insert() will treat ("foo", 1) & ("foo", 2) different items.
-
Vishal Sahu over 6 yearsThe data is in map. auto cmp = [](std::pair<K,V> const & a, std::pair<K,V> const & b) { return a.second != b.second? a.second < b.second : a.first < b.first; }; std::sort(items.begin(), items.end(), cmp); items is not map.
-
BugShotGG about 6 years@Nawaz How
there is a better approach: store std::pair<V,K> instead of std::pair<K,V> and then you don't need any comparer at all
would even work if you want to extract a value based on key? -
BugShotGG about 6 years@Nawaz I cant see how this would work. Values will be unique so same values will be dropped from map. This should only work with
std::multimap
-
Nawaz about 6 years@BugShotGG: You asked about extracting value based on key and that sounds like: given a key (which is unique), you want to extract the corresponding value.