Reputation: 243
I need to use the Set collection.
Each time I start a jvm to run the program, I want to iterate through the items in the Set in a randomly decided sequence.
The iteration sequence has nothing to do with the sequence in which I placed them in the Set, right?
So, what to do? How can I randomize the iteration sequence in a Set?
Here is my method, and it does not randomize.
public static <T> void shuffle(Set<T> set) {
List<T> shuffleMe = new ArrayList<T>(set);
Collections.shuffle(shuffleMe);
set.clear();
set.addAll(shuffleMe);
}
Upvotes: 2
Views: 11527
Reputation: 8889
You could copy the contents of the Set
into a List
, shuffle the List
, then return a new LinkedHashSet
populated from the shuffled list. Nice thing about LinkedHashSet
is that its iterators return elements in the order they were inserted.
public static <T> Set<T> newShuffledSet(Collection<T> collection) {
List<T> shuffleMe = new ArrayList<T>(collection);
Collections.shuffle(shuffleMe);
return new LinkedHashSet<T>(shuffleMe);
}
Upvotes: 1
Reputation:
Set
is unordered, so randomizing an unordered Collection
doesn't make any logical sense.
An ordered Set
is ordered using a Comparator
which means it has a fixed order, you can't shuffle it, that has no meaning as the order is determined by the Comparator
or the compare()
method.
Set
-> List
will allow you to shuffle the contents of the List
and then use a custom RandomizingIterator
to iterate across the Set
.
Link to Gist on GitHub - 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());
}
}
}
This is a straw man example, but the intention is clear, use a custom Iterator
to get custom iteration.
You can't get the normal iteration behavior back, but that doesn't seem to be a problem with your use case.
Passing the the super.iterator()
to the facade is important, it will StackOverflowError
otherwise, because it becomes a recursive call if you pass this
to .addAll()
or the List()
constructor.
HashSet
may appear to be ordered but it isn't guaranteed to stay ordered, the order depends on the hashCode
of the objects and adding a single object may reorder the how the contents are order, the contract of the Set
interface is that the order is undefined and in particular the HashSet
is nothing more than a Facade over a backing Map.keySet()
.
There are other more supposedly light weight, but much more complex solutions that use the original Iterator
and try and keep track of what has already been seen, those solutions aren't improvements over this technique unless the size of the data is excessively large, and the you are probably looking at on disk structures at that point.
Upvotes: 4
Reputation: 17455
Internally HashSet sorts all its elements, AFAIR according to their hash()
value. So you should use other classes like SortedSet
with a custom comparator
. But remember the whole idea of Set
is to find elements quickly, that's why it sorts elements internally. So you have to keep "stability" of the comparison. Maybe you don't need a set after shuffling?
Upvotes: 0
Reputation: 1518
According to the docs for java.util.Set:
The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee).
When you insert the elements there is no guarantee about the order they will be returned to you. If you want that behavior you will need to use a data structure which supports stable iteration order, e.g. List.
Upvotes: 0