эффективно найти первый элемент, соответствующий битовой маске - PullRequest
14 голосов
/ 12 февраля 2012

У меня есть список N 64-битных целых чисел, биты которых представляют собой небольшие наборы. Каждое целое число имеет максимум k битов, установленных на 1. Учитывая битовую маску, я хотел бы найти первый элемент в списке, который соответствует маске, то есть element & mask == element.

Пример:

Если мой список:

index abcdef
  0   001100
  1   001010
  2   001000
  3   000100
  4   000010
  5   000001
  6   010000
  7   100000
  8   000000

и моя маска 111000, первый элемент, соответствующий маске, имеет индекс 2.

Метод 1:

Линейный поиск по всему списку. Это занимает время O ( N ) и пространство O (1).

Метод 2:

Предварительно вычислить дерево всех возможных масок, и на каждом узле сохранить ответ для этой маски. Это занимает O (1) времени для запроса, но занимает O (2 ^ 64) места.

Вопрос:

Как найти первый элемент, соответствующий маске, быстрее, чем O ( N ), при этом все еще занимая разумное количество места? Я могу позволить себе тратить полиномиальное время на предварительные вычисления, потому что будет много запросов. Ключ в том, что k мало. В моем приложении k <= 5 и <strong>N в тысячах. Маска имеет много единиц; Вы можете предположить, что он рисуется равномерно из пространства 64-битных целых.

Обновление:

Вот пример набора данных и простой тестовой программы, работающей в Linux: http://up.thirld.com/binmask.tar.gz. Для large.in, N = 3779 и k = 3. Первая строка - N , за которой следуют N беззнаковых 64-битных целых, представляющих элементы. Компилировать с make. Запустите с ./benchmark.e >large.out, чтобы создать истинный вывод, который вы можете затем использовать для сравнения. (Маски генерируются случайным образом, но случайное начальное число фиксируется.) Затем замените функцию find_first() на вашу реализацию.

Простой линейный поиск намного быстрее, чем я ожидал. Это потому, что k мало, и поэтому для случайной маски совпадение в среднем очень быстро обнаруживается.

Ответы [ 4 ]

3 голосов
/ 12 февраля 2012

Дерево суффиксов (в битах) сделает свое дело с исходным приоритетом в конечных узлах:

000000 -> 8
     1 -> 5
    10 -> 4
   100 -> 3
  1000 -> 2
    10 -> 1
   100 -> 0
 10000 -> 6
100000 -> 7

где, если бит установлен в маске, вы ищите обе руки, а если нет, вы ищете только 0 руку; ваш ответ - это минимальное число, с которым вы сталкиваетесь на листовом узле.

Вы можете улучшить это (незначительно), обходя биты не по порядку, а с максимальной различимостью; в вашем примере обратите внимание, что для 3 элементов установлен бит 2, поэтому вы должны создать

2:0 0:0 1:0 3:0 4:0 5:0 -> 8
                    5:1 -> 5
                4:1 5:0 -> 4
            3:1 4:0 5:0 -> 3
        1:1 3:0 4:0 5:0 -> 6
    0:1 1:0 3:0 4:0 5:0 -> 7
2:1 0:0 1:0 3:0 4:0 5:0 -> 2
                4:1 5:0 -> 1
            3:1 4:0 5:0 -> 0

В вашем примере с маской это не помогает (так как вы должны пересекать стороны как bit2 == 0, так и bit2 == 1, так как ваша маска установлена ​​в бите 2), но в среднем это улучшит результаты (но за счет настройки и более сложной структуры данных). Если некоторые биты будут установлены с большей вероятностью, чем другие, это может быть огромной победой. Если они довольно близки к случайным в списке элементов, то это совсем не поможет.

Если вы застряли с установленными по существу случайными битами, вы должны получить в среднем (1-5/64)^32 выгоду от подхода с суффиксным деревом (ускорение в 13 раз), что может быть лучше, чем разница в эффективности из-за использовать более сложные операции (но не рассчитывайте на это - битовые маски быстрые). Если в вашем списке есть неслучайное распределение битов, то вы можете сделать это почти произвольно.

2 голосов
/ 15 февраля 2012

Это побитовое дерево Kd.Обычно требуется менее 64 посещений за операцию поиска.В настоящее время выбор бита (размерности) для поворота является случайным.

#include <limits.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef unsigned long long Thing;
typedef unsigned long Number;

unsigned thing_ffs(Thing mask);
Thing rand_mask(unsigned bitcnt);

#define WANT_RANDOM 31
#define WANT_BITS 3

#define BITSPERTHING (CHAR_BIT*sizeof(Thing))
#define NONUMBER ((Number)-1)

struct node {
        Thing value;
        Number num;
        Number nul;
        Number one;
        char pivot;
        } *nodes = NULL;
unsigned nodecount=0;
unsigned itercount=0;

struct node * nodes_read( unsigned *sizp, char *filename);
Number *find_ptr_to_insert(Number *ptr, Thing value, Thing mask);

unsigned grab_matches(Number *result, Number num, Thing mask);
void initialise_stuff(void);

int main (int argc, char **argv)
{
Thing mask;
Number num;
unsigned idx;

srand (time(NULL));
nodes = nodes_read( &nodecount, argv[1]);
fprintf( stdout, "Nodecount=%u\n", nodecount );
initialise_stuff();

#if WANT_RANDOM
mask = nodes[nodecount/2].value | nodes[nodecount/3].value ;
#else
mask = 0x38;
#endif

fprintf( stdout, "\n#### Search mask=%llx\n", (unsigned long long) mask );

itercount = 0;
num = NONUMBER;
idx = grab_matches(&num,0, mask);
fprintf( stdout, "Itercount=%u\n", itercount );

fprintf(stdout, "KdTree search  %16llx\n", (unsigned long long) mask );
fprintf(stdout, "Count=%u Result:\n", idx);
idx = num;
if (idx >= nodecount) idx = nodecount-1;
fprintf( stdout, "num=%4u Value=%16llx\n"
        ,(unsigned) nodes[idx].num
        ,(unsigned long long) nodes[idx].value
        );

fprintf( stdout, "\nLinear search  %16llx\n", (unsigned long long) mask );
for (idx = 0; idx < nodecount; idx++) {
        if ((nodes[idx].value & mask) == nodes[idx].value) break;
        }
fprintf(stdout, "Cnt=%u\n", idx);
if (idx >= nodecount) idx = nodecount-1;
fprintf(stdout, "Num=%4u Value=%16llx\n"
        , (unsigned) nodes[idx].num
        , (unsigned long long) nodes[idx].value );

return 0;
}

void initialise_stuff(void)
{
unsigned num;
Number root, *ptr;
root = 0;

for (num=0; num < nodecount; num++) {
        nodes[num].num = num;
        nodes[num].one = NONUMBER;
        nodes[num].nul = NONUMBER;
        nodes[num].pivot = -1;
        }
nodes[num-1].value = 0; /* last node is guaranteed to match anything */

root = 0;
for (num=1; num < nodecount; num++) {
        ptr = find_ptr_to_insert (&root, nodes[num].value, 0ull );
        if (*ptr == NONUMBER) *ptr = num;
        else fprintf(stderr, "Found %u for %u\n"
                , (unsigned)*ptr, (unsigned) num );
        }
}

Thing rand_mask(unsigned bitcnt)
{struct node * nodes_read( unsigned *sizp, char *filename)
{
struct node *ptr;
unsigned size,used;
FILE *fp;

if (!filename) {
        size = (WANT_RANDOM+0) ? WANT_RANDOM : 9;
        ptr = malloc (size * sizeof *ptr);
#if (!WANT_RANDOM)
        ptr[0].value = 0x0c;
        ptr[1].value = 0x0a;
        ptr[2].value = 0x08;
        ptr[3].value = 0x04;
        ptr[4].value = 0x02;
        ptr[5].value = 0x01;
        ptr[6].value = 0x10;
        ptr[7].value = 0x20;
        ptr[8].value = 0x00;
#else
        for (used=0; used < size; used++) {
                ptr[used].value = rand_mask(WANT_BITS);
                }
#endif /* WANT_RANDOM */
        *sizp = size;
        return ptr;
        }

fp = fopen( filename, "r" );
if (!fp) return NULL;
fscanf(fp,"%u\n",  &size );
fprintf(stderr, "Size=%u\n", size);
ptr = malloc (size * sizeof *ptr);
for (used = 0; used < size; used++) {
        fscanf(fp,"%llu\n",  &ptr[used].value );
        }

fclose( fp );
*sizp = used;
return ptr;
}

Thing value = 0;
unsigned bit, cnt;

for (cnt=0; cnt < bitcnt; cnt++) {
        bit = 54321*rand();
        bit %= BITSPERTHING;
        value |= 1ull << bit;
        }
return value;
}

Number *find_ptr_to_insert(Number *ptr, Thing value, Thing done)
{
Number num=NONUMBER;

while ( *ptr != NONUMBER) {
        Thing wrong;

        num = *ptr;
        wrong = (nodes[num].value ^ value) & ~done;
        if (nodes[num].pivot < 0) { /* This node is terminal */
                /* choose one of the wrong bits for a pivot .
                ** For this bit (nodevalue==1 && searchmask==0 )
                */
                if (!wrong) wrong = ~done ;
                nodes[num].pivot  = thing_ffs( wrong );
                }
        ptr = (wrong & 1ull << nodes[num].pivot) ? &nodes[num].nul : &nodes[num].one;
        /* Once this bit has been tested, it can be masked off. */
        done |= 1ull << nodes[num].pivot ;
        }
return ptr;
}

unsigned grab_matches(Number *result, Number num, Thing mask)
{
Thing wrong;
unsigned count;

for (count=0; num < *result; ) {
        itercount++;
        wrong = nodes[num].value & ~mask;
        if (!wrong) { /* we have a match */
                if (num < *result) { *result = num; count++; }
                /* This is cheap pruning: the break will omit both subtrees from the results.
                ** But because we already have a result, and the subtrees have higher numbers
                ** than our current num, we can ignore them. */
                break;
                }
        if (nodes[num].pivot < 0) { /* This node is terminal */
                break;
                }
        if (mask & 1ull << nodes[num].pivot) {
                /* avoid recursion if there is only one non-empty subtree */
                if (nodes[num].nul >= *result) { num = nodes[num].one; continue; }
                if (nodes[num].one >= *result) { num = nodes[num].nul; continue; }
                count += grab_matches(result, nodes[num].nul, mask);
                count += grab_matches(result, nodes[num].one, mask);
                break;
                }
        mask |= 1ull << nodes[num].pivot;
        num = (wrong & 1ull << nodes[num].pivot) ? nodes[num].nul : nodes[num].one;
        }
return count;
}

unsigned thing_ffs(Thing mask)
{
unsigned bit;

#if 1
if (!mask) return (unsigned)-1;
for ( bit=random() % BITSPERTHING; 1 ; bit += 5, bit %= BITSPERTHING) {
        if (mask & 1ull << bit ) return bit;
        }
#elif 0
for (bit =0; bit < BITSPERTHING; bit++ ) {
        if (mask & 1ull <<bit) return bit;
        }
#else
mask &= (mask-1); // Kernighan-trick
for (bit =0; bit < BITSPERTHING; bit++ ) {
        mask >>=1;
        if (!mask) return bit;
        }
#endif

return 0xffffffff;
}

struct node * nodes_read( unsigned *sizp, char *filename)
{
struct node *ptr;
unsigned size,used;
FILE *fp;

if (!filename) {
        size = (WANT_RANDOM+0) ? WANT_RANDOM : 9;
        ptr = malloc (size * sizeof *ptr);
#if (!WANT_RANDOM)
        ptr[0].value = 0x0c;
        ptr[1].value = 0x0a;
        ptr[2].value = 0x08;
        ptr[3].value = 0x04;
        ptr[4].value = 0x02;
        ptr[5].value = 0x01;
        ptr[6].value = 0x10;
        ptr[7].value = 0x20;
        ptr[8].value = 0x00;
#else
        for (used=0; used < size; used++) {
                ptr[used].value = rand_mask(WANT_BITS);
                }
#endif /* WANT_RANDOM */
        *sizp = size;
        return ptr;
        }

fp = fopen( filename, "r" );
if (!fp) return NULL;
fscanf(fp,"%u\n",  &size );
fprintf(stderr, "Size=%u\n", size);
ptr = malloc (size * sizeof *ptr);
for (used = 0; used < size; used++) {
        fscanf(fp,"%llu\n",  &ptr[used].value );
        }

fclose( fp );
*sizp = used;
return ptr;
}

ОБНОВЛЕНИЕ:

Я немного поэкспериментировал с выбором поворота, отдавая предпочтение битам с наибольшим дискриминационным значением(«информационный контент»).Это включает в себя:

  • создание гистограммы использования битов (может быть сделано при инициализации)
  • при построении дерева: выбор одного с частотой, ближайшей к 1/2 в оставшихся поддеревьях .

Результат: выбор случайного поворота выполнен лучше.

1 голос
/ 13 февраля 2012

С предварительно вычисленными битовыми масками.Формально is все еще O (N), так как операции and-mask - O (N).Последним проходом также является O (N), потому что ему нужно найти самый низкий установленный бит, но это также может быть ускорено.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

  /* For demonstration purposes.
  ** In reality, this should be an unsigned long long */
typedef unsigned char Thing;

#define BITSPERTHING (CHAR_BIT*sizeof (Thing))
#define COUNTOF(a) (sizeof a / sizeof a[0])

Thing data[] =
/****** index abcdef */
{ 0x0c /* 0   001100 */
, 0x0a /* 1   001010 */
, 0x08 /* 2   001000 */
, 0x04 /* 3   000100 */
, 0x02 /* 4   000010 */
, 0x01 /* 5   000001 */
, 0x10 /* 6   010000 */
, 0x20 /* 7   100000 */
, 0x00 /* 8   000000 */
};

        /* Note: this is for demonstration purposes.
        ** Normally, one should choose a machine wide unsigned int
        ** for bitmask arrays.
        */
struct bitmap {
        char data[ 1+COUNTOF (data)/ CHAR_BIT ];
        } nulmaps [ BITSPERTHING ];

#define BITSET(a,i) (a)[(i) / CHAR_BIT ] |= (1u <<  ((i)%CHAR_BIT) )
#define BITTEST(a,i) ((a)[(i) / CHAR_BIT ] & (1u <<  ((i)%CHAR_BIT) ))

void init_tabs(void);
void map_empty(struct bitmap *dst);
void map_full(struct bitmap *dst);
void map_and2(struct bitmap *dst, struct bitmap *src);

int main (void)
{
Thing mask;
struct bitmap result;
unsigned ibit;

mask = 0x38;
init_tabs();
map_full(&result);

for (ibit = 0; ibit < BITSPERTHING; ibit++) {
        /* bit in mask is 1, so bit at this position is in fact a don't care */
        if (mask & (1u <<ibit))  continue;
        /* bit in mask is 0, so we can only select items with a 0 at this bitpos */
        map_and2(&result, &nulmaps[ibit] );
        }

        /* This is not the fastest way to find the lowest 1 bit */
for (ibit = 0; ibit < COUNTOF (data); ibit++) {
        if (!BITTEST(result.data, ibit) ) continue;
        fprintf(stdout, " %u", ibit);
        }
fprintf( stdout, "\n" );
return 0;
}

void init_tabs(void)
{
unsigned ibit, ithing;

        /* 1 bits in data that dont overlap with 1 bits in the searchmask are showstoppers.
        ** So, for each bitpos, we precompute a bitmask of all *entrynumbers* from data[], that contain 0 in bitpos.
        */
memset(nulmaps, 0 , sizeof nulmaps);
for (ithing=0; ithing < COUNTOF(data); ithing++) {
        for (ibit=0; ibit < BITSPERTHING; ibit++) {
                if ( data[ithing] & (1u << ibit) ) continue;
                BITSET(nulmaps[ibit].data, ithing);
                }
        }
}

        /* Logical And of two bitmask arrays; simular to dst &= src */
void map_and2(struct bitmap *dst, struct bitmap *src)
{
unsigned idx;
for (idx = 0; idx < COUNTOF(dst->data); idx++) {
        dst->data[idx] &= src->data[idx] ;
        }
}

void map_empty(struct bitmap *dst)
{
memset(dst->data, 0 , sizeof dst->data);
}

void map_full(struct bitmap *dst)
{
unsigned idx;
        /* NOTE this loop sets too many bits to the left of COUNTOF(data) */
for (idx = 0; idx < COUNTOF(dst->data); idx++) {
        dst->data[idx] = ~0;
        }
}
1 голос
/ 12 февраля 2012

Построить двоичное дерево следующим образом:

  1. Каждый уровень соответствует биту
  2. Соответствующий бит находится справа, в противном случае слева

Таким образом вставьте каждое число в базу данных.

Теперь для поиска: если соответствующий бит в маске равен 1, обойти обоих потомков. Если это 0, проходите только левый узел. По сути, продолжайте обходить дерево, пока не дойдете до листового узла (кстати, 0 - это удар по каждой маске!).

Это дерево будет иметь O (N) места.

Например, дерево для 1 (001), 2 (010) и 5 ​​(101)

         root
        /    \
       0      1
      / \     |
     0   1    0
     |   |    |
     1   0    1
    (1) (2)  (5)
...