我是Java的新手,经常发现我需要对Map
值进行排序.
由于值不是唯一的,我发现自己将其转换keySet
为a array
,并通过数组排序对该数组进行排序,并使用自定义比较器对与键关联的值进行排序.
有没有更简单的方法?
这是一个通用友好版本:
public class MapUtil { public static> Map sortByValue(Map map) { List > list = new ArrayList<>(map.entrySet()); list.sort(Entry.comparingByValue()); Map result = new LinkedHashMap<>(); for (Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } }
此代码可以以多种方式中断.如果您打算使用提供的代码,请务必阅读注释以了解其含义.例如,不能再通过其密钥检索值.(get
总是回来null
.)
它似乎比上述所有内容容易得多.使用TreeMap如下:
public class Testing { public static void main(String[] args) { HashMapmap = new HashMap (); ValueComparator bvc = new ValueComparator(map); TreeMap sorted_map = new TreeMap (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 { Map base; public ValueComparator(Map 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> sorted = map.entrySet().stream() .sorted(Map.Entry.comparingByValue());
这将允许您使用按值的升序排序的条目.如果要降序值,只需反转比较器:
Stream> sorted = map.entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
如果值不具有可比性,则可以传递显式比较器:
Stream> sorted = map.entrySet().stream() .sorted(Map.Entry.comparingByValue(comparator));
然后,您可以继续使用其他流操作来使用数据.例如,如果您想要新地图中的前10名:
MaptopTen = 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 Guava来执行此操作 - 如果您的值是Comparable
可以使用的话
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))
这将为地图创建一个函数(对象)[将任何键作为输入,返回相应的值],然后对它们[值]应用自然(可比较)排序.
如果他们没有可比性,那么你需要做一些事情
valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map))
这些可以应用于TreeMap(作为Ordering
extends Comparator
),或者在某些排序后应用于LinkedHashMap
注意:如果您打算使用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,V> extends TreeMap { //A map for doing lookups on the keys for comparison so we don't get infinite loops private final Map valueMap; ValueComparableMap(final Ordering super V> partialValueOrdering) { this(partialValueOrdering, new HashMap ()); } private ValueComparableMap(Ordering super V> partialValueOrdering, HashMap 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 map = new ValueComparableMap (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进行排序.但在此之前,我们检查哈希映射以查看密钥实际上并不重复.此外,我们创建的比较器还将包含密钥,以便重复值不会删除非重复键(由于==比较).这两项对于确保保留地图合同至关重要 ; 如果你认为你不想那样,那么你几乎就是完全颠倒了地图(to Map
).
需要将构造函数调用为
new ValueComparableMap(Ordering.natural()); //or new ValueComparableMap(Ordering.from(comparator));
来自http://www.programmersheaven.com/download/49349/download.aspx
private staticMap sortByValue(Map map) { List > list = new LinkedList<>(map.entrySet()); Collections.sort(list, new Comparator
使用Java 8,您可以使用流api以非常简单的方式执行此操作:
MapsortedMap = map.entrySet().stream() .sorted(Entry.comparingByValue()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
对键进行排序需要比较器查找每个比较的每个值.一个更具可伸缩性的解决方案将直接使用entrySet,因为这样的值将立即可用于每次比较(尽管我没有用数字来支持).
这是这种东西的通用版本:
public static> List getKeysSortedByValue(Map map) { final int size = map.size(); final List > list = new ArrayList >(size); list.addAll(map.entrySet()); final ValueComparator cmp = new ValueComparator (); Collections.sort(list, cmp); final List keys = new ArrayList (size); for (int i = 0; i < size; i++) { keys.set(i, list.get(i).getKey()); } return keys; } private static final class ValueComparator > implements Comparator > { public int compare(Map.Entry, V> o1, Map.Entry, V> o2) { return o1.getValue().compareTo(o2.getValue()); } }
有一些方法可以减少上述解决方案的内存轮换.例如,创建的第一个ArrayList可以重新用作返回值; 这需要抑制一些泛型警告,但对于可重用的库代码可能是值得的.此外,不必在每次调用时重新分配比较器.
这是一个更有效但尽管不那么有吸引力的版本:
public static> List getKeysSortedByValue2(Map map) { final int size = map.size(); final List reusedList = new ArrayList(size); final List > meView = reusedList; meView.addAll(map.entrySet()); Collections.sort(meView, SINGLE); final List 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();
最后,如果您需要不断访问已排序的信息(而不是仅仅偶尔对其进行排序),则可以使用其他多地图.如果您需要更多详细信息,请告诉我们......
commons-collections库包含一个名为TreeBidiMap的解决方案.或者,您可以查看Google Collections API.它有你可以使用的TreeMultimap.
如果你不想使用这些框架......它们带有源代码.
我已经查看了给定的答案,但是当很多键具有相同的值时,其中很多都比需要的更复杂或者删除了地图元素.
这是一个我觉得更合适的解决方案:
public static> Map sortByValues(final Map map) { Comparator valueComparator = new Comparator () { public int compare(K k1, K k2) { int compare = map.get(k2).compareTo(map.get(k1)); if (compare == 0) return 1; else return compare; } }; Map sortedByValues = new TreeMap (valueComparator); sortedByValues.putAll(map); return sortedByValues; }
请注意,地图从最高值到最低值排序.
要使用Java 8中的新功能实现此目的:
import static java.util.Map.Entry.comparingByValue; import static java.util.stream.Collectors.toList;List > sort(Map map, Comparator super V> comparator) { return map.entrySet().stream().sorted(comparingByValue(comparator)).collect(toList()); }
条目按照给定的比较器的值排序.或者,如果您的值可以相互比较,则不需要显式比较器:
> List > sort(Map map) { return map.entrySet().stream().sorted(comparingByValue()).collect(toList()); }
返回的列表是调用此方法时给定映射的快照,因此两者都不会反映对另一个的后续更改.对于地图的实时可迭代视图:
> Iterable > sort(Map map) { return () -> map.entrySet().stream().sorted(comparingByValue()).iterator(); }
返回的iterable在每次迭代时都会创建给定映射的新快照,因此除非进行并发修改,否则它将始终反映映射的当前状态.
创建自定义比较器并在创建新TreeMap对象时使用它.
class MyComparator implements Comparator
在主函数中使用以下代码
MaplMap = new HashMap (); lMap.put("A", 35); lMap.put("B", 75); lMap.put("C", 50); lMap.put("D", 50); MyComparator comparator = new MyComparator(lMap); Map newMap = new TreeMap (comparator); newMap.putAll(lMap); System.out.println(newMap);
输出:
{B=75, D=50, C=50, A=35}
虽然我同意对地图进行排序的不断需要可能是一种气味,但我认为以下代码是最简单的方法,而不使用不同的数据结构.
public class MapUtilities { public static> List > sortByValue(Map map) { List > entries = new ArrayList >(map.entrySet()); Collections.sort(entries, new ByValue ()); return entries; } private static class ByValue > implements Comparator > { public int compare(Entry o1, Entry o2) { return o1.getValue().compareTo(o2.getValue()); } }
}
这是一个令人尴尬的不完整的单元测试:
public class MapUtilitiesTest extends TestCase { public void testSorting() { HashMapmap = new HashMap (); map.put("One", 1); map.put("Two", 2); map.put("Three", 3); List > sorted = MapUtilities.sortByValue(map); assertEquals("First", "One", sorted.get(0).getKey()); assertEquals("Second", "Two", sorted.get(1).getKey()); assertEquals("Third", "Three", sorted.get(2).getKey()); }
}
结果是Map.Entry对象的排序列表,您可以从中获取键和值.
使用通用比较器,例如:
final class MapValueComparator> implements Comparator { private Map map; private MapValueComparator() { super(); } public MapValueComparator(Map map) { this(); this.map = map; } public int compare(K o1, K o2) { return map.get(o1).compareTo(map.get(o2)); } }
当你有两个相等的项目时,最多投票的答案不起作用.TreeMap保留相同的值.
exmaple:未分类的地图
key/value: D/67.3 key/value: A/99.5 key/value: B/67.4 key/value: C/67.5 key/value: E/99.5
结果
key/value: A/99.5 key/value: C/67.5 key/value: B/67.4 key/value: D/67.3
离开E !!
对我来说,它可以很好地调整比较器,如果它等于不返回0但是-1.
在示例中:
class ValueComparator实现Comparator {
地图基地; public ValueComparator(Map base){this.base = base; }
public int compare(Object a,Object b){
if((Double)base.get(a) < (Double)base.get(b)) { return 1; } else if((Double)base.get(a) == (Double)base.get(b)) { return -1; } else { return -1; }}}
现在它返回:
未分类的地图:
key/value: D/67.3 key/value: A/99.5 key/value: B/67.4 key/value: C/67.5 key/value: E/99.5
结果:
key/value: A/99.5 key/value: E/99.5 key/value: C/67.5 key/value: B/67.4 key/value: D/67.3
作为对Aliens的回应(2011年11月22日):我使用这个解决方案来获取Integer Id和名称的地图,但是这个想法是一样的,所以上面的代码可能不正确(我会在测试中写出来)并给出正确的代码),这是基于上述解决方案的Map排序代码:
package nl.iamit.util; import java.util.Comparator; import java.util.Map; public class Comparators { public static class MapIntegerStringComparator implements Comparator { Mapbase; public MapIntegerStringComparator(Map base) { this.base = base; } public int compare(Object a, Object b) { int compare = ((String) base.get(a)) .compareTo((String) base.get(b)); if (compare == 0) { return -1; } return compare; } } }
这是测试类(我刚测试过,这适用于Integer,String Map:
package test.nl.iamit.util; import java.util.HashMap; import java.util.TreeMap; import nl.iamit.util.Comparators; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; public class TestComparators { @Test public void testMapIntegerStringComparator(){ HashMapunSoretedMap = new HashMap (); Comparators.MapIntegerStringComparator bvc = new Comparators.MapIntegerStringComparator( unSoretedMap); TreeMap sorted_map = new TreeMap (bvc); //the testdata: unSoretedMap.put(new Integer(1), "E"); unSoretedMap.put(new Integer(2), "A"); unSoretedMap.put(new Integer(3), "E"); unSoretedMap.put(new Integer(4), "B"); unSoretedMap.put(new Integer(5), "F"); sorted_map.putAll(unSoretedMap); Object[] targetKeys={new Integer(2),new Integer(4),new Integer(3),new Integer(1),new Integer(5) }; Object[] currecntKeys=sorted_map.keySet().toArray(); assertArrayEquals(targetKeys,currecntKeys); } }
这是地图比较器的代码:
public static class MapStringDoubleComparator implements Comparator { Mapbase; public MapStringDoubleComparator(Map base) { this.base = base; } //note if you want decending in stead of ascending, turn around 1 and -1 public int compare(Object a, Object b) { if ((Double) base.get(a) == (Double) base.get(b)) { return 0; } else if((Double) base.get(a) < (Double) base.get(b)) { return -1; }else{ return 1; } } }
这是对此的测试用例:
@Test public void testMapStringDoubleComparator(){ HashMapunSoretedMap = new HashMap (); Comparators.MapStringDoubleComparator bvc = new Comparators.MapStringDoubleComparator( unSoretedMap); TreeMap sorted_map = new TreeMap (bvc); //the testdata: unSoretedMap.put("D",new Double(67.3)); unSoretedMap.put("A",new Double(99.5)); unSoretedMap.put("B",new Double(67.4)); unSoretedMap.put("C",new Double(67.5)); unSoretedMap.put("E",new Double(99.5)); sorted_map.putAll(unSoretedMap); Object[] targetKeys={"D","B","C","E","A"}; Object[] currecntKeys=sorted_map.keySet().toArray(); assertArrayEquals(targetKeys,currecntKeys); }
cource你可以使它更通用,但我只需要1个案例(地图)
而不是Collections.sort
像我建议的那样使用Arrays.sort
.实际上Collections.sort
是这样的:
public static> void sort(List list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator i = list.listIterator(); for (int j=0; j 它只是调用
toArray
列表然后使用Arrays.sort
.这样,所有映射条目将被复制三次:一次从映射到临时列表(无论是LinkedList还是ArrayList),然后到临时数组,最后到新映射.我的解决方案省略了这一步,因为它不会创建不必要的LinkedList.这是代码,通用友好和性能最佳:
public static> Map sortByValue(Map map) { @SuppressWarnings("unchecked") Map.Entry [] array = map.entrySet().toArray(new Map.Entry[map.size()]); Arrays.sort(array, new Comparator >() { public int compare(Map.Entry e1, Map.Entry e2) { return e1.getValue().compareTo(e2.getValue()); } }); Map result = new LinkedHashMap (); for (Map.Entry entry : array) result.put(entry.getKey(), entry.getValue()); return result; }
16> Roger..:这是Anthony的答案的变体,如果存在重复值,则不起作用:
public static> Map sortMapByValues(final Map map) { Comparator valueComparator = new Comparator () { public int compare(K k1, K k2) { final V v1 = map.get(k1); final V v2 = map.get(k2); /* Not sure how to handle nulls ... */ if (v1 == null) { return (v2 == null) ? 0 : 1; } int compare = v2.compareTo(v1); if (compare != 0) { return compare; } else { Integer h1 = k1.hashCode(); Integer h2 = k2.hashCode(); return h2.compareTo(h1); } } }; Map sortedByValues = new TreeMap (valueComparator); sortedByValues.putAll(map); return sortedByValues; } 请注意,它是如何处理空值的.
这种方法的一个重要优点是它实际上返回了一个Map,与此处提供的其他一些解决方案不同.
17> Nilesh Jadav..:最佳方法
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; public class OrderByValue { public static void main(String a[]){ Mapmap = new HashMap (); map.put("java", 20); map.put("C++", 45); map.put("Unix", 67); map.put("MAC", 26); map.put("Why this kolavari", 93); Set > set = map.entrySet(); List > list = new ArrayList >(set); Collections.sort( list, new Comparator >() { public int compare( Map.Entry o1, Map.Entry o2 ) { return (o1.getValue()).compareTo( o2.getValue() );//Ascending order //return (o2.getValue()).compareTo( o1.getValue() );//Descending order } } ); for(Map.Entry entry:list){ System.out.println(entry.getKey()+" ==== "+entry.getValue()); } }} 产量
java ==== 20 MAC ==== 26 C++ ==== 45 Unix ==== 67 Why this kolavari ==== 93
18> 小智..:主要问题.如果您使用第一个答案(Google将您带到此处),请更改比较器以添加相等的子句,否则您无法通过键从sorted_map获取值:
public int compare(String a, String b) { if (base.get(a) > base.get(b)) { return 1; } else if (base.get(a) < base.get(b)){ return -1; } return 0; // returning 0 would merge keys }
19> David Bleckm..:这个问题已经有很多答案了,但没有一个提供我正在寻找的东西,一个地图实现返回按关联值排序的键和条目,并将此属性维护为键,并在地图中修改值.两个其他 问题询问此特别.
我编写了一个通用的友好示例来解决这个用例.此实现不遵守Map接口的所有合同,例如反映从原始对象中的keySet()和entrySet()返回的集合中的值更改和删除.我觉得这样的解决方案太大而无法包含在Stack Overflow答案中.如果我设法创建一个更完整的实现,也许我会将它发布到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. ** Several contracts of the Map interface are not satisfied by this minimal * implementation. */ public class ValueSortedMap
extends HashMap { protected Map > 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 >(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 keys = new ArrayList (); 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 keySet() { LinkedHashSet ret = new LinkedHashSet (size()); for (V v : valueToKeysMap.keySet()) { Collection keys = valueToKeysMap.get(v); ret.addAll(keys); } return ret; } public Set > entrySet() { LinkedHashSet > ret = new LinkedHashSet >(size()); for (Collection keys : valueToKeysMap.values()) { for (final K k : keys) { final V v = get(k); ret.add(new Map.Entry () { public K getKey() { return k; } public V getValue() { return v; } public V setValue(V v) { throw new UnsupportedOperationException(); } }); } } return ret; } }
20> Ryan Delucch..:根据上下文,使用
java.util.LinkedHashMap
哪个记住项目放入地图的顺序.否则,如果您需要根据其自然顺序对值进行排序,我建议您维护一个可以进行排序的单独ListCollections.sort()
.
21> 小智..:这太复杂了.地图不应该按照Value对它们进行排序.最简单的方法是创建自己的类,以满足您的要求.
在示例中,您应该在*所在的位置添加TreeMap比较器.但是通过java API,它只为比较器提供键,而不是值.此处所述的所有示例均基于2个地图.一个哈希和一个新树.这很奇怪.
这个例子:
Mapmap = new TreeMap (*); 因此,将地图更改为一组:
ResultComparator rc = new ResultComparator(); Setset = new TreeSet (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; } }和比较者类:
public class ResultsComparator implements Comparator{ 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() ); }
22> malix..:由于TreeMap <>不适用于可以相等的值,我使用了这个:
private> List > sort(Map map) { List > list = new LinkedList >(map.entrySet()); Collections.sort(list, new Comparator >() { public int compare(Map.Entry o1, Map.Entry o2) { return o1.getValue().compareTo(o2.getValue()); } }); return list; } 您可能希望将列表放在LinkedHashMap中,但如果您只是立即迭代它,这是多余的......
23> Pankaj Singh..:迟到。
随着Java-8的出现,我们可以以非常简单/简洁的方式将流用于数据处理。您可以使用流按值对映射条目进行排序,并创建一个LinkedHashMap来保留插入顺序的迭代。
例如:
LinkedHashMap sortedByValueMap = map.entrySet().stream() .sorted(comparing(Entry::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::getValue).thenComparing(Entry::getKey) 与
comparing(Entry::getValue).thenComparing(Entry::getKey).reversed()