如何随机化Set的迭代序列?

时间:2014-02-06 03:36:45

标签: java

我需要使用 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);
    }

4 个答案:

答案 0 :(得分:3)

您需要的是RandomizingIterator

Set是无序的,因此随机化无序Collection没有任何逻辑意义。

有序 Set是使用Comparator订购的,这意味着它有一个固定的订单,你不能随意改组,因为订单确定没有意义通过Comparatorcompare()方法。

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