简体   繁体   English

TreeMap没有正确排序

[英]TreeMap not sorting properly

I am trying to sort a TreeMap according to "weight". 我试图根据“权重”对TreeMap进行排序。 But for some reason it is deleting entries with the same weight value even though the keys are different. 但由于某种原因,即使密钥不同,它也会删除具有相同权重值的条目。

Below is the code: 以下是代码:

class Edge  {
    int source;
    int destination;
    int weight;

    public Edge(int source, int destination, int weight) {
        this.source = source;
        this.destination = destination;
        this.weight = weight;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + destination;
        result = prime * result + source;
        result = prime * result + weight;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Edge other = (Edge) obj;
        if (destination != other.destination)
            return false;
        if (source != other.source)
            return false;
        if (weight != other.weight)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Edge [source=" + source + ", destination=" + destination + ", weight=" + weight + "]";
    }
}

Data of HashMap: HashMap的数据:

{Edge [source=0, destination=1, weight=5]=5, Edge [source=1, destination=2, weight=4]=4, Edge [source=2, destination=3, weight=5]=5, Edge [source=0, destination=3, weight=6]=6, Edge [source=0, destination=2, weight=3]=3, Edge [source=1, destination=3, weight=7]=7} {Edge [source = 0,destination = 1,weight = 5] = 5,Edge [source = 1,destination = 2,weight = 4] = 4,Edge [source = 2,destination = 3,weight = 5] = 5,Edge [source = 0,destination = 3,weight = 6] = 6,Edge [source = 0,destination = 2,weight = 3] = 3,Edge [source = 1,destination = 3,weight = 7] = 7}

Map<Edge, Integer> treemap = new TreeMap<>(new MyWeightComp());
    treemap.putAll(map);

Comparator of the Treemap: 树形图的比较:

 class MyWeightComp implements Comparator<Edge>{

    @Override
    public int compare(Edge e1, Edge e2) {
        return e1.weight-e2.weight;
    }
}

Data after sorting: 排序后的数据:

{Edge [source=0, destination=2, weight=3]=3, Edge [source=1, destination=2, weight=4]=4, Edge [source=0, destination=1, weight=5]=5, Edge [source=0, destination=3, weight=6]=6, Edge [source=1, destination=3, weight=7]=7} {Edge [source = 0,destination = 2,weight = 3] = 3,Edge [source = 1,destination = 2,weight = 4] = 4,Edge [source = 0,destination = 1,weight = 5] = 5,Edge [source = 0,destination = 3,weight = 6] = 6,Edge [source = 1,destination = 3,weight = 7] = 7}

So you can see that, for some reason the data with the same weight is getting deleted even though the key is a combination of source and destination. 因此,您可以看到,由于某种原因,即使密钥是源和目标的组合,也会删除具有相同权重的数据。

All the Maps delete duplicates and if compareTo returns 0, it is assumed to be the same key. 所有映射都删除重复项,如果compareTo返回0,则假定它是相同的密钥。

class MyWeightComp implements Comparator<Edge> {

    @Override
    public int compare(Edge e1, Edge e2) {
        int cmp = Integer.compare(e1.weight, e2.weight); // handle overflows.
        if (cmp == 0)
            cmp = Integer.compare(e1.source, e2.source);
        if (cmp == 0)
            cmp = Integer.compare(e1.destination, e2.destination);
        return cmp;
    }
}

If you have fields which are not important for sorting, you still have to choose an arbitrary but consistent ordering if you don't want them to be ignored for the purposes of duplicates. 如果您有对排序不重要的字段,您仍然必须选择任意但一致的排序,如果您不希望为重复目的忽略它们。

The key consistency you need to ensure is compare(a, b) == -compare(b, a) or more accurately sign(compare(a, b)) == -sign(compare(b, a)) 您需要确保的密钥一致性是compare(a, b) == -compare(b, a)或更准确地sign(compare(a, b)) == -sign(compare(b, a))

TreeMap compare keys using comparator. TreeMap使用比较器比较键。

Your comparator return 0 for two keys with same weight. 对于具有相同重量的两个键,比较器返回0 So from TreeMap perspective such keys are equal. 因此从TreeMap角度看,这些键是相同的。

This has already been answered by Peter and talex. Peter和talex已经回答了这个问题。 I will add a slightly deeper analysis, since you mentioned learning data structures. 我将添加一个稍微深入的分析,因为您提到了学习数据结构。

First of all, there is a key difference between List and Set / Map . 首先, ListSet / Map之间存在关键区别。 Lists can contain duplicates, Sets cannot contain duplicates, Maps cannot contain duplicate keys (this applies to standard Maps, not to eg multimaps ). 列表可以包含重复项,集合不能包含重复项,映射不能包含重复键(这适用于标准地图,而不适用于多重映射 )。 In fact, Sets are internally implemented using Maps. 实际上,集合是使用地图在内部实现的。

How does a Map decide, which item is a duplicate? Map如何确定哪个项目是重复的?

HashMap uses two functions Object.hashCode and Object.equals . HashMap使用两个函数Object.hashCodeObject.equals You can put print statements into these functions: 您可以将print语句放入这些函数中:

    System.out.println(String.format("Edge.hashCode.%d.%d.%d", source, destination, weight));
    System.out.println(String.format("Edge.equals.%d.%d.%d", source, destination, weight));

Let's assume following list of 7 Edges: 让我们假设以下7个边缘列表:

    List<Edge> edges = Arrays.asList(
            new Edge(0, 1, 5),
            new Edge(1, 2, 4),
            new Edge(2, 3, 5),
            new Edge(0, 3, 6),
            new Edge(0, 3, 6), // duplicate
            new Edge(0, 2, 3),
            new Edge(1, 3, 7)
    );

Now, lets put items into the HashMap : 现在,让我们将项目放入HashMap

    Map<Edge, Integer> hashMap = new HashMap<>();
    edges.forEach(edge -> hashMap.put(edge, edge.weight));
    hashMap.forEach((edge, value) -> System.out.printf("%s%n", edge));

The produced output shows, how the HashMap decides, which items are duplicates and which are not: 生成的输出显示HashMap如何决定哪些项重复,哪些不重复:

Edge.hashCode.0.1.5
Edge.hashCode.1.2.4
Edge.hashCode.2.3.5
Edge.hashCode.0.3.6
Edge.hashCode.0.3.6
Edge.equals.0.3.6
Edge.hashCode.0.2.3
Edge.hashCode.1.3.7

You can see, that the HashMap knew, that first four items were not duplicates, because they had different hashcodes. 您可以看到, HashMap知道,前四个项目不是重复项,因为它们具有不同的哈希码。 Fifth value had the same hashcode as fourth value and HashMap needed to confirm, that this was really the same Edge by using equals . 第五个值具有与第四个值相同的哈希码,并且HashMap需要确认,通过使用equals ,这实际上是相同的Edge。 HashMap will contain 6 items: HashMap将包含6个项目:

Edge [source=0, destination=1, weight=5]
Edge [source=1, destination=2, weight=4]
Edge [source=2, destination=3, weight=5]
Edge [source=0, destination=3, weight=6]
Edge [source=0, destination=2, weight=3]
Edge [source=1, destination=3, weight=7]

Let's put same items into a TreeMap : 让我们将相同的项放入TreeMap

    SortedMap<Edge, Integer> treeMap = new TreeMap<>(new MyWeightComp());
    edges.forEach(edge -> treeMap.put(edge, edge.weight));
    treeMap.forEach((edge, value) -> System.out.printf("%s%n", edge));

This time, hashCode and equals are not used at all. 这次,根本不使用hashCodeequals Instead, only compare is used: 相反,只使用compare

Edge.compare.0.1.5:0.1.5 // first item = 5
Edge.compare.1.2.4:0.1.5 // 4 is less than 5
Edge.compare.2.3.5:0.1.5 // 5 is already in the map, this item is discarded
Edge.compare.0.3.6:0.1.5 // 6 is more than 5
Edge.compare.0.3.6:0.1.5 // 6 is already in the map, this item is discarded
Edge.compare.0.3.6:0.3.6 // 6 is already in the map, this item is discarded
Edge.compare.0.2.3:0.1.5 // 3 is less than 5
Edge.compare.0.2.3:1.2.4 //   and also less than 4
Edge.compare.1.3.7:0.1.5 // 7 is more than 5
Edge.compare.1.3.7:0.3.6 //   and also more than 6

TreeMap will contain only 5 items: TreeMap只包含5个项目:

Edge [source=0, destination=2, weight=3]
Edge [source=1, destination=2, weight=4]
Edge [source=0, destination=1, weight=5]
Edge [source=0, destination=3, weight=6]
Edge [source=1, destination=3, weight=7]

As already suggested, you can 'fix' this by comparing not only by weight, but also by all other fields. 正如已经建议的那样,您可以通过不仅按重量比较,还可以通过所有其他字段来“修复”此问题。 Java8 provides a nice API for comparing "chains" of properties: Java8提供了一个很好的API来比较属性的“链”:

    Comparator<Edge> myEdgeComparator = Comparator
            .comparingInt(Edge::getWeight)
            .thenComparing(Edge::getSource)
            .thenComparing(Edge::getDestination);

However, this may indicate, that you should have not used the TreeMap for sorting at all. 但是,这可能表明您根本不应该使用TreeMap进行排序。 After all, your initial requirements probably were following: 毕竟,您的初始要求可能如下:

  • sort by weight 按重量排序
  • keep all edges 保持所有边缘
  • ordering of Edges with same weights is not important 对具有相同权重的边缘进行排序并不重要

In this case, you should probably simply use a list and sort it: 在这种情况下,您可能只需使用一个列表并对其进行排序:

    List<Edge> list = new ArrayList<>(edges);
    list.sort(myEdgeComparator);
    list.forEach(System.out::println);

Or using Java8 streams: 或者使用Java8流:

    List<Edge> list2 = edges.stream().sorted(myEdgeComparator).collect(Collectors.toList());
    list2.forEach(System.out::println);

Source code from these examples can be found here . 这些示例的源代码可以在这里找到。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM