Поиск несуществующего ключа в std :: map - PullRequest
2 голосов
/ 27 ноября 2009

Есть ли способ найти несуществующий ключ на карте?

Я использую std::map<int,myclass> и хочу автоматически сгенерировать ключ для новых предметов. Элементы могут быть удалены с карты в другом порядке после их вставки.

Элементы myclass могут или не могут быть идентичными, поэтому они не могут служить ключом сами по себе.

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

Подойдет альтернативная структура данных с такими же функциями и производительностью.

Редактировать

Я пытаюсь создать контейнер для своих предметов - такой, чтобы я мог удалять / изменять предметы в соответствии с их ключами и перебирать предметы. Само значение ключа для меня ничего не значит, однако другие объекты будут хранить эти ключи для внутреннего использования.

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

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

Ответы [ 10 ]

5 голосов
/ 27 ноября 2009

Я бы предложил комбинацию счетчика и очереди. Когда вы удаляете элемент с карты, добавьте его ключ в очередь. Затем очередь отслеживает ключи, которые были удалены с карты, чтобы их можно было использовать снова. Чтобы получить новый ключ, вы сначала проверяете, пуста ли очередь. Если это не так, отключите верхний индекс и используйте его, в противном случае используйте счетчик, чтобы получить следующий доступный ключ.

3 голосов
/ 27 ноября 2009

Дайте мне посмотреть, понимаю ли я. Что вы хотите сделать, это

ищи ключ. Если нет, вставьте элемент.

Элементы могут быть удалены.

Держите счетчик (подождите, подождите) и вектор. Вектор будет хранить идентификаторы удаленных элементов. Когда вы собираетесь вставить новый элемент, ищите ключ в векторе. Если вектор не пустой, удалите ключ и используйте его. Если он пуст, возьмите его со счетчика (counter ++). Однако, если вы никогда не удаляете предметы с карты, вы просто застряли со счетчиком.

Альтернатива: Как насчет использования адреса памяти элемента в качестве ключа?

2 голосов
/ 27 ноября 2009

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

Если мы рассмотрим ситуацию с int, вы можете хранить std :: set смежных сегментов неиспользуемых ключей (поскольку эти сегменты не перекрываются, можно использовать естественное упорядочение - просто сравните их начальные точки). Когда нужен новый ключ, вы берете первый сегмент, обрезаете первый индекс и помещаете остальные в набор (если остальные не пусты). Когда какой-то ключ освобождается, вы обнаруживаете, есть ли в наборе соседние сегменты (в силу природы набора это возможно при сложности O (log n)), и выполняете объединение, если необходимо, в противном случае просто помещаете сегмент [n, n] в набор.

таким образом, вы определенно будете иметь тот же порядок сложности времени и порядок потребления памяти, что и карта независимо от истории запросов (поскольку количество сегментов не может быть больше, чем map.size () + 1)

как то так:

class TKeyManager
{
public:
    TKeyManager()
    {
        FreeKeys.insert(
          std::make_pair(
            std::numeric_limits<int>::min(),
            std::numeric_limits<int>::max());
    }
    int AlocateKey()
    {
        if(FreeKeys.empty())
            throw something bad;
        const std::pair<int,int> freeSegment=*FreeKeys.begin();
        if(freeSegment.second>freeSegment.first)
            FreeKeys.insert(std::make_pair(freeSegment.first+1,freeSegment.second));
        return freeSegment.first;
    }
    void ReleaseKey(int key)
    {
        std:set<std::pair<int,int>>::iterator position=FreeKeys.insert(std::make_pair(key,key)).first;
        if(position!=FreeKeys.begin())
        {//try to merge with left neighbour
            std::set<std::pair<int,int>>::iterator left=position;
            --left;
            if(left->second+1==key)
            {
                left->second=key;
                FreeKeys.erase(position);
                position=left;
            }
        }
        if(position!=--FreeKeys.end())
        {//try to merge with right neighbour
            std::set<std::pair<int,int>>::iterator right=position;
            ++right;
            if(right->first==key+1)
            {
                position->second=right->second;
                FreeKeys.erase(right);
            }
        }
    }
private:
    std::set<std::pair<int,int>> FreeKeys;
};
2 голосов
/ 27 ноября 2009

Есть ли способ найти несуществующее введите карту?

Я не уверен, что вы имеете в виду здесь. Как вы можете найти то, что не существует? Вы имеете в виду, есть ли способ определить, не содержит ли карта ключ?

Если вы это имеете в виду, вы просто используете функцию find, а если ключ не существует, он возвращает итератор, указывающий на end().

if (my_map.find(555) == my_map.end()) { /* do something */ }

Вы продолжаете говорить ...

Я использую std :: map и Я хочу автоматически сгенерировать ключ для новых предметов. Элементы могут быть удалены с карты в другом порядке от их вставка. Элементы myclass могут быть, а могут и не быть идентичными, поэтому они сами не могут служить ключом.

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

0 голосов
/ 01 декабря 2009

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

Количество элементов в текущем рабочем наборе будет действительно определять, является ли этот подход жизнеспособным или нет.

0 голосов
/ 27 ноября 2009

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

void string_increment(std::string& counter)
{
    bool carry=true;
    for (size_t i=0;i<counter.size();++i)
    {
        unsigned char original=static_cast<unsigned char>(counter[i]);
        if (carry)
        {
            ++counter[i];
        }
        if (original>static_cast<unsigned char>(counter[i]))
        {
            carry=true;
        }
        else
        {
            carry=false;
        }
    }
    if (carry)
    {
        counter.push_back(0);
    }
}

например. так что у вас есть:

std::string counter; // empty string
string_increment(counter); // now counter=="\x00"
string_increment(counter); // now counter=="\x01"
...
string_increment(counter); // now counter=="\xFF"
string_increment(counter); // now counter=="\x00\x00"
string_increment(counter); // now counter=="\x01\x00"
...
string_increment(counter); // now counter=="\xFF\x00"
string_increment(counter); // now counter=="\x00\x01"
string_increment(counter); // now counter=="\x01\x01"
...
string_increment(counter); // now counter=="\xFF\xFF"
string_increment(counter); // now counter=="\x00\x00\x00"
string_increment(counter); // now counter=="\x01\x00\x00"
// etc..
0 голосов
/ 27 ноября 2009

Как и у других, мне трудно понять, что именно вы хотите. Звучит так, будто вы хотите создать предмет, если он не найден. sdt :: map :: operator [] (const key_type & x) сделает это за вас.

std::map<int, myclass> Map;
myclass instance1, instance2;

Map[instance1] = 5;
Map[instance2] = 6;

Это то, о чем ты думаешь?

0 голосов
/ 27 ноября 2009

используйте uint64_t в качестве ключевого типа последовательности или даже если вы думаете, что этого будет недостаточно

struct sequence_key_t {
    uint64_t upper; 
    uint64_t lower; 
    operator++();
    bool operator<() 
};

как:

sequence_key_t global_counter;

std::map<sequence_key_t,myclass> my_map;

my_map.insert(std::make_pair(++global_counter, myclass()));

и у вас не возникнет никаких проблем.

0 голосов
/ 27 ноября 2009
  • Учтите, что вы решили, как генерировать ключи, не основанные на счетчиках, и обнаружили, что генерация их в массе гораздо эффективнее, чем генерация их один за другим.
  • Получив этот генератор "бесконечным" и "statefull" (это ваше требование), вы можете создать второй контейнер фиксированного размера с, скажем, 1000 неиспользованных ключей.
  • Предоставляет вам новые записи на карте с ключами из этого контейнера и возвращает ключи обратно для повторного использования.
  • Установите какой-то низкий «порог», чтобы реагировать на достижение ключевого контейнера низкого уровня, и пополнять ключи навалом, используя «бесконечный» генератор.

Фактически опубликованная проблема все еще существует "как сделать эффективный генератор на основе не счетчика". Возможно, вы захотите еще раз взглянуть на требование «бесконечности» и проверить, может ли 64-битный или 128-битный счетчик по-прежнему удовлетворять вашим алгоритмам в течение некоторого ограниченного периода времени, например 1000 лет.

0 голосов
/ 27 ноября 2009

Не могли бы вы уточнить, почему вы не можете использовать простой инкрементный счетчик в качестве автоматически генерируемого ключа? (приращение на вставке)? Кажется, что нет проблем с этим.

...