【问题标题】:Pick random bit from 32bit value in O(1) if possible如果可能,从 O(1) 中的 32 位值中选择随机位
【发布时间】:2016-02-10 13:20:26
【问题描述】:

我有一个 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)。

我很确定这一定可以通过某种方式进行一些双向加密/屏蔽/魔术......


编辑:

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

int myvalue = 631;
var matchingmasks = Enumerable.Range(0, 32)
                              .Select(i => 1 << i)
                              .Where(i => (myvalue & i) == i)
                              .ToArray();

从结果数组中,我可以选择一个随机元素,然后我会从给定值中找到我的“随机”位(标志)。然而,这仍然需要一个(隐藏的,因为 Linq)for 循环、“暴力破解”每个可能的位、为结果数组分配内存等。

【问题讨论】:

  • 任何随机生成算法(包括最简单的算法)都会在O(1)中生成一个随机值
  • @:deleted:我不想要二进制表示;请阅读问题。我想从设置的位中选择一个(一个)位,或者有它的位置(例如第 3 位)或者它的“值”(例如第 3 位的 4)@Kilanny:我不想要一个随机数;我想要一个 given 数字中的随机位。
  • 如果你有二进制表示,那么你可以操纵它来获取位或位的位置。如果我理解正确,您有 631 want 作为输入,并且您希望输出为 1, 2, 4 , 16, 32
  • @DonaldJansen:you could manipulate that to get the bits:是的;问题是如何。 (另外:64 和 512 是您为给定值 631 遗漏的可能输出)。
  • 另外:避免说“这不是最快的”的诱惑。最快可能不是你的目标。您可能不愿意花费一千万美元来开发定制硬件以尽快解决这个问题。您的目标是“考虑到我的预算和其他限制,速度足够快”。由于我们既不知道您的性能要求也不知道您的预算,因此我们不知道什么可以满足该目标。

标签: c# algorithm bit-manipulation


【解决方案1】:

首先,我建议按照您在问题中建议的简单、直接、明显的方式执行此操作:创建一个值数组,随机选择一个元素。是的,这会分配内存等等。首先优化代码的可读性和正确性;仅当您遇到已证明的性能问题时才应该对其进行优化。

如果您确实想将其优化到小题大做,此页面是我的首选资源:http://graphics.stanford.edu/~seander/bithacks.html

您需要的算法是:

  • 首先,选择您最喜欢的确定汉明权重的算法——即“有多少位?”拨打那个号码。
  • 现在从 1 到 n 中选择一个随机数 r
  • 现在阅读称为“选择具有给定计数的位位置”的算法。这需要您的数字 r 并为您提供从高端开始的第 r 个真实位的位位置。页面上给出的代码是多头的;应该可以直接将其修改为整数。

我注意到其中许多算法的一个关键特性是它们是无分支的。当您试图从算法中榨取最后一点性能时,请记住,每个“如果”都会扼杀性能。 “如果”表示缓存中有代码未运行,因为您从它分支,因此您更有可能导致缓存未命中。 “如果”意味着分支预测器有机会做出错误的选择。在 CLR 级别,每个“if”都意味着更多的基本块,这意味着抖动需要做更多的工作来进行流量分析。以此类推。

【讨论】:

  • Optimize for the code being readable and correct first; only when you have a demonstrated performance problem should you optimize it. 我全心全意地同意,我信奉这个座右铭。但是,我 已经证明当前代码(目前是“蛮力”实现)需要更多工作的步骤,并且我正在尝试通过一些旋转来优化它,因为您在上一段/句子中提到的确切原因。这段代码(将要)被调用很多(数万)次 p/sec。所以我准备权衡一些可读性。
  • 我会去访问该页面并编写一个实现并在完成后将其发布在我的问题中以获得(可能的)反馈。
  • 嗯,在我理解 Select the bit position (from the most-significant bit) with the given count (rank) 并将其重写为 32 位之前可能需要一段时间(目前甚至无法使其在 64 位原样中正常工作......)
【解决方案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();

【讨论】:

  • 这是O(1),如问题标题所示?
  • @Kilanny 你没有集合包含n 元素,那么它总是O(1)。或者以其他方式,这不是正确的问题。
  • @Kilanny 我真的不认为这可以用大 O 表示法来表达,但请随时启发我。
  • 这当然是可能的;但是,如果我有一堆值并且我想从这些值中的每一个中选择一个随机标志,则性能方面的表现不会很好。我们可以讨论这是否是 O(1)(从技术上讲它可能是),但是从给定值中获取随机位是很多“工作”。我更喜欢一些位移/掩码/魔术,而不是您的(根据我所见是正确的)解决方案。
  • 为什么是Math.Pow(2, i) 而不是1 &lt;&lt; i
【解决方案3】:

这实际上是可能的。 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 位作为一个有趣的练习。 ;)

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

【讨论】:

  • "ulong number = 0x0101010101010101;" [...] "You would expect to see all the even-numbered bits returned approximately the same number of times";这很奇怪,因为 0x0101...101... 是一个 十六进制 数字(不是二进制),我希望每 8 位都设置一次。试试ulong number = Convert.ToUInt64("0101010101010101", 2);。除此之外,它似乎工作。将尽快获得一些(基准)数字。我认为nv 的“汉明权重”是否正确?我对该函数的第二个参数应该是myvalue (631),而不是 rng。所以我也需要退出n...
  • @J.Doe 对不起,我会改正的! (我一直在运行几个测试,包括一个设置了偶数位的测试,但我发布了错误的样本。)顺便说一下,第一个参数是包含位的数字(在你的情况下是 631),所以我不知道为什么你说它应该是第二个参数。
  • "顺便说一下,第一个参数是包含位的数字(在你的情况下是 631),所以我不确定你为什么说它应该是第二个参数"我的错;恐怕今天我盯着代码看太久了。你是绝对正确的,我不知道我为什么想出这个。
【解决方案4】:

这应该是真正简单的 O(1):

byte b = 123;
Random r = new Random();
int bitNumber = r.Next(32);
var bit = (b & (1 << bitNumber-1)) != 0;

Check this

【讨论】:

  • OP 想要获取在源值中设置位的位位置(或值)。或者:你能解释一下这段代码是如何做到的吗?
【解决方案5】:

似乎是一个家庭作业问题......但是,解决方案是可能的,因为您只有 32 位要查找,每个位置只有 32 个提前知道的时间值。如果我没记错的话,这些实际上是相同的(设置了第二个位的掩码在解释为整数时具有值“2”)。

您所做的是使用准备好的位掩码构建您的 32 条目数组,该位掩码将仅返回该位。

数组查找是 O(1),因为无论您检索哪个位,速度都是恒定的。此时您 & 并与原始掩码进行比较,就像您在使用位移时所做的那样,最终结果仍然是 O(1)。

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

【讨论】:

  • 这不是家庭作业。 实际 问题是我有一个带有Flags 属性的enum;我想从给定值的设置标志中找到一个随机标志。从技术上讲,您的解决方案有效,但除非我从预先准备的数组中选择随机元素,否则它将偏向 LSB 或 MSB 或数组的任何顺序。如果我从数组中选择随机元素,我需要调用Rng.Next() 每次迭代,跟踪哪些元素已经被“使用”,哪些没有等等等等,然后我开始想知道一些“bittwiddling”是否会更快。
【解决方案6】:

查找表呢?

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

我不确定这在其他答案中在性能方面的排名。我只是添加这个答案,主要是为了在可能的实现方面保持完整性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-14
    • 2019-07-30
    • 1970-01-01
    • 2023-02-02
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多