С++: вставить в std::map, не зная ключа

Мне нужно вставить значения в std::map (или его эквивалент) в любую свободную позицию, а затем получить его ключ (чтобы удалить/изменить позже). Что-то вроде:

std::map<int, std::string> myMap;
const int key = myMap.insert("hello");

Возможно ли сделать это с помощью std::map или для этого есть подходящий контейнер?

Спасибо.


person Slaus    schedule 22.09.2011    source источник
comment
Я не думаю, что std::map - правильный выбор для того, чего вы хотите достичь. Что ты пытаешься сделать? Бессмысленно добавлять объект без связанного с ним ключа в словарь.   -  person Simone    schedule 22.09.2011
comment
Это работает не так... map, обычно реализуемый как дерево RB, не имеет ключа по умолчанию, связанного с каким-либо свободным слотом. Чего именно вы пытаетесь достичь?   -  person Matteo Italia    schedule 22.09.2011
comment
Возможно, вы ищете хеш-функцию - поищите информацию по этой теме.   -  person Alex F    schedule 22.09.2011
comment
@Matteo Italia, мне нужен способ часто вставлять/обновлять/удалять объекты в любом массиве. Я думаю, что реализация std::map (упорядоченное дерево?) дает возможность найти незанятый ключ - не так ли?   -  person Slaus    schedule 22.09.2011
comment
@Слав: нет, это не так. на карте нет незанятых ключей. Когда карта не имеет данных для определенного ключа, она также не имеет данных для самого ключа. Когда у вас есть пустой map<uint32_t>, у вас есть более 4 миллиардов незанятых ключей...   -  person PlasmaHH    schedule 22.09.2011
comment
Итак, что нужно использовать для массива объектов, который позволяет быстро удалять объекты вместе с не более медленной вставкой/обновлением, чем std::map?   -  person Slaus    schedule 22.09.2011


Ответы (5)


В дополнение к использованию set вы можете сохранить список выделенных (или свободных) ключей и найти новый ключ перед вставкой. Для map, индексированного int, вы можете просто взять последний элемент и увеличить его ключ. Но я бы предпочел простой std::vector; если удаление не поддерживается, вы можете сделать что-то простое, например:

int key = myVector.size();
myVector.push_back( newEntry );

Если вам нужно поддерживать удаление, то использование вектора типа «может быть» (boost::optional и т. д. у вас, вероятно, уже есть в вашем наборе инструментов, может быть, под именем Fallible или Maybe) может быть уместным. В зависимости от шаблонов использования (количество удалений по сравнению с общим количеством записей и т. д.) вы можете выполнить поиск вектора, чтобы повторно использовать записи. Если вы действительно амбициозны, вы можете сохранить растровое изображение свободных записей, устанавливая бит каждый раз, когда вы удаляете и вводите, и сбрасывая его всякий раз, когда вы повторно используете пространство.

person James Kanze    schedule 22.09.2011
comment
вы можете искать вектор, чтобы повторно использовать записи - или сохранить стек бесплатных индексов. Нажмите на стек, когда вы удаляете запись, и при добавлении новой записи либо стек пуст (в этом случае push_back вектор, как указано выше), либо используйте индекс, извлеченный из стека. - person Steve Jessop; 22.09.2011
comment
Хранение стека неиспользуемых записей — очень хорошая идея. Он очень прост в реализации и эффективен с точки зрения времени выполнения. - person James Kanze; 22.09.2011
comment
И возможное преимущество в производительности, которое, возможно, неочевидно, заключается в том, что использование старых записей в порядке LIFO помогает поддерживать кэш в горячем состоянии. - person Steve Jessop; 22.09.2011
comment
@ Стив Джессоп Может быть. Если вы найдете диапазон и распределите в нем, недавно выделенные значения будут иметь хорошую локализацию. И, конечно же, использование растрового изображения может привести к значительно меньшему использованию памяти (что также способствует локальности), если есть много пустых слотов. (Определение того, что лучше для местности заранее, часто является настоящей игрой в угадайку.) - person James Kanze; 22.09.2011

Вы можете добавить объект в std::set, а затем поместить весь набор в карту. Но нет, вы не можете поместить значение в карту без ключа.

person Codie CodeMonkey    schedule 22.09.2011

Самое близкое к тому, что вы пытаетесь сделать, вероятно,

myMap[myMap.size()] = "some string";

Единственное преимущество перед std::set заключается в том, что вы можете передавать целочисленные индексы другим модулям без необходимости знать тип std::set<Foo>::iterator или аналогичный.

person spraff    schedule 22.09.2011
comment
Это хитрый трюк. Единственная проблема, если есть удаления; вы можете получить уже использованный индекс. Что-то вроде prec(myMap.end())->first (при условии, что карта не пуста) решит эту проблему (и, конечно, если приложение не требует удаления, ваш трюк работает из коробки). - person James Kanze; 22.09.2011

Это невозможно. Такая операция потребует сложных знаний о типе ключа, чтобы знать, какие ключи доступны. Например, std::map должен был бы увеличивать значения int для карт int или добавлять к строкам для карт строк.

Вы можете использовать std::set и полностью отказаться от ключей.

person thiton    schedule 22.09.2011
comment
В целом вы правы, но в его случае тип ключа int, так что мы знаем, как это сделать. - person James Kanze; 22.09.2011
comment
Да, но это если вам нужно хранить только небольшие целые числа или в любом случае не нужен тип ключа. Предположим, вы получили несколько идентификаторов с большими целыми числами из внешнего источника и хотите добавить свой собственный (типичный вариант использования карты), тогда простой векторный подход не сработает. Мне нравится ваше решение, но он недооценил свою проблему :-) - person thiton; 22.09.2011
comment
Если некоторые из идентификаторов наложены (и могут быть очень большими), как вы говорите, подход std::vector не будет работать. В этом случае вы можете просто использовать индекс последнего элемента + 1 для нового элемента. Или вы можете выполнить итерацию, чтобы найти дыру (возможно, кэшируя результаты итерации в виде диапазона, чтобы вам не приходилось повторять каждый раз). Или, может быть, что-то еще --- как вы говорите, проблема серьезно занижена. - person James Kanze; 22.09.2011

Если вы хотите добиться чего-то похожего на автоматически генерируемые первичные ключи в базах данных SQL, вы можете поддерживать счетчик и использовать его для создания уникального ключа. Но, возможно, std::set — это то, что вам действительно нужно.

person piokuc    schedule 22.09.2011