英文:
Best way to update set of set
问题
以下是您提供的代码的翻译部分:
有一组连接顶点的边。我正在尝试将这些顶点分成彼此连接的组。
static class Edge {
final String from, to;
Edge(String from, String to) {
this.from = from;
this.to = to;
}
}
static Set<String> find(String k, Set<Set<String>> sets) {
for (Set<String> set : sets)
if (set.contains(k))
return set;
return null;
}
static Set<Set<String>> connectedVertices(List<Edge> edges) {
Set<Set<String>> result = new HashSet<>();
for (Edge e : edges) {
Set<String> from = find(e.from, result);
Set<String> to = find(e.to, result);
if (from == null && to == null) {
result.add(new HashSet<>(Set.of(e.from, e.to)));
} else if (from == null) {
to.add(e.from);
} else if (to == null) {
from.add(e.to);
} else if (from != to) {
result.remove(to);
from.addAll(to);
}
}
return result;
}
和
List<Edge> edges = List.of(
new Edge("a", "b"),
new Edge("c", "b"),
new Edge("c", "d"),
new Edge("a", "c"),
new Edge("e", "f"),
new Edge("x", "y"),
new Edge("y", "d"));
System.out.println(connectedVertices(edges));
但结果与我预期的不符。
预期:
[[e, f], [a, b, c, d, x, y]]
实际:
[[a, b, c, d, x, y], [a, b, c, d], [e, f]]
通过以下更改,我可以获得我期望的结果,但这样太冗长。是否有更好的方法?
static Set<Set<String>> connectedVertices(List<Edge> edges) {
Set<Set<String>> result = new HashSet<>();
for (Edge e : edges) {
Set<String> from = find(e.from, result);
// 其余部分已省略
请注意,这只是代码的翻译部分,与您提供的原始代码相同。如果您需要任何进一步的帮助,请随时提问。
英文:
There is a list of edges connecting the vertices. I'm trying to divide these vertices into groups that are connected to each other.
static class Edge {
final String from, to;
Edge(String from, String to) {
this.from = from;
this.to = to;
}
}
static Set<String> find(String k, Set<Set<String>> sets) {
for (Set<String> set : sets)
if (set.contains(k))
return set;
return null;
}
static Set<Set<String>> connectedVertices(List<Edge> edges) {
Set<Set<String>> result = new HashSet<>();
for (Edge e : edges) {
Set<String> from = find(e.from, result);
Set<String> to = find(e.to, result);
if (from == null && to == null) {
result.add(new HashSet<>(Set.of(e.from, e.to)));
} else if (from == null) {
to.add(e.from);
} else if (to == null) {
from.add(e.to);
} else if (from != to) {
result.remove(to);
from.addAll(to);
}
}
return result;
}
And
List<Edge> edges = List.of(
new Edge("a", "b"),
new Edge("c", "b"),
new Edge("c", "d"),
new Edge("a", "c"),
new Edge("e", "f"),
new Edge("x", "y"),
new Edge("y", "d"));
System.out.println(connectedVertices(edges));
But the result is not what I expected.
Expected:
[[e, f], [a, b, c, d, x, y]]
Actual:
[[a, b, c, d, x, y], [a, b, c, d], [e, f]]
I get the results I expect with the following changes, but it's verbose. Is there a better way?
static Set<Set<String>> connectedVertices(List<Edge> edges) {
Set<Set<String>> result = new HashSet<>();
for (Edge e : edges) {
Set<String> from = find(e.from, result);
Set<String> to = find(e.to, result);
if (from == null && to == null) {
result.add(new HashSet<>(Set.of(e.from, e.to)));
} else if (from == null) {
result.remove(to);
to.add(e.from);
result.add(to);
} else if (to == null) {
result.remove(from);
from.add(e.to);
result.add(from);
} else if (from != to) {
result.remove(from);
result.remove(to);
from.addAll(to);
result.add(from);
}
}
return result;
}
答案1
得分: 1
问题在于哈希集合中的元素不应该是可变的,但在这里,您正在更改外部集合的内部集合,从而改变了它们的哈希码。改变后的哈希码导致 result.remove
调用无法删除 to
。
remove
尝试使用新的哈希码查找 to
,但是 to
存储在不同的桶中,因为在添加时其哈希码不同。
来自文档:
> 注意:如果将可变对象用作集合元素,则必须非常小心。如果在对象作为集合中的元素时以影响相等比较的方式更改对象的值,则集合的行为未经指定。这个禁止的特例是,集合不能包含自身作为元素。
我只会使用一系列集合,而不是一组集合。您正在执行的唯一操作,集合确实很擅长的,就是 remove
。使用列表将会使 remove
成为一个 O(n)
操作。但由于算法的总体复杂度要比这高得多,额外的 O(n)
操作在长期内不会有太大影响。我会考虑条件 else if (from != to)
是否经常触发,以及您的“继续删除、更改,然后再添加回去”的方法是否对使用基准测试的小输入实际上更快。
static Set<String> find(String k, List<Set<String>> sets) {
for (Set<String> set : sets)
if (set.contains(k))
return set;
return null;
}
static List<Set<String>> connectedVertices(List<Edge> edges) {
List<Set<String>> result = new ArrayList<>();
for (Edge e : edges) {
Set<String> from = find(e.from, result);
Set<String> to = find(e.to, result);
if (from == null && to == null)
result.add(new HashSet<>(Set.of(e.from, e.to)));
else if (from == null) {
to.add(e.from);
} else if (to == null) {
from.add(e.to);
} else if (from != to) {
result.remove(to);
from.addAll(to);
}
}
return result;
}
如果您想要一个集合作为结果,您始终可以执行:
new HashSet(connectedVertices(edges))
英文:
The problem is that the elements of a hash set should not be mutable, but here you are changing the inner sets of the outer set, which changes their hash codes. The changed hash code causes the result.remove
call to fail to remove to
.
remove
tries to look for to
with the new hash code, but to
is stored in a different bucket, because when it was added, its hash code is different.
From the docs:
> Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set. A special case of this prohibition is that it is not permissible for a set to contain itself as an element.
I would just use a list of sets, rather than a set of sets. The only operation that you are doing, that sets are really good at, is remove
. Using a list would turn remove
into an O(n)
operation. But since the algorithm overall has a way higher complexity than that, an extra O(n)
operation is not going to matter in the long term. I would consider whether the condition else if (from != to)
is hit very often, and whether your approach of "keep on removing, changing, then adding back in" is actually faster for small inputs using a benchmark.
static Set<String> find(String k, List<Set<String>> sets) {
for (Set<String> set : sets)
if (set.contains(k))
return set;
return null;
}
static List<Set<String>> connectedVertices(List<Edge> edges) {
List<Set<String>> result = new ArrayList<>();
for (Edge e : edges) {
Set<String> from = find(e.from, result);
Set<String> to = find(e.to, result);
if (from == null && to == null)
result.add(new HashSet<>(Set.of(e.from, e.to)));
else if (from == null) {
to.add(e.from);
} else if (to == null) {
from.add(e.to);
} else if (from != to) {
result.remove(to);
from.addAll(to);
}
}
return result;
}
If you would like a set as a result, you could always do
new HashSet(connectedVertices(edges))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论