package squidpony.squidmath;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* Meant to take a fixed-size set of items and produce a shuffled stream of them such that an element is never chosen in
* quick succession; that is, there should always be a gap between the same item's occurrences. This is an Iterable of
* T, not a Collection, because it can iterate without stopping, infinitely, unless you break out of a foreach loop that
* iterates through one of these, or call the iterator's next() method only a limited number of times. Collections have
* a size that can be checked, but Iterables can be infinite (and in this case, this one is).
* Created by Tommy Ettinger on 5/21/2016.
* @param <T> the type of items to iterate over; ideally, the items are unique
*/
public class GapShuffler<T> implements Iterator<T>, Iterable<T>, Serializable {
private static final long serialVersionUID = 1277543974688106290L;
public RNG rng;
private ArrayList<T> elements;
private int size, index;
private int[][] indexSections;
private GapShuffler() {}
/**
* Constructor that takes any Collection of T, shuffles it with an unseeded RNG, and can then iterate infinitely
* through mostly-random shuffles of the given collection. These shuffles are spaced so that a single element should
* always have a large amount of "gap" in order between one appearance and the next. It helps to keep the appearance
* of a gap if every item in elements is unique, but that is not necessary and does not affect how this works.
* @param elements a Collection of T that will not be modified
*/
public GapShuffler(Collection<T> elements)
{
rng = new RNG(new LongPeriodRNG());
this.elements = rng.shuffle(elements);
size = this.elements.size();
double sz2 = size;
index = 0;
int portionSize = Math.min(20, Math.max(1, size / 2));
int minSection = Math.min(5, size / 2 + 1);
while (size % portionSize < minSection && portionSize > 2)
portionSize--;
indexSections = new int[(int)Math.ceil(sz2 / portionSize)][];
for (int i = 0; i < indexSections.length - 1; i++) {
indexSections[i] = PermutationGenerator.decodePermutation(
rng.nextLong(PermutationGenerator.getTotalPermutations(portionSize)), portionSize, i * portionSize);
sz2 -= portionSize;
}
indexSections[indexSections.length - 1] = PermutationGenerator.decodePermutation(
rng.nextLong(PermutationGenerator.getTotalPermutations((int)sz2)),
(int)sz2, (indexSections.length - 1) * portionSize);
}
/**
* Constructor that takes any Collection of T, shuffles it with the given RNG, and can then iterate infinitely
* through mostly-random shuffles of the given collection. These shuffles are spaced so that a single element should
* always have a large amount of "gap" in order between one appearance and the next. It helps to keep the appearance
* of a gap if every item in elements is unique, but that is not necessary and does not affect how this works. The
* rng parameter is copied so externally using it won't change the order this produces its values; the rng field is
* used whenever the iterator needs to re-shuffle the internal ordering of elements. I suggest that the RNG should
* use LongPeriodRNG as its RandomnessSource, since it is in general a good choice for shuffling, but since this
* class mostly delegates its unique-shuffling code to PermutationGenerator and looks up at most 20 elements'
* permutation at once (allowing it to use a single random long to generate the permutation), there probably won't
* be problems if you use any other RandomnessSource.
* @param elements a Collection of T that will not be modified
* @param rng an RNG that can be pre-seeded; will be copied and not used directly
*/
public GapShuffler(Collection<T> elements, RNG rng)
{
this.rng = rng.copy();
this.elements = rng.shuffle(elements);
size = this.elements.size();
double sz2 = size;
index = 0;
int portionSize = Math.min(20, Math.max(1, size / 2));
int minSection = Math.min(5, size / 2 + 1);
while (size % portionSize < minSection && portionSize > 2)
portionSize--;
indexSections = new int[(int)Math.ceil(sz2 / portionSize)][];
for (int i = 0; i < indexSections.length - 1; i++) {
indexSections[i] = PermutationGenerator.decodePermutation(
rng.nextLong(PermutationGenerator.getTotalPermutations(portionSize)), portionSize, i * portionSize);
sz2 -= portionSize;
}
indexSections[indexSections.length - 1] = PermutationGenerator.decodePermutation(
rng.nextLong(PermutationGenerator.getTotalPermutations((int)sz2)),
(int)sz2, (indexSections.length - 1) * portionSize);
}
/**
* Gets the next element of the infinite sequence of T this shuffles through. This class can be used as an
* Iterator or Iterable of type T.
* @return the next element in the infinite sequence
*/
public T next() {
if(index >= size)
{
index = 0;
int build = 0, inner, rotated;
for (int i = 0; i < indexSections.length; i++) {
if(indexSections.length <= 2)
rotated = (indexSections.length + 2 - i) % indexSections.length;
else
rotated = (indexSections.length + 1 - i) % indexSections.length;
inner = indexSections[rotated].length;
indexSections[rotated] =
PermutationGenerator.decodePermutation(
rng.nextLong(PermutationGenerator.getTotalPermutations(inner)),
inner,
build);
build += inner;
}
}
int ilen = indexSections[0].length, ii = index / ilen, ij = index - ilen * ii;
++index;
return elements.get(indexSections[ii][ij]);
}
/**
* Returns {@code true} if the iteration has more elements.
* This is always the case for GapShuffler.
*
* @return {@code true} always
*/
@Override
public boolean hasNext() {
return true;
}
/**
* Not supported.
*
* @throws UnsupportedOperationException always throws this exception
*/
@Override
public void remove() {
throw new UnsupportedOperationException("remove() is not supported");
}
/**
* Returns an <b>infinite</b> iterator over elements of type {@code T}; the returned iterator is this object.
* You should be prepared to break out of any for loops that use this once you've gotten enough elements!
* The remove() method is not supported by this iterator and hasNext() will always return true.
*
* @return an infinite Iterator over elements of type T.
*/
@Override
public Iterator<T> iterator() {
return this;
}
}