All Downloads are FREE. Search and download functionalities are using the official Maven repository.

squidpony.squidmath.LowStorageShuffler Maven / Gradle / Ivy

Go to download

SquidLib platform-independent logic and utility code. Please refer to https://github.com/SquidPony/SquidLib .

There is a newer version: 3.0.6
Show newest version
package squidpony.squidmath;

import java.io.Serializable;

/**
 * Gets a sequence of distinct pseudo-random ints (typically used as indices) from 0 to some bound, without storing all
 * of the sequence in memory. Uses a Feistel network, as described in
 * this post by Alan Wolfe.
 * The API is very simple; you construct a LowStorageShuffler by specifying how many items it can shuffle, and you can
 * optionally use a seed (it will be random if you don't specify a seed). Call {@link #next()} on a LowStorageShuffler
 * to get the next distinct int in the shuffled ordering; next() will return -1 if there are no more distinct ints (if
 * {@link #bound} items have already been returned). You can go back to the previous item with {@link #previous()},
 * which similarly returns -1 if it can't go earlier in the sequence. You can restart the sequence with
 * {@link #restart()} to use the same sequence over again, or {@link #restart(int)} to use a different seed (the bound
 * is fixed).
 * 
* This differs from the version in Alan Wolfe's example code and blog post; it uses a very different round function, * 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 * version he uses doesn't have anything like MurmurHash3's fmix32() to adequately avalanche bits, and since all keys * are small keys with the usage of MurmurHash2 in his code, avalanche is the most important thing. It's also perfectly * fine to use irreversible operations in a Feistel network round function, and I do that since it seems to improve * randomness slightly. The {@link #round(int, int)} method used here acts like {@link Coord#hashCode()}, but with two * small multiplications included to increase randomness significantly. Using 4 rounds turns out to be overkill in this * case. This also uses a different seed for each round. *
* This class is extremely similar to {@link SwapOrNotShuffler}; both are optimized for usage on GWT but * SwapOrNotShuffler is meant to have higher quality in general. SwapOrNotShuffler also performs much better for some * values of bound; this class slows down drastically when bound is just slightly more than a power of 4, but can be * slightly faster when bound is equal to a power of 4. There's also {@link ShuffledIntSequence}, which extends this * class and uses different behavior so it "re-shuffles" the results when all results have been produced, and * {@link SNShuffledIntSequence}, which extends SwapOrNotShuffler but is otherwise like ShuffledIntSequence. * SwapOrNotShuffler should usually be preferred over this class when the bound is unknown or known to be a problem for * this class. *
* Created by Tommy Ettinger on 9/22/2018. * @author Alan Wolfe * @author Tommy Ettinger */ public class LowStorageShuffler implements Serializable { private static final long serialVersionUID = 1L; public final int bound; protected int index, pow4, halfBits, leftMask, rightMask; protected int key0, key1;//, key2, key3; /** * Constructs a LowStorageShuffler with a random seed and a bound of 10. */ public LowStorageShuffler(){ this(10); } /** * Constructs a LowStorageShuffler with the given exclusive upper bound and a random seed. * @param bound how many distinct ints this can return */ public LowStorageShuffler(int bound) { this(bound, (int)((Math.random() * 2.0 - 1.0) * 0x80000000)); } /** * Constructs a LowStorageShuffler with the given exclusive upper bound and int seed. * @param bound how many distinct ints this can return * @param seed any int; will be used to get several seeds used internally */ public LowStorageShuffler(int bound, int seed) { // initialize our state this.bound = bound; restart(seed); // calculate next power of 4. Needed since the balanced Feistel network needs // an even number of bits to work with pow4 = HashCommon.nextPowerOfTwo(bound); pow4 = ((pow4 | pow4 << 1) & 0x55555554) - 1; // calculate our left and right masks to split our indices for the Feistel network halfBits = Integer.bitCount(pow4) >>> 1; rightMask = pow4 >>> halfBits; leftMask = pow4 ^ rightMask; } /** * Gets the next distinct int in the sequence, or -1 if all distinct ints have been returned that are non-negative * and less than {@link #bound}. * @return the next item in the sequence, or -1 if the sequence has been exhausted */ public int next() { int shuffleIndex; // index is the index to start searching for the next number at while (index <= pow4) { // get the next number shuffleIndex = encode(index++); // if we found a valid index, return it! if (shuffleIndex < bound) return shuffleIndex; } // end of shuffled list if we got here. return -1; } /** * Gets the previous returned int from the sequence (as yielded by {@link #next()}), or -1 if next() has never been * called (or the LowStorageShuffler has reached the beginning from repeated calls to this method). * @return the previously-given item in the sequence, or -1 if this can't go earlier */ public int previous() { int shuffleIndex; // index is the index to start searching for the next number at while (index > 0) { // get the next number shuffleIndex = encode(--index); // if we found a valid index, return success! if (shuffleIndex < bound) return shuffleIndex; } // end of shuffled list if we got here. return -1; } /** * Starts the same sequence over from the beginning. */ public void restart() { index = 0; } public static int determine(int z) { return (z = ((z = ((z = ((z *= 0xBDEAD) ^ z >>> 13) * 0x7FFFF) ^ z >>> 12) * 0x1FFFF) ^ z >>> 14) * 0x1FFF) ^ z >>> 15; } /** * Starts the sequence over, but can change the seed (completely changing the sequence). If {@code seed} is the same * as the seed given in the constructor, this will use the same sequence, acting like {@link #restart()}. * @param seed any int; will be used to get several seeds used internally */ public void restart(int seed) { index = 0; key0 = determine(seed ^ 0xDE4D * ~bound); key1 = determine(key0 ^ 0xBA55 * bound); key0 ^= determine(~key1 ^ 0xBEEF * bound); key1 ^= determine(~key0 ^ 0x1337 * bound); } /** * An irreversible mixing function that seems to give good results; GWT-compatible. * This is mostly the same as {@link Coord#hashCode()}, with data acting like x and seed acting like y, but also * includes two small multiplications (each by 16-bit numbers, which doesn't risk GWT safety). The algorithm is a * little complicated, and is much like the 32-bit xoroshiro variant used by {@link Lathe32RNG}, but changes any * additive operations to use XOR. Less involved schemes did not do well at all. There's four bitwise rotations and * two multiplications used here, plus several XOR and bitwise shift operations. * @param data the data being ciphered * @param seed the current seed * @return the ciphered data */ public int round(int data, int seed) { seed ^= data * 0xBCFD; seed ^= (data << 13 | data >>> 19) ^ (seed << 5) ^ (seed << 28 | seed >>> 4); data ^= (seed << 11 | seed >>> 21) * 0xC6D5; return data ^ (data << 25 | data >>> 7); // seed ^= data * 0xC6D5 + 0xB531A935; // data ^= seed * 0xBCFD + 0x41C64E6D; // seed ^= data * 0xACED; // data ^= seed * 0xBA55; // data += data >>> 21; // seed += seed >>> 22; // data += data << 8; // seed += seed << 5; // return data ^ seed; // data += data >>> 21; // seed += seed >>> 22; // data += data << 8; // seed += seed << 5; // data += data >>> 16; // seed += seed >>> 13; // data += data << 9; // seed += seed << 11; // return data ^ seed; } /** * Encodes an index with a 2-round Feistel network. It is possible that someone would want to override this method * to use more or less rounds, but there must always be an even number. * @param index the index to cipher; must be between 0 and {@link #pow4}, inclusive * @return the ciphered index, which might not be less than bound but will be less than or equal to {@link #pow4} */ public int encode(int index) { // break our index into the left and right half int left = (index & leftMask) >>> halfBits; int right = (index & rightMask); // do 2 Feistel rounds int newRight = left ^ (round(right, key0) & rightMask); left = right; right = newRight; newRight = left ^ (round(right, key1) & rightMask); // left = right; // right = newRight; // newRight = left ^ (round(right, key2) & rightMask); // left = right; // right = newRight; // newRight = left ^ (round(right, key3) & rightMask); // left = right; // right = newRight; // put the left and right back together to form the encrypted index // return left << halfBits | right; return right << halfBits | newRight; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy