当两个以上的值具有相同的排序属性时,按值排序Java TreeMap不起作用

时间:2011-10-06 10:28:51

标签: java sorting treemap

我想基于某些属性值对Java TreeMap进行排序。具体来说,我想根据TreeMap<Integer, Hashset<Integer>>的大小对Hashset<Integer>进行排序。为此,我做了以下几点:

比较类:

private static class ValueComparer implements Comparator<Integer> {
    private Map<Integer, HashSet<Integer>>  map = null;
    public ValueComparer (Map<Integer, HashSet<Integer>> map){
        super();
        this.map = map;
    }

@Override
    public int compare(Integer o1, Integer o2) {
        HashSet<Integer> h1 = map.get(o1);
        HashSet<Integer> h2 = map.get(o2);

        int compare = h2.size().compareTo(h1.size());

        if (compare == 0 && o1!=o2){
            return -1;
        }
        else {
            return compare;
        }
    }
}

用法示例:

TreeMap<Integer, HashSet<Integer>> originalMap = new TreeMap<Integer, HashSet<Integer>>();

//load keys and values into map

ValueComparer comp = new ValueComparer(originalMap);
TreeMap<Integer, HashSet<Integer>> sortedMap = new TreeMap<Integer, HashSet<Integer>>(comp);
sortedMap.putAll(originalMap);

问题:

originalMap包含两个以上相同大小的值时,这不起作用。对于其他情况,它可以正常工作。当映射中的两个以上的值具有相同的大小时,新的sorted-map中的第三个值为null,并在我尝试访问它时抛出NullPointerException。

我无法弄清问题是什么。如果有人可以指出,那就好了。

更新 以下是两个值具有相同大小时的示例:http://ideone.com/iFD9c 在上面的示例中,如果取消注释第52-54行,则此代码将失败 - 这就是我的问题所在。

6 个答案:

答案 0 :(得分:7)

更新:您无法仅从-1返回ValueComparator,因为您希望避免重复密钥被删除。查看Comparator.compare的合同。


当您将Comparator传递给TreeMap时,您会计算一个(“新”)位置来放置该条目。 否(计算)密钥可以在TreeMap中多次存在。


如果您想按照值的大小对orginalMap进行排序,您可以执行以下操作:

public static void main(String[] args) throws Exception {

    TreeMap<Integer, HashSet<Integer>> originalMap = 
        new TreeMap<Integer, HashSet<Integer>>();

    originalMap.put(0, new HashSet<Integer>() {{ add(6); add(7); }});
    originalMap.put(1, new HashSet<Integer>() {{ add(6); }});
    originalMap.put(2, new HashSet<Integer>() {{ add(9); add(8); }});


    ArrayList<Map.Entry<Integer, HashSet<Integer>>> list = 
        new ArrayList<Map.Entry<Integer, HashSet<Integer>>>();
    list.addAll(originalMap.entrySet());

    Collections.sort(list, new Comparator<Map.Entry<Integer,HashSet<Integer>>>(){
        public int compare(Map.Entry<Integer, HashSet<Integer>> o1,
                           Map.Entry<Integer, HashSet<Integer>> o2) {

            Integer size1 = (Integer) o1.getValue().size();
            Integer size2 = (Integer) o2.getValue().size();
            return size2.compareTo(size1);
        }
    });

    System.out.println(list);
}

答案 1 :(得分:1)

您的比较者不尊重比较者合同:如果compare(o1, o2) < 0,则compare(o2, o1)应为> 0。当两个大小相同且整数不相同时,您必须找到一种比较元素的确定性方法。在这种情况下,您可以使用整数的System.identityHashCode()来比较它们。

那就是说,我真的很想知道你可以用这样的地图做什么:你不能创建新的整数并使用它们从地图中获取一个值,你不能修改它所拥有的集合。 / p>

旁注:您的比较器代码示例使用mapdata来引用相同的地图。

答案 2 :(得分:1)

您的比较器逻辑(我不确定我遵循为什么如果设置的大小相等但它们的键不同则返回-1)不应该影响当您调用{{1}时Map本身返回的内容}。

你是否肯定你没有在初始地图中插入空值?这段代码是什么样的?

答案 3 :(得分:1)

您只能通过键订购TreeMap。无法按值排序创建TreeMap,因为您将获得StackOverflowException。

想一想。要从树中获取元素,您需要执行比较,但要执行比较,您需要获取元素。

您必须在其他集合中对其进行排序或使用Tree,您必须将整数(从条目值)封装到条目键中,并使用从键中获取的整数来定义比较器。

答案 4 :(得分:0)

假设您不能使用带有Set返回0的比较器,这可能有效:将originalMap.entrySet()中的所有元素添加到ArrayList,然后使用{ArrayListValueComparer进行排序1}},根据需要将其更改为return 0

然后将已排序的ArrayList中的所有条目添加到LinkedHashMap

答案 5 :(得分:0)

我遇到了与原始海报类似的问题。我有一个TreeMap,我想对一个值进行排序。但是当我制作一个比较器来查看该值时,由于JB所谈论的比较器的破坏,我遇到了问题。我能够使用我的自定义比较器并仍然遵守合同。当我看到的valuse相等时,我又回到比较键。如果价值相等,我不关心订单。

    public int compare(String a, String b) {
    if(base.get(a)[0] == base.get(b)[0]){ //need to handle when they are equal
        return a.compareTo(b);
    }else if (base.get(a)[0] < base.get(b)[0]) {
        return -1;
    } else {
        return 1;
    } // returning 0 would merge keys