Точки запроса в вершинах куба Хэмминга

У меня есть N точек, лежащих только на вершинах куба размерности D, где D примерно равно 3.

Вершина не может содержать ни одной точки. Таким образом, каждая точка имеет координаты в {0, 1}D. Меня интересует только время запроса, пока стоимость памяти является разумной (например, не экспоненциальной в N :)).

Учитывая запрос, лежащий в одной из вершин куба, и входной параметр r, найдите все вершины (таким образом, точки), которые имеют расстояние Хэмминга ‹= r с запросом.

Как можно c++ среда?


Я думаю о kd-дереве, но я не уверен и нуждаюсь в помощи, любой вклад, даже приблизительный, будет оценен! Поскольку в игру вступает расстояние Хэмминга, должны помочь побитовые манипуляции (например, XOR).


person gsamaras    schedule 23.11.2016    source источник
comment
Минус, почему? :/ Пожалуйста, г-н Downvoter, предложите, как я могу улучшить свой вопрос - вы даже не проголосовали против, чтобы помочь мне...!   -  person gsamaras    schedule 23.11.2016
comment
Не мой -1, но я так понимаю проклятие размерности означает именно то, что для D››1 никто не знает хорошего способа решить эту проблему. По мере увеличения D самый глупый из возможных алгоритмов (проверьте каждую из ваших N точек, чтобы увидеть, находится ли она в пределах r от точки запроса) быстро становится самым известным алгоритмом.   -  person j_random_hacker    schedule 23.11.2016
comment
@j_random_hacker, как один из тех, кто создал kd-geraf, я могу сказать, что мы не здесь столько всего связано с проклятием размерности, я имею в виду, что D никогда не будет 10.000 в моем случае, но спасибо за подсказку, я это исправлю! ;)   -  person gsamaras    schedule 23.11.2016
comment
Используя некоторые приемы с битовой математикой, вы можете просто перечислить все вершины, которые находятся на расстоянии ровно k, а затем просто зациклить k до r. Я уже отвечал на это раньше, позвольте мне просто найти его ..   -  person harold    schedule 23.11.2016
comment
Да, @harold, вы прокомментировали в то же время, когда я прокомментировал, что побитовые манипуляции должны помочь, я ищу что-то подобное. Отлично, буду ждать!   -  person gsamaras    schedule 23.11.2016
comment
Я нашел, но контекст был совсем другим, и это была Java.. тем не менее, та же идея применима   -  person harold    schedule 23.11.2016
comment
@harold интересно, я бы посоветовал вам немного изменить его, чтобы он соответствовал моему вопросу, и опубликовать ответ, пожалуйста. :)   -  person gsamaras    schedule 23.11.2016
comment
N точек, лежащих только на вершинах куба, и вершина A не может содержать ни одной точки: кхм, разве это не противоречит? Что мне не хватает?   -  person Yves Daoust    schedule 25.11.2016
comment
D — это что-то вроде 3: зачем тогда волноваться?   -  person Yves Daoust    schedule 25.11.2016
comment
@YvesDaoust Мне жаль, что вы проголосовали против. В моих реальных приложениях я сопоставляю точки с гиперкубом (точки назначаются гиперкубу), но во время этого процесса сопоставления некоторые вершины могут не получить ни одной точки! Что ж, D = 3 — очень сильное предположение, мне просто нужно было сделать это, чтобы пережить -2 в то время. Идея состоит в том, чтобы получить ответ на этот вопрос, а затем обобщить его самостоятельно!   -  person gsamaras    schedule 25.11.2016
comment
чтобы выжить -2: ??? И вы не ответили на мой вопрос.   -  person Yves Daoust    schedule 25.11.2016
comment
Да @YvesDaoust, я собирался удалить вопрос, так как он был слишком широким (см. комментарии выше), поэтому я решил сделать несколько сильных предположений. Я думал, что сделал, что я не покрыл? :/   -  person gsamaras    schedule 25.11.2016


Ответы (1)


Существует хороший битхак, позволяющий перейти от одной битовой маски с k битами к следующей лексикографически перестановке., что означает, что достаточно просто перебрать все маски с установленными k битами. XOR этих масок с начальным значением дает все значения на расстоянии Хэмминга точно k от него.

Итак, для D измерений, где D меньше 32 (в противном случае измените типы),

uint32_t limit = (1u << D) - 1;
for (int k = 1; k <= r; k++) {
    uint32_t diff = (1u << k) - 1;
    while (diff <= limit) {
        // v is the input vertex
        uint32_t vertex = v ^ diff;
        // use it
        diff = nextBitPermutation(diff);
    }
}

Где nextBitPermutation может быть реализовано на C++ как-то так (если у вас есть __builtin_ctz)

uint32_t nextBitPermutation(uint32_t v) {
    // see https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
    uint32_t t = v | (v - 1);
    return (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));
}

Или для MSVC (не проверено)

uint32_t nextBitPermutation(uint32_t v) {
    // see https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
    uint32_t t = v | (v - 1);
    unsigned long tzc;
    _BitScanForward(&tzc, v); // v != 0 so the return value doesn't matter
    return (t + 1) | (((~t & -~t) - 1) >> (tzc + 1));
}

Если D действительно низкое, 4 или ниже, старое popcnt-с-pshufb работает очень хорошо, и в целом все просто хорошо выстраивается, вот так:

uint16_t query(int vertex, int r, int8_t* validmask)
{
    // validmask should be array of 16 int8_t's,
    // 0 for a vertex that doesn't exist, -1 if it does
    __m128i valid = _mm_loadu_si128((__m128i*)validmask);
    __m128i t0 = _mm_set1_epi8(vertex);
    __m128i r0 = _mm_set1_epi8(r + 1);
    __m128i all =        _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    __m128i popcnt_lut = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2,  2,  3,  2,  3,  3,  4);
    __m128i dist = _mm_shuffle_epi8(popcnt_lut, _mm_xor_si128(t0, all));
    __m128i close_enough = _mm_cmpgt_epi8(r0, dist);
    __m128i result = _mm_and_si128(close_enough, valid);
    return _mm_movemask_epi8(result);
}

Это должно быть довольно быстро; быстро по сравнению с приведенным выше битхаком (там часто используется nextBitPermutation, который довольно тяжелый), а также по сравнению с циклом по всем вершинам и проверкой, находятся ли они в диапазоне (даже со встроенным popcnt, который автоматически занимает не менее 16 циклов и выше не должен, если все кэшируется или даже постоянно находится в регистре). Недостатком является то, что с результатом неудобно работать, так как это маска того, какие вершины существуют и находятся в диапазоне запрашиваемой точки, а не их список. Однако это будет хорошо сочетаться с некоторой обработкой данных, связанных с точками.

Конечно, это также масштабируется до D = 3, просто не делайте ни одну из точек> = 8 действительными. D>4 можно сделать аналогичным образом, но для этого требуется больше кода, и, поскольку это действительно решение грубой силы, которое быстро только из-за параллелизма, оно существенно замедляется экспоненциально в D.

person harold    schedule 23.11.2016
comment
Я могу использовать unit8_t, так как D больше не будет, верно? Но проблема в том, что я не понимаю, какой запрос, это v? И где мои баллы? Некоторые (многие?) вершины куба могут быть пустыми, поэтому не все перестановки указывают на точки. - person gsamaras; 23.11.2016
comment
@gsamaras да, v. И да, это перестает быть хорошей идеей, если N намного меньше 2 ^ D, и в этом случае на попадание в пустое пространство тратится слишком много времени. Вы в такой ситуации? - person harold; 23.11.2016
comment
На самом деле для D ‹ 9 у меня есть другая идея, просто нужно немного поработать над этим. - person harold; 23.11.2016
comment
Нет, D должно быть чем-то вроде log(N)/2. Итак, что вы делаете на каждой итерации, так это то, что на основе запроса v вы производите ВСЕ возможные перестановки с расстоянием Хэмминга k? Например v = 111, k = 1 и D = 3, тогда вы производите 011, 101, 110? Может быть, я должен сделать минимальный пример, чтобы проверить это. | О новой идее: О, круто, может быть, вы можете опубликовать это как второй ответ. :) - person gsamaras; 23.11.2016
comment
@gsamaras новая идея пока не работает, детали будут во многом зависеть от того, что вы на самом деле будете делать с результатами. - person harold; 23.11.2016
comment
У меня есть N баллов и один запрос, верно? Я хочу найти все точки, которые удовлетворяют этому: hamming_dist(query, current_point) <= r. Я хочу сообщить об этих пунктах, вот и все, это моя цель, понятно? - person gsamaras; 23.11.2016
comment
Хорошо, это немного более раздражает, чем, скажем, их подсчет или суммирование соответствующих значений или что-то в этом роде. В любом случае, представляет ли интерес D=4? Особенно хорошо получается. - person harold; 23.11.2016
comment
Спасибо за обновленный ответ. Честно говоря, я еще не уверен, как сообщать о точках, которые удовлетворяют hamming_dist(query, current_point) <= r, но поэтому я пытаюсь понять, как использовать ваш query(), не могли бы вы привести наглядный пример (не минимальный)? :) - person gsamaras; 23.11.2016
comment
Давайте продолжим обсуждение в чате. - person harold; 23.11.2016