【问题标题】:Best way to update set of set更新集合的最佳方法
【发布时间】:2020-09-25 13:15:29
【问题描述】:

有一个连接顶点的边列表。我正在尝试将这些顶点分成相互连接的组。

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);
        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;
}

【问题讨论】:

    标签: java set


    【解决方案1】:

    问题是散列集的元素不应该是可变的,但在这里你正在改变外部集的内部集,这会改变它们的散列码。更改的哈希码导致result.remove 调用无法删除to

    remove 尝试使用新的哈希码查找to,但to 存储在不同的存储桶中,因为添加时,其哈希码不同。

    来自docs

    注意:如果将可变对象用作集合元素,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是集合中的一个元素,则不指定集合的​​行为。这种禁止的一个特殊情况是不允许集合包含自己作为元素。

    我只会使用一组集合,而不是一组集合。您正在做的唯一操作,集合非常擅长,是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))
    

    【讨论】:

    • 实际上问题不仅在于内部集的可变性,还在于哈希集的使用。因为 equals 方法首先检查对象链接是否相同 - 即使 set 的内容已更改,这也没关系。但是 HashSet 在其内部使用基于对象哈希码的存储桶。集合的哈希码是根据其内容计算的。这实际上是主要原因。
    • @AzamatZhurtbayev 我知道。我没有解释这一点,因为我在 OP 的代码中找不到需要哈希码的行,所以我认为它一定是我不知道的其他东西。现在我意识到我是盲人,错过了result.remove(to); 行。
    猜你喜欢
    • 1970-01-01
    • 2020-02-28
    • 2020-01-15
    • 2021-03-24
    • 2010-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多