我需要使用 Set 集合。
每次启动jvm来运行程序时,我都想以随机决定的顺序遍历 Set 中的项目。
迭代序列与我将它们放在 Set 中的顺序无关,对吗?
那么,该怎么办?如何在Set?
中随机化迭代序列这是我的方法,它没有随机化。
public static <T> void shuffle(Set<T> set) {
List<T> shuffleMe = new ArrayList<T>(set);
Collections.shuffle(shuffleMe);
set.clear();
set.addAll(shuffleMe);
}
答案 0 :(得分:3)
Set
是无序的,因此随机化无序Collection
没有任何逻辑意义。
有序 Set
是使用Comparator
订购的,这意味着它有一个固定的订单,你不能随意改组,因为订单确定没有意义通过Comparator
或compare()
方法。
Set
- &gt; List
允许您随机播放 List
的内容,然后使用自定义RandomizingIterator
来遍历Set
。
GitHub上的Gist链接 - TestRandomizingIterator.java
import org.junit.Test;
import javax.annotation.Nonnull;
import java.util.*;
public class TestRandomzingIterator
{
@Test
public void testRandomIteration()
{
final Set<String> set = new HashSet<String>()
{
/** Every call to iterator() will give a possibly unique iteration order, or not */
@Nonnull
@Override
public Iterator<String> iterator()
{
return new RandomizingIterator<String>(super.iterator());
}
class RandomizingIterator<T> implements Iterator<T>
{
final Iterator<T> iterator;
private RandomizingIterator(@Nonnull final Iterator<T> iterator)
{
List<T> list = new ArrayList<T>();
while(iterator.hasNext())
{
list.add(iterator.next());
}
Collections.shuffle(list);
this.iterator = list.iterator();
}
@Override
public boolean hasNext()
{
return this.iterator.hasNext();
}
@Override
public T next()
{
return this.iterator.next();
}
/**
* Modifying this makes no logical sense, so for simplicity sake, this implementation is Immutable.
* It could be done, but with added complexity.
*/
@Override
public void remove()
{
throw new UnsupportedOperationException("TestRandomzingIterator.RandomizingIterator.remove");
}
}
};
set.addAll(Arrays.asList("A", "B", "C"));
final Iterator<String> iterator = set.iterator();
while (iterator.hasNext())
{
System.out.println(iterator.next());
}
}
}
这是一个稻草人的例子,但目的很明确,使用自定义Iterator
来获得自定义迭代。
您无法恢复正常迭代行为,但这似乎不是您的用例的问题。
将super.iterator()
传递到外观很重要,否则会StackOverflowError
,因为如果您将this
传递给.addAll()
或{{},它就会成为递归调用1}}构造函数。
List()
似乎是有序,但不保证保持有序,订单取决于对象的HashSet
并添加单个对象可能重新排序内容的排序方式,hashCode
界面的合同是订单未定义,特别是Set
只不过是支持HashSet
的门面。
还有其他更多的轻量级,但更复杂的解决方案使用原始Map.keySet()
并尝试跟踪已经看到的内容,这些解决方案并非改进除非数据的大小过大,否则您可能会在此时查看磁盘结构。
答案 1 :(得分:0)
内部HashSet根据其hash()
值对其所有元素AFAIR进行排序。因此,您应该使用其他类SortedSet
和自定义comparator
。但请记住Set
的整个想法是快速找到元素,这就是它在内部对元素进行排序的原因。所以你必须保持比较的“稳定性”。也许你在洗牌后不需要套装?
答案 2 :(得分:0)
根据java.util.Set的docs:
元素以无特定顺序返回(除非此集合是某个提供保证的类的实例)。
当您插入元素时,无法保证它们将返回给您的顺序。如果您想要这种行为,则需要使用支持稳定迭代顺序的数据结构,例如列表。
答案 3 :(得分:0)
您可以将Set
的内容复制到List
,随机播放List
,然后返回从随机列表中填充的新LinkedHashSet
。关于LinkedHashSet
的好处是它的迭代器按照它们插入的顺序返回元素。
public static <T> Set<T> newShuffledSet(Collection<T> collection) {
List<T> shuffleMe = new ArrayList<T>(collection);
Collections.shuffle(shuffleMe);
return new LinkedHashSet<T>(shuffleMe);
}