Найти позиции битов в байте

Я пытался создать реализацию разреженного октодерева, которую люди из 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;
}
Другие вопросы по тегам