我想知道是否有更快的方法来改变整数位而不是以下
public int shuffleBits(int number) {
int int_width = 31;
Random random = new Random();
for(int i = 0; i < int_width; i++) {
number = swapBit(number, i, random.nextInt(int_width - i) + i);
}
}
答案 0 :(得分:1)
这个不一定会更快,但可能会给出更均匀的分布,即使我没有完全考虑过它。
基本思想是我只是设置随机位,直到结果设置为与输入相同的位数。如果要进行优化,可以检查ones >= Integer.SIZE / 2
是否为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;
}
,在这种情况下,以所有位设置的整数开始,并将它们连续归零。不过,我怀疑这是值得的。 (更新:此优化可使性能提高约10%。)
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
我已经为原始帖子(添加了Random
函数)的代码实现了一个小基准,@garriual的建议和我的上述两个版本。公平地说,我对所有版本都应用了相同的微优化,并将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
我通过在笔记本电脑上运行基准测试得到了这些结果。
{{1}}
我会非常谨慎地在随机数生成算法中实现“快捷方式”而没有彻底的数学分析,这通常并不容易。如果你超调,你可能会很快得到一个功能很差的结果。我查看了一些直方图,并没有找到您的版本中存在缺陷的直接证据,但我甚至没有查看相关图。正如这种比较有希望证明的那样,更快的代码并不一定更复杂,但更简单的代码显然更难以出错。
答案 1 :(得分:1)
您当然可以优化和改进当前的方法。
(1)我们只想在第i和第j位不同时执行交换(如果两者都是0或1则不需要交换),我们可以简单地对两个位进行翻转。最多有k个交换,其中k是设置位的数量。
(2)我们可以使用计数器跟踪我们在循环时看到多少1,并在我们到达k时尽早退出。
public int shuffleBits(int number)
{
int int_width = 31;
int swaps = 0;
int K = Integer.bitCount(number);
int setBits = 0;
Random random = new Random();
for(int i = 0; i < int_width && setBits < K; i++) {
int j = random.nextInt(int_width - i) + i;
if(bitsAreDifferent(number, i, j)) {
number ^= (1 << i) | (1 << j);
}
if(((number >> i) & 1) == 1) setBits++;
}
return number;
}
private boolean bitsAreDifferent(int number, int i, int j) {
return ((number >> i) & 1) != ((number >> j) & 1);
}