C# 如果可能,从O(1)中的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,

我有一个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,再次返回到移位随机位
  • 以上不是O(1),如果我们恰好只“命中”0位,则可能需要多次迭代

    • 使用随机值屏蔽(和)
    • 重复此操作,直到剩余2的幂,或在值为0时重新开始
  • 不幸的是,上面的不是O(1)

    我很确定这一定是有可能的,通过某种方式进行比特窃听/掩蔽/魔法


    编辑:

    );这将为我提供所有设置的位值:

    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;
    }