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}