С++: нужен индексированный набор

Мне нужен индексированный ассоциативный контейнер, который работает следующим образом:

  • изначально пустой, размер=0.

  • когда я добавляю к нему новый элемент, он помещает его в индекс [размер], очень похоже на push_back вектора. Он увеличивает размер и возвращает индекс вновь добавленного элемента.

  • если элемент уже существует, он возвращает индекс, в котором он встречается.

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

Будет ли правильным в этой ситуации использовать разницу с set.begin()?


person user231536    schedule 01.04.2010    source источник
comment
Что делать, если вы удалите элемент из середины. Должны ли индексы элементов, стоящих за ним, измениться или нет?   -  person sbi    schedule 01.04.2010
comment
К чему клонит @sbi, я думаю: если индексы не должны измениться после удаления, std::map будет работать отлично.   -  person Thomas    schedule 01.04.2010
comment
@Thomas: я тоже так думал, пока не понял, что он хочет проверить, существуют ли уже значения. Это O(n) в std::map.   -  person sbi    schedule 01.04.2010
comment
@sbi: На какое-то время ты меня почти убедил. Но мы будем отображать значения в индексы: std::map<ElementType, int>.   -  person Thomas    schedule 01.04.2010
comment
@Thomas: Но тогда поиск по индексу будет O (n). Вероятно, он ищет мультииндексный контейнер.   -  person sbi    schedule 01.04.2010
comment
@sbi: Хм... ну... технически постер никогда не упоминал поиск по индексу... но вы, наверное, правы :)   -  person Thomas    schedule 01.04.2010
comment
@Thomas: Да, ты прав. Я как-то принял это как данность. Но пока он не отвечает на наши вопросы, все это висит в воздухе...   -  person sbi    schedule 02.04.2010


Ответы (1)


В STL нет подходящей для этого структуры данных, но одним из простых и довольно эффективных способов реализации этого было бы использование карты и вектора указателей. map сопоставляет объекты с их индексом в массиве (так что проверка существования объекта эффективна, и, если объект уже существует, индекс сразу же под рукой), а vector сопоставляет индексы с объектом в карте ( так что извлечение объектов по индексу эффективно).

std::map<T,size_t> objects;
std::vector<const T *> indexed;

Чтобы добавить элемент:

size_t add_element(const T &v) {
    std::map<T,size_t>::iterator it=objects.find(v);
    if(it==objects.end()) {
        it=objects.insert(std::map<T,size_t>::value_type(v,objects.size())).first;
        indexed.push_back(&it->first);
    }
    return it->second;
}

(Очевидные изменения в соответствии с личным стилем могут состоять в том, чтобы хранить вектор итераторов карты, использовать map::insert каждый раз и проверять логическую часть результата, чтобы увидеть, нуждается ли indexed в обновлении, и т. д.)

И чтобы получить элемент:

const T &get_element(size_t index) {
    return *indexed[index];
}

И это все. Одной из проблем является, конечно, то, что когда объект находится в наборе, его нельзя изменить. Это своего рода утечка из того, как это было реализовано здесь, потому что ключи карты по очевидным причинам являются константами, но на самом деле это то, что нужно в любом случае, я думаю, независимо от реализации. Если вы настаиваете на том, чтобы не было дубликатов, то, как только объект находится в списке, он не должен быть модифицируемым, иначе любые модификации сделают его дубликатом другого объекта.

(Также обратите внимание, что я немного схитрил, используя здесь size_t — я полагаю, что std::vector<const T *>::size_type может быть более точным — это в основном в интересах краткости!)

person Community    schedule 01.04.2010