【问题标题】:How to get last set bit in BitArray?如何获取 BitArray 中的最后一个设置位?
【发布时间】:2016-05-06 21:15:44
【问题描述】:

在 BitArray 中获取最后设置位的有效(快速)方法是什么。 (LINQ 或简单的后向 for 循环对于大型位图来说不是很快。而且我需要快)BitArray 我看到了下一个算法:返回 BitArray 内部 int 数组数据并使用一些编译器 Intrinsic Like C++ _BitScanReverse(不知道 C# 中的模拟)。

【问题讨论】:

  • 使用Linq => arrayInstance.Last()
  • 整数类型会更容易,所以如果你的数组足够短,你可以考虑一下。
  • Linq 不快。我伤心 - “有效”。没有 LINQ!
  • 你可以这样做,你只需要实现位扫描yourself
  • BitArray 类仅提供对单个位的访问,因此您无法有效地跳过全为零的整个单词。您必须找到/编写位数组的另一个实现。

标签: c# .net bit-manipulation


【解决方案1】:

“正常”解决方案:

    static long FindLastSetBit(BitArray array)
    {
        for (int i = array.Length - 1; i >= 0; i--)
        {
            if (array[i])
            {
                return i;
            }
        }

        return -1;
    }

反射解决方案(注意-依赖于BitArray的实现):

    static long FindLastSetBitReflection(BitArray array)
    {
        int[] intArray = (int[])array.GetType().GetField("m_array", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(array);

        for (var i = intArray.Length - 1; i >= 0; i--)
        {
            var b = intArray[i];
            if (b != 0)
            {
                var pos = (i << 5) + 31;
                for (int bit = 31; bit >= 0; bit--)
                {
                    if ((b & (1 << bit)) != 0)
                        return pos;

                    pos--;
                }

                return pos;
            }
        }

        return -1;
    }

在大型BitArrays 上,反射解决方案对我来说快 50-100 倍(在非常小的情况下,反射的开销将开始出现)。在我的机器上每兆字节大约需要 0.2 毫秒。

主要是if (b != 0) 一次检查 32 位。当找到正确的单词时,检查特定位的内部循环只运行一次。

已编辑:删除了不安全的代码,因为我意识到它几乎没有任何收获,它只是避免了数组边界检查,并且由于代码已经如此之快,所以它并不重要。作为记录,不安全的解决方案(对我来说快了约 30%):

    static unsafe long FindLastSetBitUnsafe(BitArray array)
    {
        int[] intArray = (int[])array.GetType().GetField("m_array", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(array);

        fixed (int* buffer = intArray)
        {
            for (var i = intArray.Length - 1; i >= 0; i--)
            {
                var b = buffer[i];
                if (b != 0)
                {
                    var pos = (i << 5) + 31;
                    for (int bit = 31; bit >= 0; bit--)
                    {
                        if ((b & (1 << bit)) != 0)
                            return pos;

                        pos--;
                    }

                    return pos;
                }
            }
        }

        return -1;
    }

【讨论】:

    【解决方案2】:

    如果您想要最后设置位的索引,您可以在 C# 6 中执行此操作。

    int? index = array.Select((b,i)=>{Index = i, Value = b})
                      .LastOrDefault(x => x.Value)
                      ?.Index;
    

    否则你必须这样做

    var last = array.Select((b,i)=>{Index = i, Value = b})
                      .LastOrDefault(x => x.Value);
    int? index = last == null ? (int?)null : last.Index;
    

    如果所有位都为零,则index 将是null

    【讨论】:

    • Linq 不快。我伤心 - “有效”。没有 LINQ!
    • @BransDs “有效”是主观的。如果您想要更具体的答案,您应该更具描述性。在任何情况下,您都必须遍历 BitArray,这正是 Linq 所做的。否则,如果您想为此使用位运算符,那么您需要使用比BitArray 更原始的类型。
    【解决方案3】:

    我不相信它可以做任何事情,除了从最后一位迭代到第一位,并询问每一个是否已设置。可以通过以下方式完成:

    BitArray bits = ...;
    int lastSet = Enumerable.Range(1, bits.Length)
                      .Select(i => bits.Length - i)
                      .Where(i => bits[i])
                      .DefaultIfEmpty(-1)
                      .First();
    

    这应该返回最后一个设置的位,如果没有,则返回 -1。自己没有测试过,可能需要调整一下。

    希望对你有帮助。

    【讨论】:

    • Linq 不快。我伤心 - “有效”。没有 LINQ!
    • 你可以用一个简单的for从BitArray的末尾到前面替换LINQ的用法。
    • 我现在看到您编辑了问题以专门询问没有 linq 并且不从头到尾迭代。我认为 BitArray 不会满足您的需求。我会使用自定义 BitArray 实现,它允许您访问内部 int/long 数组,以便您可以询问一次 32/64 位是否全为 0。
    猜你喜欢
    • 2016-03-18
    • 2016-10-31
    • 1970-01-01
    • 2013-04-11
    • 1970-01-01
    • 1970-01-01
    • 2014-06-29
    • 2016-09-23
    • 2014-10-18
    相关资源
    最近更新 更多