作为标题sais,我想在可变大小(M)的位数组中找到n个一位的连续运行。
通常的用例是N <= 8且M <= 128
我在嵌入式设备的内环中做了很多这样的操作。编写一个简单的实现很容易但不够快我的口味(例如在找到解决方案之前进行强力搜索)。
我想知道是否有人在他的技巧包中有更优雅的解决方案。
答案 0 :(得分:4)
int nr = 0;
for ( int i = 0; i < M; ++i )
{
if ( bits[i] )
++nr;
else
{
nr = 0; continue;
}
if ( nr == n ) return i - nr + 1; // start position
}
蛮力是什么意思? O(M * N)或这个O(M)溶液?如果你的意思是这样,那么我不确定你能做多少优化。
我们可以通过遍历每个字节而不是每一个字来实现持续改进。想到这一点: 当我说字节时,我的意思是这次是N位的序列。
for ( int i = 0; i < M; i += N )
if ( bits[i] == 0 ) // if the first bit of a byte is 0, that byte alone cannot be a solution. Neither can it be a solution in conjunction with the previous byte, so skip it.
continue;
else // if the first bit is 1, then either the current byte is a solution on its own or it is a solution in conjunction with the previous byte
{
// search the bits in the previous byte.
int nrprev = 0;
while ( i - nrprev >= 0 && bits[i - nrprev] ) ++nrprev;
// search the bits in the current byte;
int nrcurr = 0;
while ( bits[i + nrcurr + 1] && nrcurr + nrprev <= N ) ++nrcurr;
if ( nrcurr + nrprev >= N ) // solution starting at i - nrprev + 1.
return i - nrprev + 1;
}
未经测试。可能需要一些额外的条件来确保正确性,但这个想法似乎很合理。
答案 1 :(得分:2)
答案 2 :(得分:1)
使用查找表展开内部循环。
有四类字节:
00000001 - // Bytes ending with one or more 1's. These start a run.
11111111 - // All 1's. These continue a run.
10000000 - // Bytes starting with 1's but ending with 0's. These end a run.
10111000 - // All the rest. These can be enders or short runs.
创建一个查找表,让您区分这些。然后一次处理一个字节的位数组。
修改的
我想对查找表的内容稍微模糊一些。具体来说,我建议您需要三个表,每个表有256个条目,具有以下特征:
Number of bits set.
Number of bits set before first zero.
Number of bits set after last zero.
根据您的操作方式,您可能不需要第一个。
答案 3 :(得分:1)
我在MIPS核心上运行的嵌入式设备上做了类似的事情。 MIPS架构包括CLZ
指令(“计数前导零”),它将返回指定寄存器的前导零位数。如果需要计算前导的一位,只需在调用CLZ
之前反转数据。
示例,假设您有一个C语言函数CLZ
作为汇编指令的别名:
unsigned numbits = 0, totalbits = 0;
while (data != 0 && numbits != N) {
numbits = CLZ(data); // count leading zeroes
data <<= numbits; // shift off leading zeroes
totalbits += numbits; // keep track of how many bits we've shifted off
numbits = CLZ(~data); // count leading ones
data <<= numbits; // shift off leading ones
totalbits += numbits; // keep track of how many bits we've shifted off
}
在此循环结束时,totalbits
将指示第一次运行N个连续一位的偏移量(以左为单位)。循环内的每一行都可以用单个汇编指令表示(第四行除外,它需要一秒钟用于反转操作)。
其他非MIPS架构可能有类似的指令。
答案 4 :(得分:1)
简单SWAR回答:
考虑到您正在检查的值V
,请使用N
M
位宽寄存器。对于n
中的所有N
,请将n
注册为V >> n
。
将bitwise AND(all N)
转储到另一个M-wide寄存器中。然后只需找到该寄存器中设置的位,这将是全位运行的开始。
我确定如果你没有M
位宽的寄存器,你可以将它调整为更小的寄存器大小。
答案 5 :(得分:1)
这很容易解决,而且你不需要count-zeroo指令。
y = x ^ x-1
为您提供1到1 {1}}中最不重要的1位字符串。
x
是下一个可能是1或0的个别位,
y + 1
从该位给出一个1的字符串,直到下一个1位。
然后你可以将搜索模式乘以(y + 1)并递归...
我正在研究一种算法来获取字符串......等等......
是的......很容易解决......当我正在努力的时候,注意还有另外一个技巧。如果将一个单词划分为x ^ x-(y+1)
位的子串,则一系列n
1必须至少包含一个子串。为简单起见,假设子串是4位,字是32位。您可以同时检查子字符串以快速过滤输入:
≥2n-1
这是有效的,因为在加法运算之后,const unsigned int word_starts = 0x11111111;
unsigned int word = whatever;
unsigned int flips = word + word_starts;
if ( carry bit from previous addition ) return true;
return ~ ( word ^ flips ) & word_starts;
中与flips
中的1位对应的每个位(除了第一位)等于(通过二进制加法的定义)
word_starts
你可以通过word ^ carry_from_right ^ 1
再次提取进位,取消和取消。如果没有设置进位,则不存在1字符串。
不幸的是,你必须检查最后的进位,C不能做,但大多数处理器都可以。
答案 6 :(得分:1)
如果您使用的是英特尔兼容平台,BSF(位扫描转发)和BSR(位扫描反转)asm指令可以帮助您丢弃第一个和最后一个零位。这比蛮力方法更有效。
答案 7 :(得分:1)
对于你正在做的事情,这可能有点过头但是我需要一些重量级的自定义文件系统块分配。如果N < 32然后你可以删除代码的后半部分。
为了向后兼容,第一个字的最高有效位被视为位0。
请注意,该算法在末尾使用一个标记字(全零)来停止任何搜索,而不是不断检查数组的结尾。还要注意,该算法允许搜索从位数组中的任何位置开始(通常是最后一次成功分配的结束),而不是始终从位数组的开头开始。
提供您自己的编译器特定的msbit32()函数。
#define leftMask(x) (((int32_t)(0x80000000)) >> ((x) - 1)) // cast so that sign extended (arithmetic) shift used
#define rightMask(x) (1 << ((x) - 1))
/* Given a multi-word bitmap array find a run of consecutive set bits and clear them.
*
* Returns 0 if bitrun not found.
* 1 if bitrun found, foundIndex contains the bit index of the first bit in the run (bit index 0 is the most significant bit of the word at lowest address).
*/
static int findBitRun(int runLen, uint32_t *pBegin, uint32_t *pStartMap, uint32_t *pEndMap, uint32_t *foundIndex)
{
uint32_t *p = pBegin;
unsigned int bit;
if (runLen == 1)
{ // optimise the simple & hopefully common case
do {
if (*p)
{
bit = msbit32(*p);
*p &= ~(1 << bit);
*foundIndex = ((p - pStartMap) * 32ul) + (31 - bit);
return 1;
}
if (++p > pEndMap)
{
p = pStartMap;
}
} while (p != pBegin);
}
else if (runLen < 32)
{
uint32_t rmask = (1 << runLen) - 1;
do {
uint32_t map = *p;
if (map)
{
// We want to find a run of at least runLen consecutive ones within the word.
// We do this by ANDing each bit with the runLen-1 bits to the right
// if there are any ones remaining then this word must have a suitable run.
// The single bit case is handled above so can assume a minimum run of 2 required
uint32_t w = map & (map << 1); // clobber any 1 bit followed by 0 bit
int todo = runLen - 2; // -2 as clobbered 1 bit and want to leave 1 bit
if (todo > 2)
{
w &= w << 2; // clobber 2 bits
todo -= 2;
if (todo > 4)
{
w &= w << 4; // clobber 4 bits
todo -= 4;
if (todo > 8)
{
w &= w << 8; // clobber 8 bits
todo -= 8;
}
}
}
w &= w << todo; // clobber any not accounted for
if (w) // had run >= runLen within word
{
bit = msbit32(w); // must be start of left most run
*p &= ~(rmask << ((bit + 1) - runLen));
*foundIndex = ((p - pStartMap) * 32ul) + (31 - bit);
return 1;
}
else if ((map & 1) && (p[1] & 0x80000000ul)) // assumes sentinel at end of map
{
// possibly have a run overlapping two words
// calculate number of bits at right of current word
int rbits = msbit32((map + 1) ^ map);
int lmask = rmask << ((32 + rbits) - runLen);
if ((p[1] | lmask) == p[1])
{
p[0] &= ~((1 << rbits) - 1);
p[1] &= ~lmask;
*foundIndex = ((p - pStartMap) * 32ul) + (32 - rbits);
return 1;
}
}
}
if (++p > pEndMap)
{
p = pStartMap;
}
} while (p != pBegin);
}
else // bit run spans multiple words
{
pEndMap -= (runLen - 1)/32; // don't run off end
if (pBegin > pEndMap)
{
pBegin = pStartMap;
}
do {
if ((p[0] & 1) && ((p[0] | p[1]) == 0xfffffffful)) // may be first word of run
{
uint32_t map = *p;
uint32_t *ps = p; // set an anchor
uint32_t bitsNeeded;
int sbits;
if (map == 0xfffffffful)
{
if (runLen == 32) // easy case
{
*ps = 0;
*foundIndex = (ps - pStartMap) * 32ul;
return 1;
}
sbits = 32;
}
else
{
sbits = msbit32((map + 1) ^ map);
}
bitsNeeded = runLen - sbits;
while (p[1] == 0xfffffffful)
{
if (bitsNeeded <= 32)
{
p[1] = ~(0xfffffffful << (32 - bitsNeeded));
while (p != ps)
{
*p = 0;
--p;
}
*ps &= ~rightMask(sbits);
*foundIndex = ((p - pStartMap) * 32ul) + (32 - sbits);
return 1;
}
bitsNeeded -= 32;
if (++p == pBegin)
{
++pBegin; // ensure we terminate
}
}
if ((bitsNeeded < 32) & (p[1] & 0x80000000ul))
{
uint32_t lmask = leftMask(bitsNeeded);
if ((p[1] | lmask) == p[1])
{
p[1] &= ~lmask;
while (p != ps)
{
*p = 0;
--p;
}
*ps &= ~rightMask(sbits);
*foundIndex = ((p - pStartMap) * 32ul) + (32 - sbits);
return 1;
}
}
}
if (++p > pEndMap)
{
p = pStartMap;
}
} while (p != pBegin);
}
return 0;
}