按值对Java Map进行排序

9

我正在寻找对Map<String, Integer>按照值进行排序的方法。我发现了这篇文章,它解决了我的排序问题,但并不完全适用。根据这篇文章,我编写了以下代码:

import java.util.*;

public class Sort {

    static class ValueComparator implements Comparator<String> {

        Map<String, Integer> base;

        ValueComparator(Map<String, Integer> base) {
            this.base = base;
        }

        @Override
        public int compare(String a, String b) {
            if (base.get(a) >= base.get(b)) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        ValueComparator vc = new ValueComparator(map);
        TreeMap<String, Integer> sorted = new TreeMap<String, Integer>(vc);
        map.put("A", 1);
        map.put("B", 2);
        sorted.putAll(map);
        for (String key : sorted.keySet()) {
            System.out.println(key + " : " + sorted.get(key)); // why null values here?
        }
        System.out.println(sorted.values()); // But we do have non-null values here!
    }
}

输出:

A : null
B : null
[1, 2]
BUILD SUCCESSFUL (total time: 0 seconds)

如您所见,从输出结果可以看出,get方法总是返回null。原因是我的ValueComparator.compare()方法从未返回0,我通过发布这篇文章找到了答案。
那篇文章中有人建议采用以下方法来解决null值问题:
        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 0;
            }
            return -1;  
        }

我已经测试了这段代码,发现它存在一个关键字合并的问题。换句话说,当值相等时,对应的关键字会被合并。

我还尝试了以下方法:

            public int compare(String a, String b) {
                if (a.equals(b)) return 0;
                if (base.get(a) >= base.get(b)) {
                    return 1;
                } else return -1;
            }

它还是不起作用。一些值仍然为null。此外,这种解决方法可能存在逻辑问题。

有人能够提出一个完全有效的解决方案吗?我希望按值排序函数和get方法可以同时运行。


2
为什么/如何按值对Map进行排序?我认为这甚至没有意义...难道不是列表和集合的作用吗?只需调用_Map.values()_并将结果添加到_TreeSet_或有序_List_中即可。 - jahroy
@jahroy 你如何将排序后的值与它们对应的键关联起来?我不想手动维护键和值之间的关系。 - Terry Li
好的...所以你希望根据值的顺序对键进行排序。我猜现在我明白你想要什么了。 - jahroy
在这种情况下,你应该只使用这个:return base.get(a).compareTo(base.get(b)) - jahroy
@jahroy 如果您有比使用映射更好的解决方案,那也很好 :) - Terry Li
我刚刚添加了一个答案... 你想使用 Integer.compareTo() - jahroy
4个回答

6
在您的比较函数中,当值相等时,应该比较键。这将确保具有相同值的不同键不会被“合并”,因为它消除了否则会比较相等的条目的歧义。
例如:
    @Override
    public int compare(String a, String b) {
        Integer x = base.get(a);
        Integer y = base.get(b);
        if (x.equals(y)) {
            return a.compareTo(b);
        }
        return x.compareTo(y);
    }

请注意,您上面的代码对于空值需要根据自己的策略进行修改。

但是,请注意,您按值排序的方法相当脆弱。您的“sorted”映射将不支持添加新条目,这可能会非常令人困惑。


4
base.get(a) ==  base.get(b)

这段代码通过引用比较装箱的 Integer

将其改为 base.get(a).equals(base.get(b)),它就可以正常工作了。


我不认为这是真正的问题 :) - Terry Li
@TerryLi:不会,== 运算符不会自动拆箱。 - SLaks

1

试试这个:

return base.get(a).compareTo(base.get(b));

为了证明您对于自动拆箱的错误观点,我们提供以下内容:
Integer a = new Integer(2);
Integer b = new Integer(2);
boolean isEqual = ( a == b );
System.out.println("equal: " + isEqual);
System.out.println("a: " + a);
System.out.println("b: " + b);

我的输出是:

equal: false
a: 2
b: 2

它具有相同的键合并问题。顺便说一下,您可以使用“==”直接比较Integer对象。 - Terry Li
好的观点!我不知道那个。但是即使使用正确的比较方法,我的问题仍然存在 :( - Terry Li

1

试试这个...

HashMap<String, Integer> h = new HashMap<String, Integer>();
h.put("z",30);
h.put("e",10);
h.put("b",20);
h.put("c",20);
List<Map.Entry> a = new ArrayList<Map.Entry>(h.entrySet());
Collections.sort(a,
         new Comparator() {
             public int compare(Object o1, Object o2) {
                 Map.Entry e1 = (Map.Entry) o1;
                 Map.Entry e2 = (Map.Entry) o2;
                 return ((Comparable) e1.getValue()).compareTo(e2.getValue());
             }
         });

for (Map.Entry e : a) {
        System.out.println(e.getKey() + " " + e.getValue());
}

输出:

e 10
b 20
c 20
z 30

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接