Создания уникального идентификатора в C++

каков наилучший способ создания уникального идентификатора из двух (или более) коротких ints в C++? Я пытаюсь однозначно идентифицировать вершины в графе. Вершины содержат от двух до четырех коротких ints в качестве данных, и в идеале ID будет своего рода хэшем из них. Предпочитает компактность и уникальность над скоростью и легкостью.

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

график представляет собой набор образцов из аудиофайла. Я использую график как цепочку Маркова для создания нового аудиофайла из старого файла. Поскольку каждая вершина хранит несколько выборок и указывает на другую выборку, а все выборки короткие, казалось естественным генерировать идентификатор из данных. Объединив их в длинный звучит хорошо, но что-то как просто, как просто 0 1 2 3 generateID - Это все, что мне нужно. не уверен, сколько места необходимо, чтобы гарантировать уникальность, если каждая вершина хранит 2 16-битные выборки, есть 2^32 возможные комбинации правильно? Итак, если каждая вершина хранит 4 выборки, есть 2^64 возможных комбинации?

конкретные решения для библиотеки и платформы не имеют отношения к этому вопросу. Я не хочу, чтобы кто-то еще, кто может скомпилировать мою программу, загружал дополнительные библиотеки или менял код в соответствии с их ОС.

9 ответов


простое решение-использовать 64-битное целое число, где нижние 16 бит-первая координата вершины, следующие 16 бит-вторая и так далее. Это будет уникальным для всех ваших вершин, хотя и не очень компактным.

Так вот какой-то недоделанный код для этого. Надеюсь, я правильно наложил гипс.

uint64_t generateId( uint16_t v1, uint16_t v2, uint16_t v3, uint16_t v4)
{ 
   uint64_t id;
   id = v1 | (((uint64_t)v2) << 16) | (((uint64_t)v3) << 32) | (((uint64_t)v4) << 48);
   return id;
}

необязательно это можно сделать с помощью союза (отличная идея от Leon Timmermans, см. комментарий). Очень чистый этот путь:

struct vertex
{
    uint16_t v1;
    uint16_t v2;
    uint16_t v3;
    uint16_t v4;
};

union vertexWithId
{
    vertex v;
    uint64_t id;
};

int main()
{
    vertexWithId vWithId;
    // Setup your vertices
    vWithId.v.v1 = 2;
    vWithId.v.v2 = 5;

    // Your id is automatically setup for you!
    std::cout << "Id is " << vWithId.id << std::endl;
    return 0;
}

иногда самые простые вещи работают лучше всего.

можете ли вы просто добавить поле id к объекту Vertex и присвоить ему номер в порядке построения?

static int sNextId = 0;
int getNextId() { return ++sNextId; }

используйте длинный длинный, чтобы вы могли хранить все 4 возможности, а затем bitshift каждый короткий:

((long long)shortNumberX)

убедитесь, что вы бросили перед сдвигом, или ваши данные могут упасть с конца.

Edit: забыл добавить, вы должны или их вместе.


Если вы предпочитаете мобильность, то boost:: tuple хорошо:

вы хотели бы кортеж из 4 элементов:

typedef boost::tuple<uint16,uint16,uint16,uint16> VertexID;

Вы можете назначить такой:

VertexID id = boost::make_tuple(1,2,3,4);

кортеж boost уже имеет поддержку сравнения, равенства и т. д., поэтому он прост в использовании в контейнерах и алгоритмах.


определение " ID " в вопросе не совсем ясно: вам нужно использовать его в качестве ключа для быстрого поиска вершин? Вы можете определить компаратор для std::map (см. ниже пример)

вам нужно уметь различать два вершинных объекта с одинаковыми координатами (но разными в другом поле)? Определите некоторую "фабрику id" (cfr. одноэлементный шаблон), который генерирует, например, последовательность ints, не связанную со значениями объектов вершин. - Много как предлагает Fire Lancer (но остерегайтесь проблем с безопасностью потоков!)

на мой взгляд, две вершины с одинаковыми координатами идентичны. Так зачем тебе еще одно удостоверение?

как только вы определите 'строгий слабый заказ ' на этом типе, вы можете использовать его как ключ в например std::map,

struct Vertex {
  typedef short int Value;
  Value v1, v2;

  bool operator<( const Vertex& other ) const {
    return v1 < other.v1 || ( v1 == other.v1 && v2 < other.v2 ) ;
};

Vertex x1 = { 1, 2 };
Vertex x2 = { 1, 3 };
Vertex y1 = { 1, 2 }; // too!

typedef std::set<Vertex> t_vertices;

t_vertices vertices;
vertices.insert( x1 );
vertices.insert( x2 );
vertices.insert( y1 ); // won't do a thing since { 1, 2 } is already in the set.

typedef std::map<Vertex, int> t_vertex_to_counter;
t_vertex_to_counter count;
count[ x1 ]++;
assert( count[x1] == 1 );
assert( count[y1] == 1 );
count[ x2 ]++;
count[ y1 ]++; 
assert( count[x1] == 2 );
assert( count[y1] == 2 );

Если вы находитесь в Windows, вы можете использоватьCoCreateGUID API, в Linux вы можете использовать /proc/sys/kernel/random / uuid, вы также можете посмотреть "libuuid".


Если вы создаете хэш-таблицу, в которой хранятся ваши вершины, я могу придумать несколько способов избежать столкновений:

  1. генерировать идентификаторы непосредственно из входных данных, не выбрасывая битов, и использовать хэш-таблицу, которая достаточно велика, чтобы содержать все возможные идентификаторы. С 64-битными идентификаторами последнее будет чрезвычайно проблематичным: вам придется использовать таблицу, которая меньше вашего диапазона идентификаторов, поэтому вам придется иметь дело с коллизиями. Даже с 32-битными идентификаторами вы потребуется более 4 ГБ ОЗУ, чтобы вытащить это без столкновений.
  2. генерировать идентификаторы последовательно, как Вы читаете в вершинах. К сожалению, это делает очень дорогостоящим поиск ранее прочитанных вершин для обновления их вероятностей, поскольку генератор последовательных идентификаторов не является хэш-функцией. Если объем данных, используемых для построения цепи Маркова, значительно меньше объема данных, который используется для генерации цепи Маркова (или если они оба небольшие), это не может быть проблемой.

кроме того, вы можете использовать реализацию хэш-таблицы, которая обрабатывает конфликты для вас (например,unordered_map/обработчик действия hash_map), и сосредоточиться на остальной части вашего приложения.


Ну единственный способ гарантировать, что ID уникален, чтобы иметь больше комбинаций, чем ваш вводный идентификаторы

например, для 2 шорт (предполагая 16bit), вы должны использовать 32bit int

int ID = ((int)short1 << 16) | short2;

и для 4 шорт вам понадобится 64-битный int и т. д...

в основном с чем-либо еще столкновения (несколько вещей могут получить тот же идентификатор) в значительной степени гарантированы.

однако другой подход (который я думаю, будет лучше), чтобы получить идентификаторы было бы раздавать их, как вставляются вершины:

unsigned LastId = 0;//global

unsigned GetNewId(){return ++LastId;}

это также позволяет добавлять больше / разных данных в каждую вершину. Однако, если вы ожидаете создать более 2^32 вершин без сброса, это, вероятно, не лучший метод.


экспромтом я бы сказал, использовать простые числа,

id = 3 * value1 + 5 * value2 + .... + somePrime * valueN

убедитесь, что вы не переполняете пространство id (долго? долго долго?). Поскольку у вас есть фиксированное количество значений просто хрень какая-то случайные числа. Не беспокойтесь о их создании, в списках достаточно доступных, чтобы вы могли некоторое время работать.

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