从集合中获取任意元素

8

许多算法需要在集合非空的情况下迭代一组元素。

由于在迭代过程中可能会更改集合,因此通常需要从集合中取出一个元素,然后进行迭代,可能会向集合中添加或删除元素。以下是典型的Java代码:

Set<Integer> possibleFactors = Sets.newHashSet(2,3,4,5,6,7,8,100);
while (!possibleFactors.isEmpty()) {
    int factor = possibleFactors.iterator().next();
    for (int i=1;i<10;i++) possibleFactors.remove(i*factor);
}

编辑:根据评论的要求,我将给出一个更好的例子。我正在遍历用户选择的文件,并通过检查每个项目的权限来进行过滤。但是,作为一种优化,如果用户没有权限访问某个目录,则我将从该集合中删除其中的所有文件。

Set<Path> input = Sets.newHashSet(userSelectedPaths);
while (!input.isEmpty()) {
    Path path = input.iterator.next();
    input.remove(path);
    if (!expensivePermissionCheck(path)) {
        input.removeAll(path.getFiles());
    } else {
        processPath(path);
    }
}

然而,循环中的第一行看起来有些奇怪。它创建了一个多余的Iterable对象,而我只想要从集合中获取任意一个元素,不关心顺序。

除了性能之外,它看起来有点奇怪,也不太易读。

是否有更好的替代方案?也许完全不同的结构?

编辑:也许更好的表述应该是“如何从集合中弹出任意元素?”


你可能想用 while (possibleFactors.iterator().hasNext()) 替换 while (!possibleFactors.isEmpty()) - Rajeev Sreedharan
你的例子很令人困惑:它只是从集合中删除每个元素(因为在 while 循环的每次迭代中,for 循环的第一次迭代会删除“1 * factor”)。在寻求样式建议之前,最好有更好的规范或可工作的代码。 - Arnout Engelen
@Arnout,我的代码是可行的,但我只想给出一个普遍的例子。现在这个代码不会把集合中的所有元素删除。我马上会给出一个“现实世界的例子”。 - Elazar Leibovich
@Rajeev,但是为什么要白白创建一个迭代器对象呢? - Elazar Leibovich
@Elazar 如果remove()操作剩下了一个或多个元素,possibleFactors.isEmpty()将返回false,从而引发潜在的无限循环问题。而使用迭代器并没有太大的额外开销,相比于否则可能涉及的轻微风险。 - Rajeev Sreedharan
@Rajeev,我听不懂你说什么了。如果从集合中删除一个元素,我想要循环迭代它。而且如果集合非空,iterator().hasNext() 方法会返回 true,所以我看不出有什么区别。 - Elazar Leibovich
3个回答

6

Set接口的唯一访问方法是通过iterator()方法或toArray()方法。

如果您有一个SortedSet,则可以使用first()last()方法直接访问一个项目。


1
SortedSet的实现假设我的元素是Comparable类型,但它们并不是。而且,我不介意顺序,只要任何顺序都可以。LinkedHashSet可能可以,但遗憾的是,它没有提供获取第一个元素的方法。 - Elazar Leibovich

-1

实际上,如果集合为空,它不仅无法读取,而且还会抛出异常。

在获取next()之前,您应该始终检查hasNext(),然后根据其进行操作。


2
由于循环检查集合是否为空,因此它不会抛出异常。 - Sean Owen
1
@Sean 我认为他说得有道理,如果在不使用迭代器的 hasNext() 的情况下使用 next(),虽然不会抛出异常,但肯定会陷入无限循环;OP之所以到目前为止没有遇到问题,是因为他最终删除了所有元素,使其为空,但这只是一个等待发生问题的问题。 - Rajeev Sreedharan
1
不,如果没有更多元素,next() 会抛出 NoSuchElementException 异常。无论是否调用了 hasNext() 都是如此。至少,这是契约;请阅读 javadoc。 - Sean Owen

-2
在集合中,元素没有特定的顺序。您可以通过提供其值来删除或检查元素的存在(Set.remove(Object o) 或 Set.contains(Object o))。您可以考虑从集合中删除所有元素,或者使用“保留集合A中不在集合B中的元素”的方式来改写它。
Set.removeAll(Set B);

例如
Set<Integer> A = new HashSet();
Set<Integer> B = new HashSet();

add numbers from 1-to-10 into A
add even numbers in range [1, 10] to B

A.removeAll(B);
println(A)

will print all odd numbers, eg those that are in A but not in B.

或者您可以调用remove()方法,如果元素存在,则从集合中删除它:

for (int i = 2; i <= 10; i += 2) {
   a.remove(i)
}

它将具有与上面示例中的removeAll()相同的效果。

通常,当您使用唯一事物的集合并且它们的顺序不重要时,Set数据结构是合适的。如果您想在集合上执行Union、Intersection、Diff等操作,则Set非常有用(而且快速)。如果您有可能重复多次的项目,则可以考虑使用Multisets(Google Collections)。如果您关心事物的顺序,则List将更有帮助(尽管性能较差)。


你说了很多正确的事情,但我不明白它们与我的问题有什么关系。尤其是我的集合中的所有元素都是不重复的,我不需要多重集合,而且我不知道如何在迭代集合的同时添加/删除其中的元素。 - Elazar Leibovich
我的回答基于你问题模糊的表述。抱歉,我的心灵读取设备今天不工作。 - Andreas Bakurov
不要太苛刻了,我并没有冒犯的意思。如果我的问题表述得太模糊,我会很高兴你能帮我澄清。但我不确定一个模糊的答案是否有帮助。 - Elazar Leibovich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接