我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。
由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。
有没有更简单的方法?
我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。
由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。
有没有更简单的方法?
当前回答
我建议使用Arrays.sort,而不是像某些人那样使用Collections.ort。实际上Collections.ort的作用是这样的:
public static <T extends Comparable<? super T>> void sort(List<T> list) {
Object[] a = list.toArray();
Arrays.sort(a);
ListIterator<T> i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set((T)a[j]);
}
}
它只调用列表上的array,然后使用Arrays.sort。这样,所有映射条目将被复制三次:一次从映射复制到临时列表(无论是LinkedList还是ArrayList),然后复制到临时数组,最后复制到新映射。
我的解决方案省略了这一步,因为它不会创建不必要的LinkedList。以下是代码,通用友好,性能最佳:
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map)
{
@SuppressWarnings("unchecked")
Map.Entry<K,V>[] array = map.entrySet().toArray(new Map.Entry[map.size()]);
Arrays.sort(array, new Comparator<Map.Entry<K, V>>()
{
public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2)
{
return e1.getValue().compareTo(e2.getValue());
}
});
Map<K, V> result = new LinkedHashMap<K, V>();
for (Map.Entry<K, V> entry : array)
result.put(entry.getKey(), entry.getValue());
return result;
}
其他回答
static <K extends Comparable<? super K>, V extends Comparable<? super V>>
Map sortByValueInDescendingOrder(final Map<K, V> map) {
Map re = new TreeMap(new Comparator<K>() {
@Override
public int compare(K o1, K o2) {
if (map.get(o1) == null || map.get(o2) == null) {
return -o1.compareTo(o2);
}
int result = -map.get(o1).compareTo(map.get(o2));
if (result != 0) {
return result;
}
return -o1.compareTo(o2);
}
});
re.putAll(map);
return re;
}
@Test(timeout = 3000l, expected = Test.None.class)
public void testSortByValueInDescendingOrder() {
char[] arr = "googler".toCharArray();
Map<Character, Integer> charToTimes = new HashMap();
for (int i = 0; i < arr.length; i++) {
Integer times = charToTimes.get(arr[i]);
charToTimes.put(arr[i], times == null ? 1 : times + 1);
}
Map sortedByTimes = sortByValueInDescendingOrder(charToTimes);
Assert.assertEquals(charToTimes.toString(), "{g=2, e=1, r=1, o=2, l=1}");
Assert.assertEquals(sortedByTimes.toString(), "{o=2, g=2, r=1, l=1, e=1}");
Assert.assertEquals(sortedByTimes.containsKey('a'), false);
Assert.assertEquals(sortedByTimes.get('a'), null);
Assert.assertEquals(sortedByTimes.get('g'), 2);
Assert.assertEquals(sortedByTimes.equals(charToTimes), true);
}
Java 8提供了一个新的答案:将条目转换为流,并使用Map中的比较器组合符。条目:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue());
这将允许您使用按值升序排序的条目。如果您想要递减值,只需反转比较器:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
如果这些值不可比较,则可以传递显式比较器:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(comparator));
然后,您可以继续使用其他流操作来使用数据。例如,如果要在新地图中显示前10名:
Map<K,V> topTen =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
上面看到的LinkedHashMap按插入顺序迭代条目。
或打印到System.out:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(System.out::println);
这太复杂了。地图不应该按价值排序。最简单的方法是创建自己的类,以满足您的需求。
在下面的示例中,您应该在*所在的位置添加TreeMap比较器。但通过javaAPI,它只提供比较器键,而不提供值。此处所述的所有示例均基于2个地图。一个哈希和一个新树。这很奇怪。
示例:
Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);
因此,通过以下方式将地图更改为集合:
ResultComparator rc = new ResultComparator();
Set<Results> set = new TreeSet<Results>(rc);
您将创建类Results,
public class Results {
private Driver driver;
private Float time;
public Results(Driver driver, Float time) {
this.driver = driver;
this.time = time;
}
public Float getTime() {
return time;
}
public void setTime(Float time) {
this.time = time;
}
public Driver getDriver() {
return driver;
}
public void setDriver (Driver driver) {
this.driver = driver;
}
}
以及Comparator类:
public class ResultsComparator implements Comparator<Results> {
public int compare(Results t, Results t1) {
if (t.getTime() < t1.getTime()) {
return 1;
} else if (t.getTime() == t1.getTime()) {
return 0;
} else {
return -1;
}
}
}
这样,您可以轻松添加更多依赖项。
最后一点,我将添加简单迭代器:
Iterator it = set.iterator();
while (it.hasNext()) {
Results r = (Results)it.next();
System.out.println( r.getDriver().toString
//or whatever that is related to Driver class -getName() getSurname()
+ " "
+ r.getTime()
);
}
我认为最好的方法是使用特殊的数据结构。您可以考虑TreeMap,但在一般情况下,值可能不是唯一的。因此,您的选择是PriorityQueue:
public static <K, V> Iterator<Map.Entry<K, V>> sortByValue(
Map<K, V> map,
Comparator<V> valueComparator) {
Queue<Map.Entry<K, V>> queue = new PriorityQueue<>((one, two) ->
valueComparator.compare(one.getValue(), two.getValue()));
queue.addAll(map.entrySet());
return queue.iterator();
}
这种方法正好能达到目的。(“挫折”是Values必须实现java.util.Comparable接口)
/**
* Sort a map according to values.
* @param <K> the key of the map.
* @param <V> the value to sort according to.
* @param mapToSort the map to sort.
* @return a map sorted on the values.
*/
public static <K, V extends Comparable< ? super V>> Map<K, V>
sortMapByValues(final Map <K, V> mapToSort)
{
List<Map.Entry<K, V>> entries =
new ArrayList<Map.Entry<K, V>>(mapToSort.size());
entries.addAll(mapToSort.entrySet());
Collections.sort(entries,
new Comparator<Map.Entry<K, V>>()
{
@Override
public int compare(
final Map.Entry<K, V> entry1,
final Map.Entry<K, V> entry2)
{
return entry1.getValue().compareTo(entry2.getValue());
}
});
Map<K, V> sortedMap = new LinkedHashMap<K, V>();
for (Map.Entry<K, V> entry : entries)
{
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
http://javawithswaranga.blogspot.com/2011/06/generic-method-to-sort-hashmap.html