C# 如果可能,从O(1)中的32位值中选择随机位
我有一个32位的随机值(比如说C# 如果可能,从O(1)中的32位值中选择随机位,c#,algorithm,bit-manipulation,C#,Algorithm,Bit Manipulation,我有一个32位的随机值(比如说631) 0…0000001001110111 这些位中的每一位都是一个标志。如果可能的话,我想从O(1)操作中的这些位返回一个随机标志。如何从给定值631中选择位位置0、1、2、4、5、6或9(或其对应值1、2、4、16、32、64、512)?最好对某些位有尽可能少的偏差 我想到的东西: 将值右移随机位数(在这种情况下,最大值为10) 查看是否设置了LSB 如果是:获得位位置(最后移位的位数);完成 如果没有: 如果结果值==0;重新开始 如果结果值!=0,
631
)
0…0000001001110111
这些位中的每一位都是一个标志。如果可能的话,我想从O(1)操作中的这些位返回一个随机标志。如何从给定值631
中选择位位置0、1、2、4、5、6或9(或其对应值1、2、4、16、32、64、512)?最好对某些位有尽可能少的偏差
我想到的东西:
- 将值右移随机位数(在这种情况下,最大值为10)
- 查看是否设置了LSB
- 如果是:获得位位置(最后移位的位数);完成
- 如果没有:
- 如果结果值==0;重新开始
- 如果结果值!=0,再次返回到移位随机位
- 使用随机值屏蔽(和)
- 重复此操作,直到剩余2的幂,或在值为0时重新开始
编辑: );这将为我提供所有设置的位值:
int myvalue = 631;
var matchingmasks = Enumerable.Range(0, 32)
.Select(i => 1 << i)
.Where(i => (myvalue & i) == i)
.ToArray();
int myvalue=631;
var matchingmasks=可枚举的范围(0,32)
.选择(i=>1(myvalue&i)==i)
.ToArray();
从生成的数组中,我可以选择一个随机元素,然后从给定值中找到我的“随机”位(标志)。但是,这仍然需要一个(隐藏,因为Linq)for循环,“暴力强制”每个可能的位、结果数组的内存分配等。您只需事先创建掩码,然后选择与源值匹配的掩码:
uint source = 631;
uint[] masks = Enumerable.Range(0, 32).Select(i => (uint)1 << i).ToArray();
uint[] matchingMask = masks.Where(m => (m & source) == m).ToArray();
这应该是非常简单的O(1):
字节b=123;
随机r=新随机();
整数位数=r.Next(32);
var bit=(b&(1似乎是一个家庭作业问题…但是,解决方案是可能的,因为您只有32位要查找,每个位置有32个已知的提前值。如果我没有弄错的话,它们实际上是相同的(设置了第二位的掩码在解释为整数时具有值“2”)
您要做的是使用准备好的位掩码构建32个条目的数组,该掩码将只返回该位
数组查找是O(1),因为无论检索哪个位,速度都是恒定的。此时,您将&与原始掩码进行比较,就像使用位移位时所做的那样,最终结果仍然是O(1)
请注意,虽然这是O(1),但它可能不会比位移位快。数组是32*4字节的内存,所以是128字节。这不是很大,但也不是很小。您需要运行一个简单的测试来确认执行最多32位移位指令比从数组中检索项花费更多的时间(我猜阵列速度更快,但我可能错了).首先,我建议您按照您在问题中建议的简单、直接、明显的方式执行此操作:创建一个值数组,随机选择一个元素。是的,这会分配内存等等。首先优化代码,使其可读性和正确性;只有当您遇到性能问题时,才应该对其进行优化。
如果您确实想将其优化到一点旋转,此页面是我的“转到”资源:
这里需要的算法有:
- 首先,选择你最喜欢的确定汉明权重的算法——也就是说,“有多少位是开着的?”调用这个数字n
- 现在从1到n中选择一个随机数r
- 现在阅读名为“使用给定计数选择位位置”的算法。这将获取数字r,并从高端开始给出rth真位的位位置。页面上给出的代码用于long;修改int应该很简单
我注意到,许多算法的一个关键特征是它们是无分支的。当你试图从算法中榨取最后一盎司的性能时,记住每一个“如果”都会扼杀性能。“如果”表示缓存中有代码未运行,因为您从缓存中分支出去,因此使缓存丢失的可能性更大。“如果”表示分支预测器有可能做出错误的选择。在CLR级别,每个“如果”意味着更多的基本块,这意味着抖动要做更多的流分析工作。等等。这实际上是有可能的
我已将其转换为以下C代码。它是O(1),因为操作数不依赖于设置的位数:
public static uint SelectRandomSetBit(ulong v, Random rng)
{
ulong a = v - ((v >> 1) & ~0UL / 3);
ulong b = (a & ~0UL / 5) + ((a >> 2) & ~0UL / 5);
ulong c = (b + (b >> 4)) & ~0UL / 0x11;
ulong d = (c + (c >> 8)) & ~0UL / 0x101;
ulong t = ((d >> 32) + (d >> 48));
int n = (int)((d * (~(ulong)0 / 255)) >> (64 - 1) * 8);
ulong r = (uint) rng.Next(1, n+1);
ulong s = 64;
s -= ((t - r) & 256) >> 3;
r -= (t & ((t - r) >> 8));
t = (d >> (int)(s - 16)) & 0xff;
s -= ((t - r) & 256) >> 4;
r -= (t & ((t - r) >> 8));
t = (c >> (int)(s - 8)) & 0xf;
s -= ((t - r) & 256) >> 5;
r -= (t & ((t - r) >> 8));
t = (b >> (int)(s - 4)) & 0x7;
s -= ((t - r) & 256) >> 6;
r -= (t & ((t - r) >> 8));
t = (a >> (int)(s - 2)) & 0x3;
s -= ((t - r) & 256) >> 7;
r -= (t & ((t - r) >> 8));
t = (v >> (int)(s - 1)) & 0x1;
s -= ((t - r) & 256) >> 8;
return (uint)(s-1);
}
下面是我如何测试它的:
Random rng = new Random();
ulong number = 0x0101010101010101;
int[] bits = new int[64];
for (int i = 0; i < 1000000; ++i)
++bits[SelectRandomSetBit(number, rng)];
for (int i = 0; i < 64; ++i)
Console.WriteLine($"bit {i} was returned {bits[i]} times.");
Random rng=new Random();
ulong编号=0x0101010101;
int[]位=新的int[64];
对于(int i=0;i<1000000;++i)
++位[SelectRandomSetBit(数字,rng)];
对于(int i=0;i<64;++i)
WriteLine($“位{i}返回了{bits[i]}次。”);
您希望看到每8位返回的次数大致相同,而其他位都没有返回。这确实是发生的情况
我把把它转换成32位作为一个有趣的练习
(这在任何情况下都可能是一个不必要的优化:一个简单的循环来计算位,然后随机选择一个可能足够快…那么查找表呢
public static class RandomExtensions
{
public static uint GetRandomBitOf( this Random rand, uint mask )
{
if( mask == 0 ) return 0;
var lo = smLookup[mask & 0xFFFF];
var hi = smLookup[mask >> 16];
int i = rand.Next( lo.Length + hi.Length );
return i < lo.Length ? (uint) lo[i] : (uint) hi[i - lo.Length] << 16;
}
static RandomExtensions()
{
smLookup = new ushort[65536][];
for( int i = 0; i < smLookup.Length; ++i )
{
ushort j = (ushort) i;
smLookup[i] = Enumerable
.Range( 0, 16 )
.Select( b => (ushort) ( 1 << b ) )
.Where( b => ( j & b ) != 0 )
.ToArray();
}
}
private static ushort[][] smLookup;
}
公共静态类扩展
{
公共静态uint GetRandomBitOf(此随机rand,uint掩码)
{
如果(掩码==0)返回0;
var lo=smLookup[mask&0xFFFF];
var hi=smLookup[mask>>16];
int i=下一个随机数(低长度+高长度);
返回iRandom rng = new Random();
ulong number = 0x0101010101010101;
int[] bits = new int[64];
for (int i = 0; i < 1000000; ++i)
++bits[SelectRandomSetBit(number, rng)];
for (int i = 0; i < 64; ++i)
Console.WriteLine($"bit {i} was returned {bits[i]} times.");
public static class RandomExtensions
{
public static uint GetRandomBitOf( this Random rand, uint mask )
{
if( mask == 0 ) return 0;
var lo = smLookup[mask & 0xFFFF];
var hi = smLookup[mask >> 16];
int i = rand.Next( lo.Length + hi.Length );
return i < lo.Length ? (uint) lo[i] : (uint) hi[i - lo.Length] << 16;
}
static RandomExtensions()
{
smLookup = new ushort[65536][];
for( int i = 0; i < smLookup.Length; ++i )
{
ushort j = (ushort) i;
smLookup[i] = Enumerable
.Range( 0, 16 )
.Select( b => (ushort) ( 1 << b ) )
.Where( b => ( j & b ) != 0 )
.ToArray();
}
}
private static ushort[][] smLookup;
}