我想编写一个比较器,让我按值而不是默认的自然顺序对TreeMap进行排序。
我试过这样的事情,但找不到出了什么问题:
import java.util.*;
class treeMap {
public static void main(String[] args) {
System.out.println("the main");
byValue cmp = new byValue();
Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
map.put("de",10);
map.put("ab", 20);
map.put("a",5);
for (Map.Entry<String,Integer> pair: map.entrySet()) {
System.out.println(pair.getKey()+":"+pair.getValue());
}
}
}
class byValue implements Comparator<Map.Entry<String,Integer>> {
public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
if (e1.getValue() < e2.getValue()){
return 1;
} else if (e1.getValue() == e2.getValue()) {
return 0;
} else {
return -1;
}
}
}
我想我的问题是:我可以将Map.Entry
传递给比较器吗?
答案 0 :(得分:165)
您不能让TreeMap
本身对值进行排序,因为这违反了SortedMap
规范:
Map
进一步在键上提供总排序。
但是,使用外部集合,您可以随时按键,值,甚至两者的组合(!!)对Map.entrySet()
进行排序。
这是一个返回SortedSet
Map.Entry
的通用方法,给定Map
,其值为Comparable
:
static <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
@Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
return res != 0 ? res : 1;
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
现在您可以执行以下操作:
Map<String,Integer> map = new TreeMap<String,Integer>();
map.put("A", 3);
map.put("B", 2);
map.put("C", 1);
System.out.println(map);
// prints "{A=3, B=2, C=1}"
System.out.println(entriesSortedByValues(map));
// prints "[C=1, B=2, A=3]"
请注意,如果您尝试修改SortedSet
本身或其中的Map.Entry
,则会发生时髦的事情,因为这不再是{{1}原始地图的“视图”是的。
一般来说,需要按照值对地图的条目进行排序是非典型的。
entrySet()
==
注释
原始比较器使用Integer
对Integer
进行比较。这几乎总是错误的,因为带有==
操作数的==
是引用相等,而不是值相等。
Integer
答案 1 :(得分:73)
polygenelubricants答案是 几乎 完美。它有一个重要的错误。它不会处理值相同的映射条目。
此代码:...
Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);
for (Entry<String, Integer> entry : entriesSortedByValues(nonSortedMap)) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
输出:
ape:1
frog:2
pig:3
注意我们的牛如何消失,因为它与我们的猿共享价值“1”:O!
这段代码的修改解决了这个问题:
static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
@Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
return res != 0 ? res : 1; // Special fix to preserve items with equal values
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
答案 2 :(得分:32)
在Java 8中:
LinkedHashMap<Integer, String> sortedMap =
map.entrySet().stream().
sorted(Entry.comparingByValue()).
collect(Collectors.toMap(Entry::getKey, Entry::getValue,
(e1, e2) -> e1, LinkedHashMap::new));
答案 3 :(得分:13)
TreeMap
始终按键排序,其他任何内容都是不可能的。 Comparator
仅允许您控制 键的排序方式。
如果您想要排序的值,则必须将它们提取到List
并对其进行排序。
答案 4 :(得分:9)
使用Comparator
无法做到这一点,因为它总是会让地图的键进行比较。 TreeMap
只能按密钥排序。
答案 5 :(得分:5)
Olof的答案很好,但在完美之前需要一个。在他的回答下面的评论中,dacwe(正确地)指出他的实施违反了套装的比较/等于合同。如果您尝试在明确在集合中的条目上调用包含或删除,则集合将无法识别它,因为允许具有相等值的条目的代码放在集合中。因此,为了解决这个问题,我们需要测试密钥之间的相等性:
static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
@Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
if (e1.getKey().equals(e2.getKey())) {
return res; // Code will now handle equality properly
} else {
return res != 0 ? res : 1; // While still adding all entries
}
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
“请注意,如果有序集合要正确实现Set接口,则由有序集合维护的排序(无论是否提供显式比较器)必须与equals一致... Set接口是根据等于操作,但是有序集使用compareTo(或compare)方法执行所有元素比较,因此从排序集的角度来看,这个方法认为相等的两个元素是。 “ (http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html)
由于我们最初忽略了相等性以强制集合添加等值条目,现在我们必须测试密钥中的相等性,以便集合实际返回您正在寻找的条目。这有点混乱,绝对不是如何使用集合 - 但它有效。
答案 6 :(得分:3)
我知道这篇文章专门要求按值对TreeMap进行排序,但是对于我们这些并不真正关心实现但是想要一个保持集合排序的解决方案的解决方案,我会很感激对此的反馈。基于TreeSet的解决方案。例如,按键不能轻易检索元素,但对于我手边的用例(查找具有最低值的n个键),这不是必需的。
TreeSet<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
{
@Override
public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2)
{
int valueComparison = o1.getValue().compareTo(o2.getValue());
return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison;
}
});
int key = 5;
double value = 1.0;
set.add(new AbstractMap.SimpleEntry<>(key, value));
答案 7 :(得分:2)
很多人听说过建议使用List,我也喜欢使用它
这里有两种方法可以根据它们的值对Map的条目进行排序。
static final Comparator<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR =
new Comparator<Entry<?, Double>>() {
@Override
public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
return o1.getValue().compareTo(o2.getValue());
}
};
static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
{
Set<Entry<?, Double>> entryOfMap = temp.entrySet();
List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
return entries;
}
答案 8 :(得分:0)
import java.util.*;
public class Main {
public static void main(String[] args) {
TreeMap<String, Integer> initTree = new TreeMap();
initTree.put("D", 0);
initTree.put("C", -3);
initTree.put("A", 43);
initTree.put("B", 32);
System.out.println("Sorted by keys:");
System.out.println(initTree);
List list = new ArrayList(initTree.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
System.out.println("Sorted by values:");
System.out.println(list);
}
}