如何先按值对 std::map 排序,然后按键排序?
Posted
技术标签:
【中文标题】如何先按值对 std::map 排序,然后按键排序?【英文标题】:How can I sort a std::map first by value, then by key? 【发布时间】:2013-11-19 10:59:46 【问题描述】:我需要按值对std::map
进行排序,然后按键。该地图包含如下数据:
1 realistically
8 really
4 reason
3 reasonable
1 reasonably
1 reassemble
1 reassembled
2 recognize
92 record
48 records
7 recs
我需要按顺序获取值,但更重要的是,在值按顺序排列之后,键需要按字母顺序排列。我该怎么做?
【问题讨论】:
你使用 std::map 来存储数据吗? 将 std::pairstd::map
将按keys
对其元素进行排序。排序时它不关心values
。
您可以使用std::vector<std::pair<K,V>>
,然后使用std::sort
后跟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);
第一个排序应该使用std::sort
,因为它是nlog(n)
,然后使用std::stable_sort
,在最坏的情况下是n(log(n))^2
。
请注意,虽然出于性能原因选择了std::sort
,但正确排序需要std::stable_sort
,因为您希望保留按值排序。
@gsf 在评论中指出,如果您选择一个比较器首先比较 values
,并且如果它们相等,则可以使用 only std::sort
,如果它们相等,则对 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);
这应该是有效的。
但是等等,有一个更好的方法:存储 std::pair<V,K>
而不是 std::pair<K,V>
然后你就不需要任何比较器了 - std::pair
的标准比较器就足够了,因为它比较 first
(即V
)首先是second
,即K
:
std::vector<std::pair<V,K>> items;
//...
std::sort(items.begin(), items.end());
这应该很好用。
【讨论】:
如果我像现在这样按字母顺序添加到向量中,排序的效率会受到影响吗? 哦,我明白了,不会是因为您首先按价值排序吗? @TrevorHutto:如果其中一个(key
或value
)已排序,那么您只需要stable_sort
对另一个未排序的排序。
我认为我不能在这种情况下应用它。你可能会以不同的方式告诉我。但是我有添加到地图中的函数,但是如果地图中已经包含它,它会将值增加一,当我按频率查找并尝试对字符串执行某些操作时,我该怎么做?
我得到了它与您答案中的最后一个解决方案一起工作,就像一个魅力。谢谢。【参考方案2】:
std::map
已经使用您定义的谓词或std::less
对值进行排序,如果您不提供的话。 std::set
还将按照定义比较器的顺序存储项目。但是 set 和 map 都不允许您拥有多个键。如果您想单独使用您的数据结构来完成此操作,我建议您定义一个std::map<int,std::set<string>
。您还应该意识到 std::less
for string 将按字典顺序而不是按字母顺序排序。
【讨论】:
【参考方案3】:编辑:其他两个答案说得很好。我假设您想将它们排列成其他结构,或者打印出来。
“最佳”可以表示许多不同的东西。您的意思是“最简单”、“最快”、“最高效”、“最少代码”、“最易读”吗?
最明显的方法是循环两次。在第一遍中,对值进行排序:
if(current_value > examined_value)
current_value = examined_value
(and then swap them, however you like)
然后在第二遍时,按字母顺序排列单词,但前提是它们的值匹配。
if(current_value == examined_value)
(alphabetize the two)
严格来说,这是一种“冒泡排序”,速度很慢,因为每次进行交换时,都必须重新开始。当您通过整个列表而不进行任何交换时,就完成了一次“通过”。
还有其他排序算法,但原理是一样的:按值排序,然后按字母排序。
【讨论】:
【参考方案4】:您可以使用std::set
代替std::map
。
您可以在std::pair
中存储键和值,容器的类型将如下所示:
std::set< std::pair<int, std::string> > items;
std::set
将按原始键和存储在std::map
中的值对其值进行排序。
【讨论】:
这里不是这样:输入是地图。这可能是由于各种原因,例如,值是数据库中出现的字符串等。我们不能在这种情况下使用 set,因为 set::insert() 将处理 ("foo", 1) & ("foo", 2 ) 不同的项目。【参考方案5】:正如 Nawaz 的 answer 中所解释的,您无法根据需要自行对地图进行排序,因为 std::map
仅根据键对其元素进行排序。因此,您需要一个不同的容器,但如果您必须坚持使用您的地图,那么您仍然可以(临时)将其内容复制到另一个数据结构中。
我认为,最好的解决方案是使用 std::set
存储翻转的键值对,如 ks1322 的 answer 中所示。
std::set
是默认排序的,order of the pairs 正是你需要的:
3) 如果
lhs.first<rhs.first
,则返回true
。否则,如果rhs.first<lhs.first
,则返回false
。否则,如果lhs.second<rhs.second
,则返回true
。否则,返回false
。
这样您就不需要额外的排序步骤,并且生成的代码很短:
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;
输出:
1 realistically
1 reasonably
1 reassemble
1 reassembled
2 recognize
3 reasonable
4 reason
7 recs
8 really
48 records
92 record
Code on Ideone
注意:由于C++17,您可以使用range-based for loops 和structured bindings 来迭代地图。 结果,用于复制地图的代码变得更短且更易读:
for (auto const &[k, v] : m)
s.emplace(v, k); // Flip the pairs.
【讨论】:
以上是关于如何先按值对 std::map 排序,然后按键排序?的主要内容,如果未能解决你的问题,请参考以下文章