如果可能,从O(1)中的32位值中选择随机位

时间:2016-02-10 13:20:26

标签: c# algorithm bit-manipulation

我有一个32位随机值(比如//Create a shading object with cell-specific coords PdfShading shading = PdfShading.simpleAxial(w, position.getLeft(), position.getBottom(), position.getRight(), position.getTop(), BaseColor.GRAY, new BaseColor(255,255,255,0)); )。

631

这些位中的每一位都是一个标志。我想从这些位返回一个随机标志,如果可能的话, O(1)操作。如何从给定值{{1}中选择位位置0,1,2,4,5,6或9(或它对应的值1,2,4,16,32,64,512) }}?优选地,尽可能偏向某些位。

我提出的事情:

    • 右移位值随机位数(本例中最多10个)
    • 查看是否设置了LSB
      • 如果是:得到一个位置(最后移位的位数);完成
      • 如果不是:
        • 如果结果值== 0;重新开始
        • 如果结果值!= 0,则返回再次移位随机位
  1. 上面不是O(1),如果我们恰好碰到了'#39;那么可能需要多次迭代。 0位。

      • 具有随机值的面具(和)
      • 重复直到剩余2的幂或当值为0时重新开始。
    1. 同样,不幸的是,上面不是O(1)。

      我非常肯定这一定是可以通过某种方式进行bithisfting / masking / magic ......

      编辑:

      As CodeCaster suggested;这将给我所有设置位值:

      0...0000001001110111

      从结果数组中我可以选择一个随机元素,我已经找到了随机的'来自给定值的位(标志)。然而,这仍然需要一个(隐藏的,因为Linq)for-loop,' brute -forcing'每个可能的位,结果数组的内存分配等。

6 个答案:

答案 0 :(得分:5)

首先,我建议你在问题中提出一个简单,直接,明显的方法:创建一个值数组,随机选择一个元素。是的,这会分配内存和诸如此类的东西。首先优化可读和正确的代码;只有当你有一个已证明的性能问题时才应优化它。

如果您确实希望将其优化到位,那么这个页面就是我的首选资源:http://graphics.stanford.edu/~seander/bithacks.html

您在此处想要的算法是:

  • 首先,选择你最喜欢的算法来确定汉明重量 - 也就是说,"有多少比特在开?"拨打该号码。
  • 现在从1到n
  • 选择一个随机数r
  • 现在阅读名为&#34的算法;选择具有给定计数"的位位置。这将获取您的数字r,并从高端开始为您提供第r个真位的位位置。页面上给出的代码是长的;它应该直接修改为ints。

我注意到许多算法的一个关键特性是它们是无分支的。当你试图从算法中榨取最后一盎司的性能时,请记住每一个" if"杀死表现。一个"如果"意味着缓存中的代码未运行,因为您从中分离出来,因此您更有可能发生缓存未命中。一个"如果"意味着分支预测器有机会做出错误的选择。在CLR级别,每个"如果"意味着更多的基本块,这意味着更多的工作用于抖动进行流量分析。等等。

答案 1 :(得分:2)

您可以预先创建蒙版,并选择与源值匹配的蒙版:

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 a random element

如果你想要位位置,你可以像这样使用索引Select()重载:

var matchingMask = masks.Select((m, i) => new { Index = i, Mask = m}) 
                        .Where(m => (m.Mask & source) == m.Mask)
                        .ToArray();

答案 2 :(得分:1)

看起来像是一个家庭作业问题......但是,解决方案是可能的,因为你只需要32位来查找,并且每个位置的时间值已经提前32位。如果我没有弄错,这些实际上是相同的(第二位设置的掩码具有值&#34; 2&#34;当被解释为整数时)。

你要做的是使用准备好的位掩码构建32个入口数组,该位掩码只返回该位。

数组查找为O(1),因为无论您要检索哪个位,速度都是常量。此时你和&amp;并且与使用位移时的原始掩码进行比较,最终结果仍为O(1)。

注意虽然这是O(1),但它可能不会比位移更快。这些数组是32 * 4字节的内存,所以128字节。这不是很大,但它也不小。您需要运行一个简单的测试来确认执行多达32位移位指令比从数组中检索项目花费更多时间(我的猜测是数组更快,但我可能错了)。

答案 3 :(得分:1)

这实际上是可能的。 There's a 64-bit solution here

我已将其转换为以下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位作为一项有趣的练习。 ;)

(在任何情况下,这可能是一个不必要的优化:一个简单的循环来计算位然后随机选择一个可能足够快......)

答案 4 :(得分:0)

这应该是真的&amp;简单地O(1):

frame.setLayout(null);

Check this

答案 5 :(得分:0)

查找表怎么样?

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

我不确定其他答案中的表现在哪里排名。我只是为了完整性而添加这个答案。