如果可能,从 O(1) 中的 32 位值中选择随机位
本文关键字:中的 随机 选择 如果 | 更新日期: 2023-09-27 18:33:58
我有一个32位的随机值(比如631
(。
0...0000001001110111
这些位中的每一个都是一个标志。如果可能的话,我想在 O(1( 操作中从这些位返回一个随机标志。如何从给定的值中选择位位置 0、1、2、4、5、6 或 9(或相应的值 1、2、4、16、32、64、512(631
?最好对某些位的偏差尽可能小。
我想出的东西:
-
- 向右移动值随机位数(在本例中最多 10 位(
- 查看是否设置了 LSB
- 如果是:获得位位置(上次移动的位数(;完成
- 如果没有:
- 如果结果值 == 0;重新开始
- 如果结果值 != 0,则再次返回移动随机位
上面不是 O(1(,如果我们碰巧只"命中"0 位,可能需要多次迭代。
-
- 具有随机值的掩码(和(
- 重复直到剩下 2 的幂,或在值为 0 时重新开始。
同样,不幸的是,上面不是O(1(。
我很确定这必须以某种方式通过一些双面/掩蔽/魔法来实现......
编辑:
正如CodeCaster所建议的;这将给我所有设置的位值:
int myvalue = 631;
var matchingmasks = Enumerable.Range(0, 32)
.Select(i => 1 << i)
.Where(i => (myvalue & i) == i)
.ToArray();
从结果数组中,我可以选择一个随机元素,我将从给定值中找到我的"随机"位(标志(。但是,这仍然需要一个(隐藏的,因为 Linq(for 循环,"暴力强制"每个可能的位,结果数组的内存分配等。
首先,我建议以您在问题中建议的简单,直接,明显的方式来执行此操作:创建一个值数组,随机选择一个元素。是的,这会分配内存等等。首先优化代码的可读性和正确性;只有当您有明显的性能问题时,才应该对其进行优化。
如果您确实想将其优化为有点叽叽喳喳,此页面是我的首选资源:http://graphics.stanford.edu/~seander/bithacks.html
您需要在此处使用的算法是:
- 首先,选择你最喜欢的算法来确定汉明权重——即"有多少位? 称该号码为 n。
- 现在从 1 到 n 中选择一个随机数 r
- 现在阅读名为"选择具有给定计数的位位置"的算法。 这将取您的数字 r,并为您提供从高端开始的第 r 个真实位的位位置。 页面上给出的代码用于多头;为整数修改它应该很简单。
我注意到许多这些算法的一个关键特征是它们是无分支的。当你试图从算法中榨取最后一盎司的性能时,请记住,每个"如果"都会扼杀性能。"if"表示缓存中有代码未运行,因为您从它分支出来,因此您更有可能丢失缓存。 "如果"表示分支预测器有机会做出错误的选择。在CLR级别,每个"if"都意味着更多的基本块,这意味着抖动进行流动分析的工作更多。等等。
您只需事先创建掩码,然后选择与源值匹配的掩码:
uint source = 631;
uint[] masks = Enumerable.Range(0, 32).Select(i => (uint)1 << i).ToArray();
uint[] matchingMask = masks.Where(m => (m & source) == m).ToArray();
现在matchingMask
包含构成source
值的值,在本例中为: 1, 2, 4, 16, 32, 64, 512
.
然后从matchingMask
中选择一个随机元素。
如果你想要位位置,你可以使用索引Select()
重载,如下所示:
var matchingMask = masks.Select((m, i) => new { Index = i, Mask = m})
.Where(m => (m.Mask & source) == m.Mask)
.ToArray();
这实际上是可能的。这里有一个 64 位解决方案。
我已将其转换为以下 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.");
您希望看到每 8 位返回大致相同的次数,而其他位都没有返回。事实确实如此。
我将其转换为 32 位是一个有趣的练习。 ;)
(无论如何,这可能是不必要的优化:一个简单的循环来计算位,然后随机选择一个可能足够快......
这应该是真的和简单的 O(1(:
byte b = 123;
Random r = new Random();
int bitNumber = r.Next(32);
var bit = (b & (1 << bitNumber-1)) != 0;
检查这个
似乎是家庭作业问题...但是,解决方案是可能的,因为您只需要查找 32 位,每个位置只有 32 个已知的提前时间值。 如果我没记错的话,它们实际上是相同的(设置了第二个位的掩码在解释为整数时具有值"2"(。
您要做的是使用准备好的位掩码构建 32 条目数组,该掩码将仅返回该位。
数组查找为 O(1(,因为无论您检索哪个位,速度都是恒定的。在这一点上,你和与原始掩码进行比较,就像你在使用位移时所做的那样,最终结果仍然是O(1(。
请注意,虽然这是 O(1(,但它可能不会比位移更快。数组是 32*4 字节的内存,因此为 128 字节。这并不大,但也不小。您需要运行一个简单的测试来确认执行多达 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;
}
我不确定这在其他答案中的表现排名如何。 我只是添加这个答案,主要是为了在可能的实现方面保持完整性。