我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。
由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。
有没有更简单的方法?
我对Java比较陌生,经常发现需要对值进行Map<Key,Value>排序。
由于这些值不是唯一的,我发现自己将keySet转换为一个数组,并使用自定义比较器通过数组排序对该数组进行排序,该比较器根据与该键关联的值进行排序。
有没有更简单的方法?
当前回答
commons集合库包含一个名为TreeBidiMap的解决方案。或者,你可以看看谷歌收藏API。它有你可以使用的TreeMultimap。
如果你不想使用这些框架。。。它们带有源代码。
其他回答
当我面对这个问题时,我只是在旁边创建一个列表。如果您将它们放在一个自定义的Map实现中,它会有一种很好的感觉……您可以使用类似以下的方式,仅在需要时执行排序。(注意:我还没有真正测试过这个,但它可以编译……可能是某个地方的一个愚蠢的小bug)
(如果您希望按键和值对其进行排序,请让类扩展TreeMap,不要定义访问器方法,并让赋值函数调用super.xxxxx而不是map_.xxxx)
package com.javadude.sample;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SortedValueHashMap<K, V> implements Map<K, V> {
private Map<K, V> map_ = new HashMap<K, V>();
private List<V> valueList_ = new ArrayList<V>();
private boolean needsSort_ = false;
private Comparator<V> comparator_;
public SortedValueHashMap() {
}
public SortedValueHashMap(List<V> valueList) {
valueList_ = valueList;
}
public List<V> sortedValues() {
if (needsSort_) {
needsSort_ = false;
Collections.sort(valueList_, comparator_);
}
return valueList_;
}
// mutators
public void clear() {
map_.clear();
valueList_.clear();
needsSort_ = false;
}
public V put(K key, V value) {
valueList_.add(value);
needsSort_ = true;
return map_.put(key, value);
}
public void putAll(Map<? extends K, ? extends V> m) {
map_.putAll(m);
valueList_.addAll(m.values());
needsSort_ = true;
}
public V remove(Object key) {
V value = map_.remove(key);
valueList_.remove(value);
return value;
}
// accessors
public boolean containsKey(Object key) { return map_.containsKey(key); }
public boolean containsValue(Object value) { return map_.containsValue(value); }
public Set<java.util.Map.Entry<K, V>> entrySet() { return map_.entrySet(); }
public boolean equals(Object o) { return map_.equals(o); }
public V get(Object key) { return map_.get(key); }
public int hashCode() { return map_.hashCode(); }
public boolean isEmpty() { return map_.isEmpty(); }
public Set<K> keySet() { return map_.keySet(); }
public int size() { return map_.size(); }
public Collection<V> values() { return map_.values(); }
}
我合并了user157196和Carter Page的解决方案:
class MapUtil {
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue( Map<K, V> map ){
ValueComparator<K,V> bvc = new ValueComparator<K,V>(map);
TreeMap<K,V> sorted_map = new TreeMap<K,V>(bvc);
sorted_map.putAll(map);
return sorted_map;
}
}
class ValueComparator<K, V extends Comparable<? super V>> implements Comparator<K> {
Map<K, V> base;
public ValueComparator(Map<K, V> base) {
this.base = base;
}
public int compare(K a, K b) {
int result = (base.get(a).compareTo(base.get(b)));
if (result == 0) result=1;
// returning 0 would merge keys
return result;
}
}
这个问题已经有了很多答案,但没有一个能为我提供我想要的,一个返回按关联值排序的键和条目的映射实现,并在映射中修改键和值时维护这个属性。另外两个问题对此提出了具体要求。
我编写了一个通用友好的示例来解决这个用例。此实现不遵守Map接口的所有约定,例如反映原始对象中keySet()和entrySet()返回的集合中的值更改和删除。我觉得这样的解决方案太大,无法包含在堆栈溢出的答案中。如果我成功地创建了一个更完整的实现,也许我会将其发布到Github,然后在这个答案的更新版本中链接到它。
import java.util.*;
/**
* A map where {@link #keySet()} and {@link #entrySet()} return sets ordered
* by associated values based on the the comparator provided at construction
* time. The order of two or more keys with identical values is not defined.
* <p>
* Several contracts of the Map interface are not satisfied by this minimal
* implementation.
*/
public class ValueSortedMap<K, V> extends HashMap<K, V> {
protected Map<V, Collection<K>> valueToKeysMap;
// uses natural order of value object, if any
public ValueSortedMap() {
this((Comparator<? super V>) null);
}
public ValueSortedMap(Comparator<? super V> valueComparator) {
this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator);
}
public boolean containsValue(Object o) {
return valueToKeysMap.containsKey(o);
}
public V put(K k, V v) {
V oldV = null;
if (containsKey(k)) {
oldV = get(k);
valueToKeysMap.get(oldV).remove(k);
}
super.put(k, v);
if (!valueToKeysMap.containsKey(v)) {
Collection<K> keys = new ArrayList<K>();
keys.add(k);
valueToKeysMap.put(v, keys);
} else {
valueToKeysMap.get(v).add(k);
}
return oldV;
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public V remove(Object k) {
V oldV = null;
if (containsKey(k)) {
oldV = get(k);
super.remove(k);
valueToKeysMap.get(oldV).remove(k);
}
return oldV;
}
public void clear() {
super.clear();
valueToKeysMap.clear();
}
public Set<K> keySet() {
LinkedHashSet<K> ret = new LinkedHashSet<K>(size());
for (V v : valueToKeysMap.keySet()) {
Collection<K> keys = valueToKeysMap.get(v);
ret.addAll(keys);
}
return ret;
}
public Set<Map.Entry<K, V>> entrySet() {
LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size());
for (Collection<K> keys : valueToKeysMap.values()) {
for (final K k : keys) {
final V v = get(k);
ret.add(new Map.Entry<K,V>() {
public K getKey() {
return k;
}
public V getValue() {
return v;
}
public V setValue(V v) {
throw new UnsupportedOperationException();
}
});
}
}
return ret;
}
}
迟到。
随着Java-8的出现,我们可以以非常简单/简洁的方式使用流进行数据操作。您可以使用流按值对映射条目进行排序,并创建一个LinkedHashMap,以保留插入顺序迭代。
Eg:
LinkedHashMap sortedByValueMap = map.entrySet().stream()
.sorted(comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)) //first sorting by Value, then sorting by Key(entries with same value)
.collect(LinkedHashMap::new,(map,entry) -> map.put(entry.getKey(),entry.getValue()),LinkedHashMap::putAll);
对于反向排序,请替换:
comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)
with
comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()
这太复杂了。地图不应该按价值排序。最简单的方法是创建自己的类,以满足您的需求。
在下面的示例中,您应该在*所在的位置添加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()
);
}