首页 文章

按值对 Map <键,值>进行排序

提问于
浏览
1440

我是Java的新手,经常发现我需要对值进行 Map<Key, Value> 排序 .

由于值不是唯一的,我发现自己将 keySet 转换为 array ,并通过 array sort 对该数组进行排序,其中 custom comparator 对与键关联的值进行排序 .

有没有更简单的方法?

30 回答

  • 11

    一些简单的更改,以便使用具有重复值的对的排序映射 . 在比较方法(类ValueComparator)中,当值相等时,不返回0但返回比较2个键的结果 . 键在 Map 中是不同的,因此您可以成功保留重复值(顺便按键排序) . 所以上面的例子可以像这样修改:

    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 ((String)a).compareTo((String)b);
            } else {
              return -1;
            }
          }
        }
    
  • 9

    主要问题 . 如果您使用第一个答案(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
        }
    
  • 175

    这太复杂了 . Map 不应该按照Value对它们进行排序 . 最简单的方法是创建自己的类,以满足您的要求 .

    在示例中,您应该在*所在的位置添加TreeMap比较器 . 但是通过java API,它只为比较器提供键,而不是值 . 此处所述的所有示例均基于2个 Map . 一个哈希和一个新树 . 这很奇怪 .

    这个例子:

    Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);
    

    因此,以这种方式将 Map 更改为集合:

    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;
        }
    }
    

    和比较者类:

    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()
            );
    }
    
  • 5

    根据上下文,使用 java.util.LinkedHashMap<T> 来记住项目放入 Map 的顺序 . 否则,如果您需要根据其自然顺序对值进行排序,我建议您维护一个单独的List,可以通过 Collections.sort() 进行排序 .

  • 25

    创建自定义比较器并在创建新TreeMap对象时使用它 .

    class MyComparator implements Comparator<Object> {
    
        Map<String, Integer> map;
    
        public MyComparator(Map<String, Integer> map) {
            this.map = map;
        }
    
        public int compare(Object o1, Object o2) {
    
            if (map.get(o2) == map.get(o1))
                return 1;
            else
                return ((Integer) map.get(o2)).compareTo((Integer)     
                                                                map.get(o1));
    
        }
    }
    

    在主函数中使用以下代码

    Map<String, Integer> lMap = new HashMap<String, Integer>();
        lMap.put("A", 35);
        lMap.put("B", 75);
        lMap.put("C", 50);
        lMap.put("D", 50);
    
        MyComparator comparator = new MyComparator(lMap);
    
        Map<String, Integer> newMap = new TreeMap<String, Integer>(comparator);
        newMap.putAll(lMap);
        System.out.println(newMap);
    

    输出:

    {B=75, D=50, C=50, A=35}
    
  • 30

    而不是使用 Collections.sort ,我建议使用 Arrays.sort . 实际上 Collections.sort 的作用是这样的:

    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]);
        }
    }
    

    它只是在列表中调用 toArray 然后使用 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;
    }
    
  • 14

    要使用Java 8中的新功能实现此目的:

    import static java.util.Map.Entry.comparingByValue;
    import static java.util.stream.Collectors.toList;
    
    <K, V> List<Entry<K, V>> sort(Map<K, V> map, Comparator<? super V> comparator) {
        return map.entrySet().stream().sorted(comparingByValue(comparator)).collect(toList());
    }
    

    条目按照给定的比较器的值排序 . 或者,如果您的值可以相互比较,则不需要明确的比较器:

    <K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) {
        return map.entrySet().stream().sorted(comparingByValue()).collect(toList());
    }
    

    返回的列表是调用此方法时给定映射的快照,因此两者都不会反映对另一个的后续更改 . 对于 Map 的实时可迭代视图:

    <K, V extends Comparable<? super V>> Iterable<Entry<K, V>> sort(Map<K, V> map) {
        return () -> map.entrySet().stream().sorted(comparingByValue()).iterator();
    }
    

    返回的iterable在每次迭代时都会创建给定映射的新快照,因此除非进行并发修改,否则它将始终反映映射的当前状态 .

  • 407

    由于 TreeMap<> does not work 对于可以相等的值,我使用了这个:

    private <K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map)     {
        List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
        Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
            public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });
    
        return list;
    }
    

    您可能希望将 list 放在 LinkedHashMap 中,但如果您're only going to iterate over it right away, that'多余的......

  • 7

    基于@devinmoore代码,使用泛型并支持升序和降序排序的 Map 排序方法 .

    /**
     * Sort a map by it's keys in ascending order. 
     *  
     * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
     * @author Maxim Veksler
     */
    public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map) {
        return sortMapByKey(map, SortingOrder.ASCENDING);
    }
    
    /**
     * Sort a map by it's values in ascending order.
     *  
     * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
     * @author Maxim Veksler
     */
    public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map) {
        return sortMapByValue(map, SortingOrder.ASCENDING);
    }
    
    /**
     * Sort a map by it's keys.
     *  
     * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
     * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
     * @author Maxim Veksler
     */
    public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map, final SortingOrder sortingOrder) {
        Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
            public int compare(Entry<K, V> o1, Entry<K, V> o2) {
                return comparableCompare(o1.getKey(), o2.getKey(), sortingOrder);
            }
        };
    
        return sortMap(map, comparator);
    }
    
    /**
     * Sort a map by it's values.
     *  
     * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
     * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
     * @author Maxim Veksler
     */
    public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map, final SortingOrder sortingOrder) {
        Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
            public int compare(Entry<K, V> o1, Entry<K, V> o2) {
                return comparableCompare(o1.getValue(), o2.getValue(), sortingOrder);
            }
        };
    
        return sortMap(map, comparator);
    }
    
    @SuppressWarnings("unchecked")
    private static <T> int comparableCompare(T o1, T o2, SortingOrder sortingOrder) {
        int compare = ((Comparable<T>)o1).compareTo(o2);
    
        switch (sortingOrder) {
        case ASCENDING:
            return compare;
        case DESCENDING:
            return (-1) * compare;
        }
    
        return 0;
    }
    
    /**
     * Sort a map by supplied comparator logic.
     *  
     * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
     * @author Maxim Veksler
     */
    public static <K, V> LinkedHashMap<K, V> sortMap(final Map<K, V> map, final Comparator<Map.Entry<K, V>> comparator) {
        // Convert the map into a list of key,value pairs.
        List<Map.Entry<K, V>> mapEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet());
    
        // Sort the converted list according to supplied comparator.
        Collections.sort(mapEntries, comparator);
    
        // Build a new ordered map, containing the same entries as the old map.  
        LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(map.size() + (map.size() / 20));
        for(Map.Entry<K, V> entry : mapEntries) {
            // We iterate on the mapEntries list which is sorted by the comparator putting new entries into 
            // the targeted result which is a sorted map. 
            result.put(entry.getKey(), entry.getValue());
        }
    
        return result;
    }
    
    /**
     * Sorting order enum, specifying request result sort behavior.
     * @author Maxim Veksler
     *
     */
    public static enum SortingOrder {
        /**
         * Resulting sort will be from smaller to biggest.
         */
        ASCENDING,
        /**
         * Resulting sort will be from biggest to smallest.
         */
        DESCENDING
    }
    
  • 14

    虽然我同意对 Map 进行排序的不断需要可能是一种气味,但我认为以下代码是最简单的方法,而不使用不同的数据结构 .

    public class MapUtilities {
    
    public static <K, V extends Comparable<V>> List<Entry<K, V>> sortByValue(Map<K, V> map) {
        List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(map.entrySet());
        Collections.sort(entries, new ByValue<K, V>());
        return entries;
    }
    
    private static class ByValue<K, V extends Comparable<V>> implements Comparator<Entry<K, V>> {
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return o1.getValue().compareTo(o2.getValue());
        }
    }
    

    }

    这是一个令人尴尬的不完整的单元测试:

    public class MapUtilitiesTest extends TestCase {
    public void testSorting() {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);
    
        List<Map.Entry<String, Integer>> 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对象的排序列表,您可以从中获取键和值 .

  • 11

    我已经合并了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;
        }
    }
    
  • 3

    这个问题已经有很多答案了,但是没有一个给我提供了我正在寻找的东西,一个 Map 实现返回按关联值排序的键和条目,并将此属性维护为键,并在映射中修改值 . 两个other questions特别要求这个 .

    我编写了一个通用的友好示例来解决这个用例 . 此实现不遵守Map接口的所有 Contract ,例如反映从原始对象中的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.
     * <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;
        }
    }
    
  • 17

    确定斯蒂芬的解决方案非常棒,但对于那些不能使用 Guava 的人来说:

    这是我按值排序 Map 的解决方案 . 此解决方案处理两倍相同值的情况等...

    // If you want to sort a map by value, and if there can be twice the same value:
    
    // here is your original map
    Map<String,Integer> mapToSortByVa