这个不一定会更快,但可能会提供更均匀的分布,即使我没有充分考虑它。
基本思想是我只是设置随机位,直到结果与输入设置的位数相同。如果你想优化,你可以检查是否ones >= Integer.SIZE / 2,在这种情况下,从一个所有位都设置的整数开始,然后依次将它们清零。不过,我怀疑这是否值得。 (更新:此优化使性能提高了约 10%。)
public final int shuffle(final int number) {
final int ones = Integer.bitCount(number);
int result = 0;
while (Integer.bitCount(result) < ones) {
final int position = this.rnd.nextInt(Integer.SIZE);
result |= (1 << position);
}
return result;
}
我假设类成员 rnd 类型为 java.util.Random。每次调用函数时创建一个new 随机生成器是一个真正的性能杀手,所以你不想这样做。
另一种更简单的方法是简单地生成均匀分布的随机整数,直到最终生成具有正确位数集的整数。
public int shuffle(final int number) {
final int ones = Integer.bitCount(number);
int result;
do {
result = this.random.nextInt();
} while (Integer.bitCount(result) != ones);
return result;
}
我已经为您原始帖子中的代码实施了一个小型基准测试(添加了swapBit 函数),@garriual 的建议和我的上述两个版本。公平地说,我对所有版本都应用了相同的微优化,并将 Random 对象的创建移出函数。
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
interface BitShuffler {
int shuffle(int number);
}
// Version by 'peter' (http://stackoverflow.com/q/28640108/1392132) with a few
// minor stylistic edits and micro-optimizations. The 'swapBit' function
// (missing in the OP) was implemented inspired by Sean Eron Anderson's "Bit
// Twiddling Hacks" (http://graphics.stanford.edu/~seander/bithacks.html).
final class Version0 implements BitShuffler {
private final Random random = new Random();
@Override
public int shuffle(int number) {
for (int i = 0; i < Integer.SIZE - 1; ++i) {
final int j = this.random.nextInt(Integer.SIZE - 1 - i) + i;
number = Version0.swapBit(number, i, j);
}
return number;
}
private static int swapBit(final int n, final int i, final int j) {
final int d = ((n >>> i) ^ (n >>> j)) & 1;
return n ^ ((d << i) | (d << j));
}
}
// Version by 'garriual' (http://stackoverflow.com/a/28640666/1392132) with a
// few minor stylistic edits and micro-optimizations.
final class Version1 implements BitShuffler {
private final Random random = new Random();
@Override
public int shuffle(int number) {
final int k = Integer.bitCount(number);
int swaps = 0;
int setBits = 0;
for (int i = 0; i < Integer.SIZE - 1 && setBits < k; ++i) {
final int j = this.random.nextInt(Integer.SIZE - 1 - i) + i;
if (Version1.bitsAreDifferent(number, i, j)) {
number ^= (1 << i) | (1 << j);
}
if (((number >> i) & 1) == 1) {
++setBits;
}
}
return number;
}
private static boolean bitsAreDifferent(final int n, final int i, final int j) {
return ((n >> i) & 1) != ((n >> j) & 1);
}
}
// Version by '5gon12eder' (http://stackoverflow.com/a/28640257/1392132) with
// additional optimization for numbers with more than half of the bits set.
final class Version2 implements BitShuffler {
private final Random random = new Random();
@Override
public int shuffle(final int number) {
final int ones = Integer.bitCount(number);
final int bits = (ones <= Integer.SIZE / 2) ? ones : Integer.SIZE - ones;
int result = 0;
while (Integer.bitCount(result) < bits) {
final int position = this.random.nextInt(Integer.SIZE);
result |= (1 << position);
}
return (ones == bits) ? result : ~result;
}
}
// Yet another version by '5gon12eder'
// (http://stackoverflow.com/a/28640257/1392132).
final class Version3 implements BitShuffler {
private final Random random = new Random();
@Override
public int shuffle(final int number) {
final int ones = Integer.bitCount(number);
int result;
do {
result = this.random.nextInt();
} while (Integer.bitCount(result) != ones);
return result;
}
}
public class Main {
// Run that many iterations per benchmark.
private static final int ITERATIONS = 10000000;
// Run each benchmark that many times to allow the JIT compiler to "warm up".
private static final int RUNS = 3;
public static void main(String[] args) {
final Random rnd = new Random();
final BitShuffler[] implementations = {
new Version0(),
new Version1(),
new Version2(),
new Version3(),
};
for (final BitShuffler impl : implementations) {
for (int i = 0; i < Main.RUNS; ++i) {
final long t1 = System.nanoTime();
int dummy = 0;
for (int j = 0; j < Main.ITERATIONS; ++j) {
final int input = rnd.nextInt();
final int output = impl.shuffle(input);
dummy ^= output; // prevent computation from being optimized away
assert Integer.bitCount(input) == Integer.bitCount(output);
}
final long t2 = System.nanoTime();
final double seconds = 1.0E-9 * (t2 - t1);
System.out.printf("%s (%08X): %5.2f s%n",
impl.getClass().getCanonicalName(),
dummy,
seconds);
}
System.out.println();
}
}
}
我通过在笔记本电脑上运行基准测试获得这些结果。
Version0 (E53D1257): 8.79 s
Version0 (9B5AD10C): 8.85 s
Version0 (2F64EE10): 8.85 s
Version1 (B994EEFB): 10.45 s
Version1 (85F45427): 10.56 s
Version1 (351A72A6): 10.45 s
Version2 (E6A69739): 4.59 s
Version2 (B5DFC42C): 4.58 s
Version2 (816CA9A4): 4.58 s
Version3 (D42B8B0B): 7.16 s
Version3 (1FC7A303): 7.90 s
Version3 (3CB0C233): 8.33 s
如果没有全面的数学分析,我会非常谨慎地在随机数生成算法中实现“快捷方式”,这通常并不容易。如果你过冲,你很可能会得到一个函数,很快就会返回一个很差的结果。我查看了一些直方图,没有找到您版本中存在缺陷的直接证据,但我什至没有查看相关图。正如这个比较希望表明的那样,更快的代码不一定更复杂,但更简单的代码显然更难出错。