如何最快地枚举整数并返回打开的每个位的指数?已经看到使用 << 和 Math.Pow 的示例。想知道是否有其他更快的方法。
谢谢。
如何最快地枚举整数并返回打开的每个位的指数?已经看到使用 << 和 Math.Pow 的示例。想知道是否有其他更快的方法。
谢谢。
最快的方法?查找表几乎总是最快的方法。建立一个包含四十亿条目的int[][]数组,每个int一个,包含您想要的数字数组。当然,初始化表格需要一些时间,但查询将非常快速。
我注意到您没有用足够精确的方式说明“最快”的含义,以便任何人都可以回答这个问题。它是指包括启动时间在内的平均最快时间,还是假设启动成本可以忽略不计的边际查找时间?我的解决方案草图假定后者。
显然,具有2亿字节地址空间的32位机器将没有足够的地址空间来存储三十亿字节的数组。给自己弄一台64位机器。如果你想要它变快,你至少需要安装那么多物理内存——否则分页会使你死亡。
我真希望你能够因为每次查找少花几纳秒而值得购买所有这些额外的硬件。或者也许你实际上并不想要最快的方法?
:-)
我认为位运算可能是最快的。没有经过测试,但我认为以下代码应该很快(至少和IEnumerables一样快)。
IEnumerable<int> GetExponents(Int32 value)
{
for(int i=0; i<32; i++)
{
if(value & 1)
yield return i;
value >>= 1;
}
}
如果你想让它更快,你可以考虑返回一个List<int>
。
if (value & 1)
无法正常工作,会报错"无法将int转换为bool",因此我写成了if ((value & 1) == 1)
。 - RayIEnumerable
不会执行。这个主题中的某些示例优化:
第一个示例(最快 - 10M次运行的时间为2.35秒,范围为1..10M):
static uint[] MulDeBruijnBitPos = new uint[32]
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
static uint[] GetExponents(uint value)
{
uint[] data = new uint[32];
int enabledBitCounter = 0;
while (value != 0)
{
uint m = (value & (0 - value));
value ^= m;
data[enabledBitCounter++] = MulDeBruijnBitPos[(m * (uint)0x077CB531U) >> 27];
}
Array.Resize<uint>(ref data, enabledBitCounter);
return data;
}
另一个版本(第二快 - 10M次运行时间为3秒,范围为1..10M):
static uint[] GetExponents(uint value)
{
uint[] data = new uint[32];
int enabledBitCounter = 0;
for (uint i = 0; value > 0; ++i)
{
if ((value & 1) == 1)
data[enabledBitCounter++] = i;
value >>= 1;
}
Array.Resize<uint>(ref data, enabledBitCounter);
return data;
}
一个字节位的查找数组应该是在安全的C#代码中可以实现的最快方法。将整数中的4个字节(如有必要,转换为uint)移位并索引到数组中。
查找数组的元素可以是指数数组,或者根据位的操作方式,可能是执行工作的委托。
仅仅是为了好玩,这里有一个使用LINQ的一行代码。
虽然这不是最快的方法,但它并不比使用yield
和迭代器块的其他答案慢太多。
int test = 42;
// returns 1, 3, 5
// 2^1 + 2^3 + 2^5
// = 2 + 8 + 32
// = 42
var exponents = Enumerable.Range(0, 32).Where(x => ((test >> x) & 1) == 1);
为了更快的解决方案,我可能会返回一个普通的集合而不是使用迭代块。像这样:
int test = 2458217;
// returns 0, 3, 5, 6, 9, 15, 16, 18, 21
// 2^0 + 2^3 + 2^5 + 2^6 + 2^9 + 2^15 + 2^16 + 2^18 + 2^21
// = 1 + 8 + 32 + 64 + 512 + 32768 + 65536 + 262144 + 2097152
// = 2458217
var exponents = GetExponents(test);
// ...
public static List<int> GetExponents(int source)
{
List<int> result = new List<int>(32);
for (int i = 0; i < 32; i++)
{
if (((source >> i) & 1) == 1)
{
result.Add(i);
}
}
return result;
}
static int[] MulDeBruijnBitPos = new int[32]
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
static IEnumerable<int> GetExponents(UInt32 v)
{
UInt32 m;
while( v != 0 ) {
m = (v & (UInt32) (-v));
yield return MulDeBruijnBitPos[((UInt32) (m * 0x077CB531U)) >> 27];
v ^= m;
}
}
我想位移(<<)是最快的。
如果你不会被一点C++所困扰:
void func(int i, int& n, int a[]){
n = 0;
if (i < 0) a[n++] = 31;
i <<= 1;
if (i < 0) a[n++] = 30;
i <<= 1;
if (i < 0) a[n++] = 29;
i <<= 1;
...
if (i < 0) a[n++] = 0;
}