我正在尝试根据“权重”对TreeMap进行排序。但是出于某种原因,即使键不同,它也会删除具有相同权重值的条目。
下面是代码:
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 + "]";
}
}
HashMap的数据:
{Edge [源= 0,目的地= 1,权重= 5] = 5,Edge [源= 1, 目的地= 2,权重= 4] = 4,边线[来源= 2,目的地= 3, 权重= 5] = 5,边缘[源= 0,目的地= 3,权重= 6] = 6,边缘 [source = 0,destination = 2,weight = 3] = 3,边线[source = 1,destination = 3, 体重= 7] = 7}
Map<Edge, Integer> treemap = new TreeMap<>(new MyWeightComp());
treemap.putAll(map);
树形图的比较器:
class MyWeightComp implements Comparator<Edge>{
@Override
public int compare(Edge e1, Edge e2) {
return e1.weight-e2.weight;
}
}
排序后的数据:
{边缘[源= 0,目的地= 2,权重= 3] = 3,边缘[源= 1,目的地= 2,权重= 4] = 4,边缘[源= 0,目的地= 1,权重= 5] = 5,边缘[source = 0,目的地= 3,权重= 6] = 6,边缘[source = 1,目的地= 3,权重= 7] = 7}
因此您可以看到,由于某些原因,即使键是源和目标的组合,具有相同权重的数据也会被删除。
答案 0 :(得分:5)
所有Map都删除重复项,如果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;
}
}
如果您的字段对于排序不重要,那么如果您不想为了重复而忽略它们,则仍然必须选择任意但一致的顺序。
您需要确保的关键一致性是compare(a, b) == -compare(b, a)
或更准确地说是sign(compare(a, b)) == -sign(compare(b, a))
答案 1 :(得分:3)
TreeMap
使用比较器比较键。
您的比较器为两个权重相同的键返回0
。因此,从TreeMap
角度来看,这样的键是相等的。
答案 2 :(得分:0)
彼得和塔莱克斯已经回答了这个问题。由于您提到了学习数据结构,因此我将进行更深入的分析。
首先,List
和Set
/ Map
之间存在关键区别。列表可以包含重复项,集合不能包含重复项,地图不能包含重复键(这适用于标准地图,不适用于multimaps)。实际上,集是使用地图在内部实现的。
Map
如何确定哪个项目重复?
HashMap
使用两个功能Object.hashCode
和Object.equals
。您可以将打印语句放入以下函数中:
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));
让我们假设以下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)
);
现在,让我们将物品放入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));
产生的输出显示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
您可以看到HashMap
知道前四项不是重复项,因为它们具有不同的哈希码。第五个值与第四个值具有相同的哈希码,并且需要确认HashMap
,使用equals
确实是相同的Edge。 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]
让我们将相同的项目放入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));
这次,hashCode
和equals
完全没有使用。相反,仅使用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
仅包含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]
正如已经建议的那样,您不仅可以通过比较权重,还可以通过比较所有其他字段来“修复”此问题。 Java8提供了一个不错的API,用于比较属性的“链”:
Comparator<Edge> myEdgeComparator = Comparator
.comparingInt(Edge::getWeight)
.thenComparing(Edge::getSource)
.thenComparing(Edge::getDestination);
但是,这可能表明您根本不应该使用TreeMap
进行排序。毕竟,您的最初要求可能是:
在这种情况下,您可能应该简单地使用列表并对其进行排序:
List<Edge> list = new ArrayList<>(edges);
list.sort(myEdgeComparator);
list.forEach(System.out::println);
或使用Java8流:
List<Edge> list2 = edges.stream().sorted(myEdgeComparator).collect(Collectors.toList());
list2.forEach(System.out::println);
这些示例的源代码可以在here中找到。