Как обновить существующий элемент std:: set?
У меня есть std::set<Foo>
, и я хотел бы обновить некоторое значение
существующий элемент в нем. Обратите внимание, что значение, которое я обновляю, не меняет порядок в наборе:
#include <iostream>
#include <set>
#include <utility>
struct Foo {
Foo(int i, int j) : id(i), val(j) {}
int id;
int val;
bool operator<(const Foo& other) const {
return id < other.id;
}
};
typedef std::set<Foo> Set;
void update(Set& s, Foo f) {
std::pair<Set::iterator, bool> p = s.insert(f);
bool alreadyThere = p.second;
if (alreadyThere)
p.first->val += f.val; // error: assignment of data-member
// ‘Foo::val’ in read-only structure
}
int main(int argc, char** argv){
Set s;
update(s, Foo(1, 10));
update(s, Foo(1, 5));
// Now there should be one Foo object with val==15 in the set.
return 0;
}
Есть ли какой-нибудь краткий способ сделать это? Или мне нужно проверить, есть ли элемент уже там, и если да, удалите его, добавьте значение и снова вставьте?
Ответы
Ответ 1
Так как val
не участвует в сравнении, его можно объявить mutable
struct Foo {
Foo(int i, int j) : id(i), val(j) {}
int id;
mutable int val;
bool operator<(const Foo& other) const {
return id < other.id;
}
};
Это означает, что значение val
может меняться в логически-const Foo, что означает, что оно не должно влиять на другие операторы сравнения и т.д.
Или вы можете просто удалить и вставить, что занимает O (1) дополнительное время (по сравнению с доступом и изменением), если вставка использует позицию непосредственно перед сразу после старого в качестве подсказки.
Что-то вроде:
bool alreadyThere = !p.second; // you forgot the !
if (alreadyThere)
{
Set::iterator hint = p.first;
hint++;
s.erase(p.first);
s.insert(hint, f);
}
Ответ 2
Не пытайтесь решить эту проблему, работая над константой элементов в set
. Вместо этого, почему бы не использовать map
, который уже выражает отношение ключевого значения, которое вы моделируете, и предоставляет простые способы обновления существующих элементов.
Ответ 3
Сделать val
изменчивым как:
mutable int val;
Теперь вы можете изменять/изменять/мутировать val
, даже если foo
is const:
void f(const Foo & foo)
{
foo.val = 10; //ok
foo.id = 11; //compilation error - id is not mutable.
}
Кстати, из вашего кода вам кажется, что если p.second
истинно, то значение уже существует в наборе, и поэтому вы обновляете связанное значение. Думаю, вы ошибаетесь. На самом деле это наоборот. doc в cpluscplus говорит,
Пара:: второй элемент в паре имеет значение true, если новый элемент был вставлен или false, если существовал элемент с тем же значением.
что является правильным, на мой взгляд.
Однако, если вы используете std::map
, ваше решение будет простым:
void update(std::map<int,int> & m, std::pair<int,int> value)
{
m[value.first] += value.second;
}
Что делает этот код? m[value.first]
создает новую запись, если ключ не существует на карте, а значением новой записи является значение по умолчанию int
, которое равно нулю. Поэтому он добавляет value.second
в zero
. Или, если ключ существует, он просто добавляет к нему value.second
. То есть приведенный выше код эквивалентен этому:
void update(std::map<int,int> & m, std::pair<int,int> value)
{
std::map<int,int>::iterator it = m.find(value);
if ( it != m.end()) //found or not?
it.second += value; //add if found
else
{
m.insert(value); //insert if not found
}
}
Но это слишком много, не так ли? Это не очень хорошо. Раньше более кратким и очень результативным.
Ответ 4
вы можете использовать MAP, который имеет очень быстрый доступ к вашему элементу, если у вас есть KEY. в этом случае я думаю, что использование MAP было бы лучшим способом достижения максимальной скорости. STD:: MAP