【问题标题】:Find consecutive ones and zeros查找连续的一和零
【发布时间】:2019-04-24 19:37:09
【问题描述】:

我正在寻找将整数流转换为计数连续 1 和 0 的列表的最快方法。

例如整数 [4294967295,4194303,3758096384]

处于位级别:

11111111111111111111111111111111
11111111111111111111110000000000
00000000000000000000000000000111

(每个位串都是小端序)

所以程序应该输出三个值:[54 39 3] 有 54 个 1,后面跟着 39 个 0,最后是 3 个 1。

我一直在研究这些算法: http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear

可能我需要按照这些思路写一些东西

i=(the first bit of the first integer)
repeat till the end
    find the number of consecutive i's in this integer
    if we reach the end of the integer, continue with the next
    else i = (not)i

但我想知道是否有人能想到更好的方法。

目前该函数在 Matlab 中构建如下:

%get all bits in a long vector
data = uint32([4294967295,4194303,3758096384]);
logi = false([1,length(data)*32]);
for ct = 1:length(data)
    logi(1+32*(ct-1):ct*32)=bitget(data(1+(ct-1)),1:32);
end
%count consecutive 1s and 0s
Lct=1;
L=1;i = logi(1);
for ct = 2:length(logi)
    if logi(ct)==i
        L(Lct)=L(Lct)+1;
    else
        i=logi(ct);
        Lct=Lct+1;
        L(Lct)=1;
    end
end

>> L = 54    39     3

注意:我花了一些时间才弄清楚问题。因此,关于语言和问题的确切性质的 cmets。希望(经过多次修改)这个问题现在可以找到并且答案对其他人也有用。

【问题讨论】:

  • 你能选择一种语言吗?这是一个有趣的问题,但您以 C 和 C++ 为目标这一事实破坏了它。
  • 对不起,我使用 matlab,但这种语言会因此而变慢。所以我会将它实现为一个 mex 函数。因此我可以使用 c 或 c++。
  • 很遗憾没有人会在此基础上回答。两种语言中可用的工具差异太大。正如 Tamatoa 对 Moana 所说,“选一个,选一个。”。
  • 好的,那我们去c++
  • 总和必须为N*32,其中 N 为整数个数。在示例中:54+39+3=96 值是 1 和 0 序列的长度。

标签: c++ bit-manipulation


【解决方案1】:

之前我误解了这个问题。现在我知道你在问什么了。 这应该可以,我已经测试过了:

#include <iostream>
#include <deque>

using namespace std;

//old version for whole collection
void ConsecutiveOnesAndZeros(deque<uint32_t> values, deque<uint8_t> &outCount)
{
    int i;
    if (!values.empty()) {
        uint8_t count = 0, lastBit = (values[0] & 1);
        for (uint32_t &value : values)
        {
            for (i = 0; (i < 32) && (value != 0); i++)
            {
                if (lastBit != uint8_t((value >> i) & 1))
                {
                    outCount.push_back(count);
                    count = 0;
                    lastBit = !lastBit;
                }
                count++;
            }
            if (i < 32) count += (32 - i);
        }
        outCount.push_back(count);
    }
}

//stream version for receiving integer
void ConsecutiveOnesAndZeros(uint32_t value, uint8_t &count, uint8_t &lastBit, deque<uint8_t> &outCount)
{
    int i;
    for (i = 0; (i < 32) && (value != 0); i++)
    {
        if (lastBit != uint8_t((value >> i) & 1))
        {
            if(count) outCount.push_back(count);
            count = 0;
            lastBit = !lastBit;
        }
        count++;
    }
    if (i < 32) count += (32 - i);
}

int main()
{
    deque<uint8_t> outCount;
    deque<uint32_t> stream = { 4294967295u,4194303u,3758096384u };

    ConsecutiveOnesAndZeros(stream, outCount);
    for (auto res : outCount) {
        printf_s("%d,", res);
    }
    printf_s("\n");

    uint8_t count = 0, bit = 0;
    outCount.clear();
    for (auto val : stream) 
        ConsecutiveOnesAndZeros(val, count, bit, outCount);
    if (count) outCount.push_back(count);

    for (auto res : outCount) {
        printf_s("%d,", res);
    }
    printf_s("\n");

    system("pause");
}

更新 - 我对检查值 != 0 进行了一些优化。我还将 ConsecutiveOnesAndZeros 划分为两个函数,用于从接收到的流中给出下一个整数。

【讨论】:

  • 这不只是一个popcount吗?
  • 这个问题的答案如何?这会计算设置的位数。
【解决方案2】:

好吧,您可以尝试通过将第一部分分成线程来加快速度。

例如,如果您有一个您描述的函数,您可以将其中的几个称为std::threadstd::future,具体取决于您希望如何处理它。在它们都完成后,您可以比较两个边界位(一个在前一个的末尾,一个在下一个的开始),或者将第一个结果计数添加到最后一个结果计数,或者将结果推送到上一个,结果的所有其他部分都被推送到上一个而不进行任何比较。

如果您的输入很短,这当然会过度。

【讨论】:

    【解决方案3】:

    首先,要说你的样本数是错误的,因为第二个的最高位是一个,它应该大于2147483643,但它只是4194303,第三个应该是7,所以我猜你在将它们转换为十进制时已经反转了位位置。请参阅我在main() 开头的最后一个完整代码的评论,关于如何确定数字(在您的示例中看起来)与您的位模式对应的数字是(十六进制/十进制):

    [0xffffffff/4294967295][0xfffffc00/4294966272][0x00000007/7]
    

    (如果我们把更多的权重数字放在左边,为什么我们不也用二进制呢?)

    为了解决你的问题,你可以考虑当你有n连续的一个数字的LSB部分,你增加一个值,那么你有所有这些连续的切换到零(通过进位传播),直到您拥有的最后一个,并且如果您有n 连续零并递减该值,那么您将所有这些零转换为一......好吧,有一个更多位,因为进位再次进一步级联。这个想法是检查我们在 LSB 中有什么位,并根据这一点,递增或递减该值并将其与原始值进行异或……您将得到的结果是一个在LSB 与 LSB 相等的位加一,例如:

     1100100011111111
    

    由于 LSB 为 1,我们将其递增:

     1100100100000000
            ^^^^^^^^^ changed bits.
    

    如果我们现在将这个值与之前的值进行异或:

     0000000111111111  => 9 "1" bits, that indicate that 8 "1" consecutive bits were present
    

    如果我们准备一个switch 语句,其中包含我们可以从该函数中获得的所有可能值,您可以获得以下结果的非常有效的方法:

     int get_consecutive_bits(unsigned value)
     {
         unsigned next = value;
         switch (value) {
         case 0: case ~0: return 32; /* these are special cases, see below */
         }
         switch (value & 1) { /* get the lower bit */
         case 0: next--; break; /* decrement */
         case 1: next++; break; /* increment */
         }
         switch (value ^ next) { /* make the xor */
         case 0x00000003: return 1;
         case 0x00000007: return 2;
         case 0x0000000f: return 3;
         case 0x0000001f: return 4;
         case 0x0000003f: return 5;
         case 0x0000007f: return 6;
         /* ... */
         case 0xffffffff: return 31;
         } /* switch */
     }
    

    现在您必须累积该值,以防下一个数组单元以与您完成前一个相同的位值开始。我们从来没有0x00000001case 语句的原因是我们在第二位强制进位,所以我们总是有一个1 或更大的值,两个位改变(...0000001 =&gt; ...0000010 =&gt; ...0000011 和@987654337 @) 这也意味着对于值0000...00001111...1111,我们应该得到比字长多一点的值,使这些值变得特殊(因为它们使进位转到 msb 的下一位,第 33 位)所以我们首先检查这些值。

    这是在一个数组单元的块中完成任务的一种非常有效的方法。当你得到的值包括 MSB 时,你必须累积,因为下一个单词可以从你之前结束的那个位开始。

    下面的代码应该说明算法:

    pru_49297910.c

    /* pru_49297910.c -- answer to https://stackoverflow.com/questions/49297910/
     * Author: Luis Colorado <luiscoloradourcola@gmail.com>
     * Date: Wed Apr 24 11:12:21 EEST 2019
     * Copyright: (C) Luis Colorado.  All rights reserved.
     * License: BSD.  Open source.
     */
    
    #include <cassert>
    #include <iostream>
    
    #define BITS_PER_ELEMENT    32
    
    int get_consecutive_bits(unsigned value)
    {
        switch (value) {
        case 0: case ~0: /* these are special cases, see below */
                return BITS_PER_ELEMENT;
        }
        unsigned next = value;
        switch (value & 1) { /* get the lower bit */
        case 0: next--; break; /* decrement */
        case 1: next++; break; /* increment */
        }
        switch (value ^ next) { /* make the xor */
        case 0x00000003: return 1;      case 0x00000007: return 2;
        case 0x0000000f: return 3;      case 0x0000001f: return 4;
        case 0x0000003f: return 5;      case 0x0000007f: return 6;
        case 0x000000ff: return 7;      case 0x000001ff: return 8;
        case 0x000003ff: return 9;      case 0x000007ff: return 10;
        case 0x00000fff: return 11;     case 0x00001fff: return 12;
        case 0x00003fff: return 13;     case 0x00007fff: return 14;
        case 0x0000ffff: return 15;     case 0x0001ffff: return 16;
        case 0x0003ffff: return 17;     case 0x0007ffff: return 18;
        case 0x000fffff: return 19; case 0x001fffff: return 20;
        case 0x003fffff: return 21; case 0x007fffff: return 22;
        case 0x00ffffff: return 23; case 0x01ffffff: return 24;
        case 0x03ffffff: return 25; case 0x07ffffff: return 26;
        case 0x0fffffff: return 27; case 0x1fffffff: return 28;
        case 0x3fffffff: return 29; case 0x7fffffff: return 30;
        case 0xffffffff: return 31;
        } /* switch */
        assert(!"Impossible");
        return 0;
    }
    
    #define FLUSH() do{                         \
                runlen(accum, state);   \
            state ^= 1;                         \
            accum = 0;                          \
        } while (0)
    
    void run_runlen_encoding(unsigned array[], int n, void (*runlen)(int, unsigned))
    {
        int state = 0; /* always begin in 0 */
        int accum = 0; /* accumulated bits */
        while (n--) {
            /* see if we have to change */
            if (state ^ (array[n] & 1)) /* we changed state */
                        FLUSH();
                int nb = BITS_PER_ELEMENT; /* number of bits to check */
                int w = array[n];
            while (nb > 0) {
                        int b = get_consecutive_bits(w);
                        if (b < nb) {
                                accum += b;
                                FLUSH();
                                w >>= b;
                                nb -= b;
                        } else {  /* b >= nb, we only accumulate nb */
                    accum += nb;
                                nb = 0;
                        }
                }
        }
        if (accum)
                FLUSH();
    } /* run_runlen_encoding */
    
    void output_runlen(int n, unsigned kind)
    {
        if (n) { /* don't print for n == 0 */
                static int i = 0;
                std::cout << "[" << n << "/" << kind << "]";
                if (!(++i % 10))
                        std::cout << std::endl;
        }
    } /* output_runlen */
    
    int main()
    {
         /* 0b1111_1111_1111_1111_1111_1111_1111_1111, 0b1111_1111_1111_1111_1111_1100_0000_0000, 0b0000_0000_0000_0000_0000_0000_0000_0111 */
         /*    0xf____f____f____f____f____f____f____f,    0xf____f____f____f____f____c____0____0,    0x0____0____0____0____0____0____0____7 */
         /*                                0xffffffff,                                0xfffffc00,                                0x00000007 */
        unsigned int array[] =
    #if 1
            { 0xffffffff, 0xfffffc00, 0x00000007 }; /* correct values for your example */
    #else
                { 4294967295, 4194303, 3758096384 }; /* original values, only first matches. */
    #endif
        size_t array_n = sizeof array / sizeof array[0];
    
        run_runlen_encoding(array, array_n, output_runlen);
        std::cout << std::endl;
    } /* main */
    

    注意:

    由于我们需要计算进位位在一个增量中跳跃的距离,我们必须从低位到最高位,使输出的顺序与您尝试的相反,但我相信您会能够更改顺序以使其显示为您在问题中所述。

    程序输出显示:

    $ pru_49297910
    [3/1][39/0][54/1]
    

    【讨论】:

    • 感谢您的回答。这是解决问题的聪明方法!每个整数不再需要 32 次移位和 32 次与运算。我希望它会快很多,尤其是对于多个整数可以全为零或全一的情况。你也解释的很好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-13
    • 1970-01-01
    • 2020-04-09
    • 1970-01-01
    • 2018-10-23
    • 1970-01-01
    相关资源
    最近更新 更多