Найти позиции битов в байте
Я пытался создать реализацию разреженного октодерева, которую люди из nVidia ("Эффективные разреженные воксельные октреи") делали для своих воксельных вещей, когда натолкнулись на эту проблему:
У меня есть битовое поле типа byte (поэтому только 8 бит), которое говорит мне, где находятся листы октодерева (1 говорит, что лист, 0 означает, что нет листа, 8 узлов подключены -> 8 бит). То, что я хочу сделать сейчас, это вернуть массив позиций листа. Моя текущая реализация использует цикл while, чтобы узнать, установлен ли LSB. После этого вход смещается на 1. Вот как я это делаю:
int leafposition = _leafmask & _validmask;
int[] result = new int[8];
int arrayPosition = 0;
int iteration = 0;
while ( leafposition > 0 )
{
iteration++; //nodes are not zero-indexed ... ?
if ( (leafposition & 1) == 1 ) // LSB set?
{
result.SetValue( iteration, arrayPosition );
arrayPosition++;
};
leafposition = leafposition >> 1;
}
return result;
Это не выглядит элегантно и имеет две неприятные вещи:
- Этот цикл пока имитирует цикл
- результирующий массив, скорее всего, будет меньше, чем 8 значений, но изменение размера обходится дорого
Я ожидаю, что результат будет как [2,4,6]
для 42 (0010 1010)
,
Может ли кто-нибудь предложить более элегантное решение, которое все еще читабельно?
Результат
Я использую функцию для подсчета листьев октреи, которую я реализовал ранее, чтобы установить для массива соответствующий размер.
3 ответа
Если вы ищете краткость кода, я бы использовал это:
int[] result = new int[8];
byte leafposition = 42;
int arrayPosition = 0;
for (int iteration = 0; iteration < 8; ++iteration)
if ((leafposition & (1 << iteration)) != 0)
result[arrayPosition++] = iteration + 1; // one-indexed
Если вы хотите повысить производительность, я бы использовал предварительно заполненный массив (из 256 записей). Вы можете генерировать это статически (во время компиляции) или лениво (перед вызовом вашего метода в первый раз).
int[][] leaves =
{
/* 00000000 */ new int[] { },
/* 00000001 */ new int[] { 1 },
/* 00000010 */ new int[] { 2 },
/* 00000011 */ new int[] { 1, 2 },
/* 00000100 */ new int[] { 3 },
/* 00000101 */ new int[] { 1, 3 },
/* 00000110 */ new int[] { 2, 3 },
/* 00000111 */ new int[] { 1, 2, 3 },
/* 00001000 */ new int[] { 4 },
/* 00001001 */ new int[] { 1, 4 },
/* ... */
};
byte leafposition = 42;
int[] result = leaves[leafposition];
Редактировать: если вы используете таблицу поиска и можете позволить однократную инициализацию (которая будет амортизироваться при последующем многократном использовании), я бы предложил создать ее динамически (а не раздувать исходный код). Вот пример кода в LINQ; вместо этого вы можете использовать версию цикла.
int[][] leaves = new int[256][];
for (int i = 0; i < 256; ++i)
leaves[i] = Enumerable.Range(0, 8)
.Where(b => (i & (1 << b)) != 0)
.Select(b => b + 1)
.ToArray();
Вот решение в стиле C, которое использует __builtin_ffs
int arrayPosition = 0;
unsigned int tmp_bitmap = original_bitfield;
while (tmp_bitmap > 0) {
int leafposition = __builtin_ffs(tmp_bitmap) - 1;
tmp_bitmap &= (tmp_bitmap-1);
result[arrayPosition++] = leafposition;
}
Как насчет,
public static IEnumerable<int> LeafPositions(byte octet)
{
for (var i = 1; octet > 0; octet >>= 1, i++)
{
if ((octet & 1) == 1)
{
yield return i;
}
}
}
или, на мой взгляд, легче читать.
IEnumerable<int> LeafPositions(byte octet)
{
if ((octet & 1) == 1) yield return 1;
if ((octet & 2) == 2) yield return 2;
if ((octet & 4) == 4) yield return 3;
if ((octet & 8) == 8) yield return 4;
if ((octet & 16) == 16) yield return 5;
if ((octet & 32) == 32) yield return 6;
if ((octet & 64) == 64) yield return 7;
if ((octet & 128) == 128) yield return 8;
}
Или, идя в крайности
IEnumerable<int> LeafPositions(byte octet)
{
switch (octet)
{
case 1:
yield return 1;
break;
case 2:
yield return 2;
break;
case 3:
yield return 1;
yield return 2;
break;
...
case 255:
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
break;
}
yield break;
}