我是Java的新手,经常发现我需要对它进行排序 Map<Key, Value>
关于价值观。
由于价值不是唯一的,我发现自己转换了 keySet
变成一个 array
,并通过排序该数组 数组排序 用一个 定制比较器 对与键关联的值进行排序。
有更容易的方法吗?
我是Java的新手,经常发现我需要对它进行排序 Map<Key, Value>
关于价值观。
由于价值不是唯一的,我发现自己转换了 keySet
变成一个 array
,并通过排序该数组 数组排序 用一个 定制比较器 对与键关联的值进行排序。
有更容易的方法吗?
这是一个通用友好版本:
public class MapUtil {
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
list.sort(Entry.comparingByValue());
Map<K, V> result = new LinkedHashMap<>();
for (Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}
此代码可以以多种方式中断。 如果您打算使用提供的代码,请务必阅读注释以了解其含义。例如,不能再通过其密钥检索值。 (get
总是回来 null
。)
它似乎比上述所有内容容易得多。使用TreeMap如下:
public class Testing {
public static void main(String[] args) {
HashMap<String, Double> map = new HashMap<String, Double>();
ValueComparator bvc = new ValueComparator(map);
TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);
map.put("A", 99.5);
map.put("B", 67.4);
map.put("C", 67.4);
map.put("D", 67.3);
System.out.println("unsorted map: " + map);
sorted_map.putAll(map);
System.out.println("results: " + sorted_map);
}
}
class ValueComparator implements Comparator<String> {
Map<String, Double> base;
public ValueComparator(Map<String, Double> base) {
this.base = base;
}
// Note: this comparator imposes orderings that are inconsistent with
// equals.
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
}
}
输出:
unsorted map: {D=67.3, A=99.5, B=67.4, C=67.4}
results: {D=67.3, B=67.4, C=67.4, A=99.5}
Java 8提供了一个新的答案:将条目转换为流,并使用Map.Entry中的比较器组合器:
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));
或打印到 System.out
:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(System.out::println);
三个1行答案......
我会用 Google Collections 番石榴 这样做 - 如果你的价值观是 Comparable
那你可以用
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))
这将为地图创建一个函数(对象)[将任何键作为输入,返回相应的值],然后对它们[值]应用自然(可比较)排序。
如果他们没有可比性,那么你需要做一些事情
valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map))
这些可以应用于TreeMap(如 Ordering
扩展 Comparator
),或者a 一些排序后的LinkedHashMap
NB:如果您打算使用TreeMap,请记住,如果比较== 0,那么该项已经在列表中(如果您有多个比较相同的值,则会发生这种情况)。为了缓解这种情况,您可以将密钥添加到比较器中(假设您的密钥和值是 Comparable
):
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map)).compound(Ordering.natural())
= 将自然顺序应用于键映射的值,并使用键的自然顺序进行复合
请注意,如果您的密钥与0比较,这仍然不起作用,但这对大多数人来说应该足够了 comparable
物品(如 hashCode
, equals
和 compareTo
通常是同步...)
看到 Ordering.onResultOf() 和 Functions.forMap()。
所以现在我们已经有了一个可以满足我们想要的比较器,我们需要从中获得结果。
map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);
现在这很可能会起作用,但是:
TreeMap
;没有必要尝试比较插入的密钥,直到它没有值,直到放置之后,即,它会非常快地破坏第1点对我来说是一个破坏性的事情;谷歌收藏是非常懒惰(这是好的:你可以在瞬间完成几乎所有的操作;真正的工作是在你开始使用结果时完成的),这需要复制一个 整个 地图!
不过不要担心;如果你对以这种方式排序的“实时”地图足够痴迷,你可以解决上述问题中的一个而不是一个(!),如下所示:
注意:这在2012年6月发生了重大变化 - 之前的代码永远不会起作用:需要内部HashMap来查找值而不会在之间创建无限循环 TreeMap.get()
- > compare()
和 compare()
- > get()
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import com.google.common.base.Functions;
import com.google.common.collect.Ordering;
class ValueComparableMap<K extends Comparable<K>,V> extends TreeMap<K,V> {
//A map for doing lookups on the keys for comparison so we don't get infinite loops
private final Map<K, V> valueMap;
ValueComparableMap(final Ordering<? super V> partialValueOrdering) {
this(partialValueOrdering, new HashMap<K,V>());
}
private ValueComparableMap(Ordering<? super V> partialValueOrdering,
HashMap<K, V> valueMap) {
super(partialValueOrdering //Apply the value ordering
.onResultOf(Functions.forMap(valueMap)) //On the result of getting the value for the key from the map
.compound(Ordering.natural())); //as well as ensuring that the keys don't get clobbered
this.valueMap = valueMap;
}
public V put(K k, V v) {
if (valueMap.containsKey(k)){
//remove the key in the sorted set before adding the key again
remove(k);
}
valueMap.put(k,v); //To get "real" unsorted values for the comparator
return super.put(k, v); //Put it in value order
}
public static void main(String[] args){
TreeMap<String, Integer> map = new ValueComparableMap<String, Integer>(Ordering.natural());
map.put("a", 5);
map.put("b", 1);
map.put("c", 3);
assertEquals("b",map.firstKey());
assertEquals("a",map.lastKey());
map.put("d",0);
assertEquals("d",map.firstKey());
//ensure it's still a map (by overwriting a key, but with a new value)
map.put("d", 2);
assertEquals("b", map.firstKey());
//Ensure multiple values do not clobber keys
map.put("e", 2);
assertEquals(5, map.size());
assertEquals(2, (int) map.get("e"));
assertEquals(2, (int) map.get("d"));
}
}
当我们放置时,我们确保哈希映射具有比较器的值,然后放入TreeSet进行排序。但在此之前,我们检查哈希映射,看看密钥实际上并不重复。此外,我们创建的比较器还将包含密钥,以便重复值不会删除非重复键(由于==比较)。
这2项是 重要 确保保留地图合同;如果你认为你不想要那个,那么你几乎就是完全颠倒了地图(对 Map<V,K>
)。
需要将构造函数称为
new ValueComparableMap(Ordering.natural());
//or
new ValueComparableMap(Ordering.from(comparator));
从 http://www.programmersheaven.com/download/49349/download.aspx
private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
Collections.sort(list, new Comparator<Object>() {
@SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
}
});
Map<K, V> result = new LinkedHashMap<>();
for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
result.put(entry.getKey(), entry.getValue());
}
return result;
}
对键进行排序需要比较器查找每个比较的每个值。一个更具可扩展性的解决方案将直接使用entrySet,因为这样的值将立即可用于每次比较(尽管我没有用数字来支持)。
这是这种东西的通用版本:
public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue(Map<K, V> map) {
final int size = map.size();
final List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>(size);
list.addAll(map.entrySet());
final ValueComparator<V> cmp = new ValueComparator<V>();
Collections.sort(list, cmp);
final List<K> keys = new ArrayList<K>(size);
for (int i = 0; i < size; i++) {
keys.set(i, list.get(i).getKey());
}
return keys;
}
private static final class ValueComparator<V extends Comparable<? super V>>
implements Comparator<Map.Entry<?, V>> {
public int compare(Map.Entry<?, V> o1, Map.Entry<?, V> o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
对于上述解决方案,有一些方法可以减少内存轮换。例如,创建的第一个ArrayList可以重新用作返回值;这将需要抑制一些泛型警告,但对于可重用的库代码可能是值得的。此外,不必在每次调用时重新分配比较器。
这是一个更有效但尽管不那么吸引人的版本:
public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue2(Map<K, V> map) {
final int size = map.size();
final List reusedList = new ArrayList(size);
final List<Map.Entry<K, V>> meView = reusedList;
meView.addAll(map.entrySet());
Collections.sort(meView, SINGLE);
final List<K> keyView = reusedList;
for (int i = 0; i < size; i++) {
keyView.set(i, meView.get(i).getKey());
}
return keyView;
}
private static final Comparator SINGLE = new ValueComparator();
最后,如果您需要不断访问已排序的信息(而不是仅仅偶尔对其进行排序),则可以使用其他多地图。如果您需要更多详细信息,请告诉我们......
使用Java 8,您可以使用 溪流api 以明显不那么冗长的方式做到这一点:
Map<K, V> sortedMap = map.entrySet().stream()
.sorted(Entry.comparingByValue())
.collect(toMap(Entry::getKey, Entry::getValue,
(e1,e2) -> e1, LinkedHashMap::new));