Skip to content

Instantly share code, notes, and snippets.

@chrisruffalo
Created October 19, 2023 15:54
Show Gist options
  • Save chrisruffalo/ede61c2c781a742cf7ec47c7084d9a16 to your computer and use it in GitHub Desktop.
Save chrisruffalo/ede61c2c781a742cf7ec47c7084d9a16 to your computer and use it in GitHub Desktop.
Random Iterator in Java
package io.github.chrisruffalo.pintle.resolution.resolver;
import java.util.*;
/**
* Randomly iterates through a list. This class is not thread
* safe. This is a lot more efficient than the following:
* <pre>
* final List<T> copy = new ArrayList(source);
* Collections.shuffle(copy);
* Iterator<T> iterator = copy.iterator();
* </pre>
* <br/><br/>
* This is the test case that was used to verify this somewhat works:
* <pre>
* @Test
* public void random() {
* final List<String> ordered = new ArrayList<>();
* for (int i = 0; i <= 5000; i++) {
* ordered.add(String.format("%d", i));
* }
* // there are now 5000! combinations of this, this should
* // _never_ collide
* final String joined = Strings.join("", ordered);
* final Iterator<String> random = new RandomIterator<>(ordered);
* final StringBuilder randomJoined = new StringBuilder();
* while(random.hasNext()) {
* randomJoined.append(random.next());
* }
* Assertions.assertEquals(joined.length(), randomJoined.length());
* Assertions.assertNotEquals(joined,randomJoined);
* }
* </pre>
*
* @param <T> of the list type.
*/
public class RandomIterator<T> implements Iterator<T> {
/**
* If you want to use this for anything secure or
* that matters you'd need to use a SecureRandom
* but don't do that.
*/
private final Random random = new Random();
/**
* The source content list. This list is not modified.
*/
private final List<T> contents;
/**
* A bit set that represents the iterated elements
* in the list.
*/
private final BitSet visited;
/**
* Build a new iterator by starting from a list
* of contents. Only a list will work because
* of the ability to access nodes by index.
*
* @param contents that need to be randomly iterated over
*/
public RandomIterator(List<T> contents) {
this.contents = contents;
visited = new BitSet(contents.size());
}
@Override
public boolean hasNext() {
return visited.nextClearBit(0) < contents.size();
}
@Override
public T next() {
// this uses the hasNext function to decide if there
// are remaining un-iterated elements
if (hasNext()) {
// this loop continuously
int index;
// using the index of the first clear bit allows
// for a little smaller range in the random generator
// and makes it so that longer lists are handled
// a little more efficiently
int first = visited.nextClearBit(0);
do {
index = random.nextInt(contents.size() - first) + first;
} while(visited.get(index));
// a new element can be retrieved but first we mark it as iterated in the bitset
visited.set(index);
// and we get the contents of that element from the list
return contents.get(index);
}
throw new NoSuchElementException("No elements remain");
}
@Override
public void remove() {
throw new UnsupportedOperationException("The 'remove' operation is not supported");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment