001package squidpony.squidmath; 002 003import java.io.Serializable; 004 005/** 006 * An infinite sequence of pseudo-random ints (typically used as indices) from 0 to some bound, with all possible ints 007 * returned in a shuffled order before re-shuffling for the next result. Does not store the sequence in memory. Uses a 008 * Feistel network, as described in <a href="https://blog.demofox.org/2013/07/06/fast-lightweight-random-shuffle-functionality-fixed/">this post by Alan Wolfe</a>. 009 * The API is very simple; you construct a ShuffledIntSequence by specifying how many items it should shuffle (the 010 * actual sequence is boundless, but the items it can return are limited to between 0 and some bound), and you can 011 * optionally use a seed (it will be random if you don't specify a seed). Call {@link #next()} on a ShuffledIntSequence 012 * to get the next distinct int in the shuffled ordering; next() will re-shuffle the sequence if it runs out of distinct 013 * possible results. You can go back to the previous item with {@link #previous()}, which is allowed to go earlier than 014 * the first result generated (it will jump back to what is effectively the previous shuffled sequence). You can restart 015 * the sequence with {@link #restart()} to use the same sequence over again (which doesn't make much sense here, since 016 * this makes many sequences by re-shuffling), or {@link #restart(int)} to use a different seed (the bound is fixed). 017 * <br> 018 * This differs from the version in Alan Wolfe's example code and blog post; it uses a very different round function, 019 * and it only uses 2 rounds of it (instead of 4). Wolfe's round function is MurmurHash2, but as far as I can tell the 020 * version he uses doesn't have anything like MurmurHash3's fmix32() to adequately avalanche bits, and since all keys 021 * are small keys with the usage of MurmurHash2 in his code, avalanche is the most important thing. It's also perfectly 022 * fine to use irreversible operations in a Feistel network round function, and I do that since it seems to improve 023 * randomness slightly. The {@link #round(int, int)} method used here acts like SilkRNG's way of mixing two states, but 024 * here changed to be irreversible and unbalanced (which is fine here). Using 4 rounds turns out to be overkill in this 025 * case. This also uses a different seed for each round. 026 * <br> 027 * This class extends {@link LowStorageShuffler}, changing it from producing a unique set of ints once, to producing 028 * many sets of ints one after the next. 029 * <br> 030 * Created by Tommy Ettinger on 9/30/2018. 031 * @author Alan Wolfe 032 * @author Tommy Ettinger 033 */ 034public class ShuffledIntSequence extends LowStorageShuffler implements Serializable { 035 private static final long serialVersionUID = 1L; 036 protected int seed; 037 /** 038 * Constructs a ShuffledIntSequence with a random seed and a bound of 10. 039 */ 040 public ShuffledIntSequence(){ 041 this(10); 042 } 043 044 /** 045 * Constructs a ShuffledIntSequence with the given exclusive upper bound and a random seed. 046 * @param bound how many distinct ints this can return 047 */ 048 public ShuffledIntSequence(int bound) 049 { 050 this(bound, (int)((Math.random() * 2.0 - 1.0) * 0x80000000)); 051 } 052 053 /** 054 * Constructs a ShuffledIntSequence with the given exclusive upper bound and int seed. 055 * @param bound how many distinct ints this can return 056 * @param seed any int; will be used to get several seeds used internally 057 */ 058 public ShuffledIntSequence(int bound, int seed) 059 { 060 super(bound, seed); 061 this.seed = seed; 062 } 063 064 /** 065 * Gets the next distinct int in the sequence, shuffling the sequence if it has been exhausted so it never runs out. 066 * @return the next item in the sequence 067 */ 068 @Override 069 public int next() 070 { 071 int r = super.next(); 072 if(r == -1) 073 { 074 restart(seed = seed + 0x9E3779B9 | 0); 075 return super.next(); 076 } 077 return r; 078 } 079 /** 080 * Gets the previous returned int from the sequence (as yielded by {@link #next()}), restarting the sequence in a 081 * correctly-ordered way if it would go to before the "start" of the sequence (it is actually close to infinite both 082 * going forwards and backwards). 083 * @return the previously-given item in the sequence, or -1 if something goes wrong (which shouldn't be possible) 084 */ 085 @Override 086 public int previous() 087 { 088 int shuffleIndex; 089 // two tries to get a result, return -1 in the probably-impossible case this fails 090 for (int i = 0; i < 2; i++) { 091 while (index > 0) { 092 // get the next number 093 shuffleIndex = encode(--index); 094 095 // if we found a valid index, return success! 096 if (shuffleIndex < bound) 097 return shuffleIndex; 098 } 099 restart(seed = seed - 0x9E3779B9 | 0); 100 index = pow4; 101 } 102 return -1; 103 } 104 105 /** 106 * Starts the sequence over, but can change the seed (completely changing the sequence). If {@code seed} is the same 107 * as the seed given in the constructor, this will use the same sequence, acting like {@link #restart()}. 108 * @param seed any int; will be used to get several seeds used internally 109 */ 110 @Override 111 public void restart(int seed) 112 { 113 super.restart(seed); 114 this.seed = seed; 115 } 116}