001package squidpony.squidmath; 002 003import java.io.Serializable; 004 005/** 006 * Gets a sequence of distinct pseudo-random ints (typically used as indices) from 0 to some bound, without storing all 007 * of the sequence in memory. Uses a Feistel network, as described in 008 * <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 LowStorageShuffler by specifying how many items it can shuffle, and you can 010 * optionally use a seed (it will be random if you don't specify a seed). Call {@link #next()} on a LowStorageShuffler 011 * to get the next distinct int in the shuffled ordering; next() will return -1 if there are no more distinct ints (if 012 * {@link #bound} items have already been returned). You can go back to the previous item with {@link #previous()}, 013 * which similarly returns -1 if it can't go earlier in the sequence. You can restart the sequence with 014 * {@link #restart()} to use the same sequence over again, or {@link #restart(int)} to use a different seed (the bound 015 * is fixed). 016 * <br> 017 * This differs from the version in Alan Wolfe's example code and blog post; it uses a very different round function, 018 * 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 019 * version he uses doesn't have anything like MurmurHash3's fmix32() to adequately avalanche bits, and since all keys 020 * are small keys with the usage of MurmurHash2 in his code, avalanche is the most important thing. It's also perfectly 021 * fine to use irreversible operations in a Feistel network round function, and I do that since it seems to improve 022 * randomness slightly. The {@link #round(int, int)} method used here acts like an unbalanced, irreversible PRNG with 023 * two states, and that turns out to be just fine for a Feistel network. Using 4 rounds turns out to be overkill in this 024 * case. This also uses a different seed for each round. 025 * <br> 026 * This class is similar to {@link ShuffledIntSequence}, which extends this class and uses different 027 * behavior so it "re-shuffles" the results when all results have been produced. 028 * <br> 029 * Created by Tommy Ettinger on 9/22/2018. 030 * @author Alan Wolfe 031 * @author Tommy Ettinger 032 */ 033public class LowStorageShuffler implements Serializable { 034 private static final long serialVersionUID = 1L; 035 public final int bound; 036 protected int index, pow4, halfBits, leftMask, rightMask; 037 protected int key0, key1;//, key2, key3; 038 039 /** 040 * Constructs a LowStorageShuffler with a random seed and a bound of 10. 041 */ 042 public LowStorageShuffler(){ 043 this(10); 044 } 045 /** 046 * Constructs a LowStorageShuffler with the given exclusive upper bound and a random seed. 047 * @param bound how many distinct ints this can return 048 */ 049 public LowStorageShuffler(int bound) 050 { 051 this(bound, (int)((Math.random() * 2.0 - 1.0) * 0x80000000)); 052 } 053 054 /** 055 * Constructs a LowStorageShuffler with the given exclusive upper bound and int seed. 056 * @param bound how many distinct ints this can return 057 * @param seed any int; will be used to get several seeds used internally 058 */ 059 public LowStorageShuffler(int bound, int seed) 060 { 061 // initialize our state 062 this.bound = bound; 063 restart(seed); 064 // calculate next power of 4. Needed since the balanced Feistel network needs 065 // an even number of bits to work with 066 pow4 = HashCommon.nextPowerOfTwo(bound); 067 pow4 = ((pow4 | pow4 << 1) & 0x55555554) - 1; 068 // calculate our left and right masks to split our indices for the Feistel network 069 halfBits = Integer.bitCount(pow4) >>> 1; 070 rightMask = pow4 >>> halfBits; 071 leftMask = pow4 ^ rightMask; 072 } 073 074 /** 075 * Gets the next distinct int in the sequence, or -1 if all distinct ints have been returned that are non-negative 076 * and less than {@link #bound}. 077 * @return the next item in the sequence, or -1 if the sequence has been exhausted 078 */ 079 public int next() 080 { 081 int shuffleIndex; 082 // index is the index to start searching for the next number at 083 while (index <= pow4) 084 { 085 // get the next number 086 shuffleIndex = encode(index++); 087 088 // if we found a valid index, return it! 089 if (shuffleIndex < bound) 090 return shuffleIndex; 091 } 092 093 // end of shuffled list if we got here. 094 return -1; 095 } 096 /** 097 * Gets the previous returned int from the sequence (as yielded by {@link #next()}), or -1 if next() has never been 098 * called (or the LowStorageShuffler has reached the beginning from repeated calls to this method). 099 * @return the previously-given item in the sequence, or -1 if this can't go earlier 100 */ 101 public int previous() 102 { 103 int shuffleIndex; 104 // index is the index to start searching for the next number at 105 while (index > 0) 106 { 107 // get the next number 108 shuffleIndex = encode(--index); 109 110 // if we found a valid index, return success! 111 if (shuffleIndex < bound) 112 return shuffleIndex; 113 } 114 115 // end of shuffled list if we got here. 116 return -1; 117 } 118 119 /** 120 * Starts the same sequence over from the beginning. 121 */ 122 public void restart() 123 { 124 index = 0; 125 } 126 127 /** 128 * Used to rearrange the bits of seeds this is given in a way that should partly randomize them. 129 * A high-quality 32-bit input, 32-bit output unary hash is pretty hard to find. 130 * @param z any int 131 * @return a pseudo-random but deterministic change of z 132 */ 133 public static int determine(int z) 134 { 135 return (z = ((z = ((z = (z ^ 0xC1C64E6D) * 0xDAB) ^ z >>> 13 ^ 0x9E3779B9) * 0x7FFFF) ^ z >>> 12) * 0x1FFFF) ^ z >>> 15; 136 137 } 138 /** 139 * Starts the sequence over, but can change the seed (completely changing the sequence). If {@code seed} is the same 140 * as the seed given in the constructor, this will use the same sequence, acting like {@link #restart()}. 141 * @param seed any int; will be used to get several seeds used internally 142 */ 143 public void restart(int seed) 144 { 145 index = 0; 146 key0 = determine(seed ^ 0xDE4D * ~bound); 147 key1 = determine(key0 ^ 0xBA55 * bound); 148 key0 ^= determine(~key1 ^ 0xBEEF * bound); 149 key1 ^= determine(~key0 ^ 0x1337 * bound); 150 } 151 152 /** 153 * An irreversible mixing function that seems to give good results; GWT-compatible. 154 * This is similar to {@link SilkRNG}'s way of combining two states, but because this doesn't need to be reversible 155 * or even evenly distributed, it has been significantly simplified. It uses one int multiplication, two additions, 156 * two subtractions, two XORs, two unsigned right shifts, and one signed right shift. 157 * @param data the data being ciphered 158 * @param seed the current seed 159 * @return the ciphered data 160 */ 161 public static int round(int data, int seed) 162 { 163 final int s = seed + data; 164 final int x = (s ^ s >>> 17) * (seed - data + 0x9E3779BB >> 12) - s; 165 return x ^ x >>> 15; 166 167 ////used earlier, similar to Coord.xoroHashCode(int, int) 168 //seed ^= data * 0xBCFD; 169 //seed ^= (data << 13 | data >>> 19) ^ (seed << 5) ^ (seed << 28 | seed >>> 4); 170 //data ^= (seed << 11 | seed >>> 21) * 0xC6D5; 171 //return data ^ (data << 25 | data >>> 7); 172 173// seed ^= data * 0xC6D5 + 0xB531A935; 174// data ^= seed * 0xBCFD + 0x41C64E6D; 175// seed ^= data * 0xACED; 176// data ^= seed * 0xBA55; 177// data += data >>> 21; 178// seed += seed >>> 22; 179// data += data << 8; 180// seed += seed << 5; 181// return data ^ seed; 182 183// data += data >>> 21; 184// seed += seed >>> 22; 185// data += data << 8; 186// seed += seed << 5; 187// data += data >>> 16; 188// seed += seed >>> 13; 189// data += data << 9; 190// seed += seed << 11; 191// return data ^ seed; 192 } 193 194 /** 195 * Encodes an index with a 2-round Feistel network. It is possible that someone would want to override this method 196 * to use more or less rounds, but there must always be an even number. 197 * @param index the index to cipher; must be between 0 and {@link #pow4}, inclusive 198 * @return the ciphered index, which might not be less than bound but will be less than or equal to {@link #pow4} 199 */ 200 public int encode(int index) 201 { 202 // break our index into the left and right half 203 int left = (index & leftMask) >>> halfBits; 204 int right = (index & rightMask); 205 // do 2 Feistel rounds 206 int newRight = left ^ (round(right, key0) & rightMask); 207 left = right; 208 right = newRight; 209 newRight = left ^ (round(right, key1) & rightMask); 210// left = right; 211// right = newRight; 212// newRight = left ^ (round(right, key2) & rightMask); 213// left = right; 214// right = newRight; 215// newRight = left ^ (round(right, key3) & rightMask); 216 217// left = right; 218// right = newRight; 219 220 // put the left and right back together to form the encrypted index 221// return left << halfBits | right; 222 return right << halfBits | newRight; 223 } 224}