Есть ли лучшая реализация для подсчета уникальных пар целых чисел?

Это на С++. Мне нужно вести счет для каждой пары чисел. Два числа имеют тип «int». Я сортирую два числа, поэтому пара (n1 n2) совпадает с парой (n2 n1). Я использую std::unordered_map в качестве контейнера.

Я использовал элегантную функцию сопряжения, разработанную Мэтью Шудзиком, Wolfram Research, Inc.. В моей реализации функция дает мне уникальное число типа "long" (64 бита на моей машине) для каждой пары двух чисел типа "int". Я использую это как мой ключ для unordered_map (std::unordered_map). Есть ли лучший способ вести подсчет таких пар? Под «лучше» я подразумеваю «быстрее» и, если возможно, с меньшим использованием памяти.

Кроме того, мне не нужны все биты long. Даже если вы можете предположить, что два числа могут достигать максимального значения для 32 бит, я предполагаю, что максимально возможное значение моей функции сопряжения потребует не более 36 бит. Если ничего другого, по крайней мере, есть ли способ использовать всего 36 бит в качестве ключа для unordered_map? (какой-то другой тип данных)

Я думал об использовании набора битов, но я не совсем уверен, будет ли std::hash генерировать уникальный ключ для любого заданного набора битов из 36 бит, который можно использовать в качестве ключа для unordered_map.

Буду очень признателен за любые мысли, предложения и т.


person learningToCode    schedule 06.10.2014    source источник
comment
Как насчет std::set длины 2 для каждой пары? Таким образом, порядок не важен.   -  person Cory Kramer    schedule 06.10.2014
comment
Итак, вход без знака?   -  person IdeaHat    schedule 06.10.2014
comment
Хорошо, и использовать набор как ключ для unordered_map?   -  person learningToCode    schedule 06.10.2014
comment
Вход может быть любым. Положительные целые числа. Я использовал int, но unsigned int также будет работать.   -  person learningToCode    schedule 06.10.2014
comment
long - не полагайтесь на машину, используйте более конкретные типы, например: uint64_t   -  person Karoly Horvath    schedule 06.10.2014
comment
Спасибо. Я понимаю, что такие конкретные типы решат только проблемы с переносимостью. Я буду использовать его.   -  person learningToCode    schedule 06.10.2014
comment
Вопрос: знаете ли вы все пары целых чисел, которые вам нужно вести подсчет в начале? Или новые пары целых чисел могут быть вставлены в любое время?   -  person Nir Friedman    schedule 07.10.2014
comment
@NirFriedman, я не знаю всех пар целых чисел в начале. Любая новая пара может возникнуть во время выполнения. В любом случае, что, если бы я знал, что тогда?   -  person learningToCode    schedule 07.10.2014
comment
Затем это: en.wikipedia.org/wiki/.   -  person Nir Friedman    schedule 07.10.2014


Ответы (2)


Прежде всего, я думаю, что вы пришли с неправильным предположением. Для std::unordered_map и std::unordered_set хэш не обязательно должен быть уникальным (и в принципе не может быть таким для таких типов данных, как, например, std::string), должна быть низкая вероятность того, что 2 разных ключа будут генерировать одно и то же значение хеш-функции. Но если произойдет столкновение, это не будет концом света, просто доступ будет медленнее. Я бы сгенерировал 32-битный хеш из 2 чисел, и если у вас есть представление о типичных значениях, просто проверьте вероятность столкновения хэшей и соответственно выберите хеш-функцию.

Чтобы это работало, вы должны использовать пару 32-битных чисел в качестве ключа в std::unordered_map и предоставить правильную хеш-функцию. Вычисление уникального 64-битного ключа и его использование с хэш-картой вызывает споры, поскольку hash_map затем вычисляет другой хэш этого ключа, поэтому, возможно, вы делаете его медленнее.

Что касается 36-битного ключа, это не очень хорошая идея, если у вас нет специального процессора, который обрабатывает 36-битные данные. Ваши данные либо будут выровнены по 64-битной границе, и у вас не будет никаких преимуществ экономии памяти, либо в противном случае вы получите штраф за невыровненный доступ к данным. В первом случае у вас будет просто дополнительный код для получения 36 бит из 64-битных данных (если процессор поддерживает это). Во втором ваш код будет медленнее, чем 32-битный хэш, даже если есть некоторые коллизии.

Если этот hash_map является узким местом, вы можете рассмотреть другую реализацию хеш-карты, например goog-sparsehash.sourceforge.net.

person Slava    schedule 06.10.2014
comment
Спасибо. В этом есть смысл. Я хотел, чтобы он был уникальным, чтобы я мог просто использовать файл unordered_map. Если он не уникален, то я должен реализовать свою собственную таблицу, правильно? Или я где-то ошибаюсь? - person learningToCode; 06.10.2014
comment
@learningToCode обновил ответ, нет, вам не нужно повторно реализовывать unordered_map - person Slava; 06.10.2014
comment
большое спасибо. Это действительно интересно и не очевидно для меня. Если мой хэш генерирует один и тот же ключ для двух разных входов (однако с низкой вероятностью), и позволяет вызвать ключ типа «K» (uint32_t). Скажем, у меня есть таблица std::unordered_map‹uint32_t, int›. Я использовал его как таблицу [K]++ для увеличения счетчика. Итак, я не понимаю, как может быть возможно разрешение двух разных пар, отображаемых в K. Я посмотрю, но если это что-то простое, пожалуйста, дайте мне знать или перенаправьте меня на него, и большое спасибо. - person learningToCode; 06.10.2014
comment
@learningToCode, вы неправильно понимаете концепцию хэш-карты. Ключ в карте должен быть парой чисел, а не хешем. Хеш-функция указывается отдельно, и на самом деле не имеет большого значения, выдает ли она 64 или 32 бита, поскольку хэш не хранится в карте. Если вы действительно хотите сэкономить место, вам нужно найти способ уникально упаковать эту пару в 32 бита, 36 бит не сэкономят место и не увеличат скорость, если только вы не найдете процессор, который изначально работает с 36 битами, в чем я сомневаюсь. - person Slava; 07.10.2014
comment
@learningToCode извините, изначально не совсем понял ваш вопрос и пропустил ту часть, в которой вы используете 64-битный ключ в качестве ключа. Обновленный ответ. - person Slava; 07.10.2014
comment
Спасибо. Я не знал, что могу настроить хэш-функцию unordered_map. По какой-то причине я просто предположил, что если бы я хотел отобразить два числа, мне пришлось бы сгенерировать какой-то уникальный ключ в качестве индекса для хэш-карты. Я рассмотрю непосредственное указание хеш-функций. Таким образом, я думаю, что смогу сгенерировать 32-битный код, и, надеюсь, он сможет справиться с коллизиями. Спасибо! Дайте мне знать, если я что-то пропустил. - person learningToCode; 07.10.2014

Мои два цента, функции сопряжения, описанные в статье, НАМНОГО сложнее, чем вам на самом деле нужно. Сопоставить 2 32-битных значения UNISIGNED с 64 однозначно легко. Следующее делает это и даже обрабатывает непарные состояния, не слишком сильно ударяя по математическому периферийному устройству (если вообще).

uint64_t map(uint32_t a, uint32_t b)
{
    uint64_t x = a+b;
    uint64_t y = abs((int32_t)(a-b));

    uint64_t ans = (x<<32)|(y);
    return ans;
}

void unwind(uint64_t map, uint32_t* a, uint32_t* b)
{
  uint64_t x = map>>32;
  uint64_t y = map&0xFFFFFFFFL;

  *a = (x+y)>>1;
  *b = (x-*a);
}

Другая альтернатива:

uint64_t map(uint32_t a, uint32_t b)
{
  bool bb = a>b;
    uint64_t x = ((uint64_t)a)<<(32*(bb));
    uint64_t y = ((uint64_t)b)<<(32*!(bb));

    uint64_t ans = x|y;
    return ans;
}

void unwind(uint64_t map, uint32_t* a, uint32_t* b)
{

  *a = map>>32;
  *b = map&0xFFFFFFFF;
}

Это работает как уникальный ключ. Вы можете легко изменить его, чтобы он был поставщиком хэш-функции для неупорядоченной карты, хотя будет ли это быстрее, чем std::map, зависит от количества полученных вами значений.

ПРИМЕЧАНИЕ: это не удастся, если значения a+b > 32 бит.

person IdeaHat    schedule 06.10.2014
comment
Спасибо. Я должен был подумать об этом. Просто любопытно, почему вам нужно складывать и вычитать два числа, а не просто сдвигать одно на первые 32 бита, а следующее число как остальные 32 бита 64-битного числа? - person learningToCode; 07.10.2014
comment
@learningToCode Я хотел избежать ветвления и зафиксировать тот факт, что (a, b) == (b, a). Также у меня есть склонность слишком много обдумывать. Предоставлен альтернативный вариант, который должен делать именно то, что вы предложили, без ветвления, и, вероятно, такой же быстрый, хотя вам придется его измерить. - person IdeaHat; 07.10.2014
comment
Спасибо за ваше время. Это мой первый день в stackoverflow в качестве члена. Я многому учусь. Спасибо! - person learningToCode; 07.10.2014