std :: map insert или std :: map find?

93

Предполагая карту, на которой вы хотите сохранить существующие записи. В 20% случаев вводимая вами запись - это новые данные. Есть ли преимущество в выполнении std :: map :: find, а затем std :: map :: insert с использованием этого возвращенного итератора? Или быстрее попытаться вставить, а затем действовать в зависимости от того, указывает ли итератор, что запись была вставлена ​​или нет?

Суперполок
источник
4
Меня поправили и я намеревался использовать std :: map :: lower_bound вместо std :: map :: find.
Superpolock,

Ответы:

148

Ответ: вы тоже. Вместо этого вы хотите сделать что - то предложенное пунктом 24 эффективного СТЛ по Скотт Мейерс :

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}
Люк
источник
2
Это действительно так, как работает find, хитрость в том, что он объединяет поиск, необходимый для поиска и вставки. Конечно, то же самое происходит при простом использовании insert и последующем просмотре второго возвращаемого значения.
puetzk
1
Два вопроса: 1) Чем отличается использование lower_bound от использования find для карты? 2) Для 'карты', не правда ли, что операция правой руки && всегда истинна, когда 'lb! = Mymap.end ()'?
Ричард Корден,
12
@Richard: find () возвращает end (), если ключ не существует, lower_bound возвращает позицию, в которой должен находиться элемент (что, в свою очередь, может использоваться как подсказка для вставки). @puetzek: Разве "просто вставка" не перезапишет референтное значение для существующих ключей? Не уверен, что ОП этого хочет.
peterchen 06
2
кто-нибудь знает, есть ли что-то подобное для unordered_map?
Джованни Фуншал,
3
@peterchen map :: insert не перезаписывает существующее значение, если оно существует, см. cplusplus.com/reference/map/map/insert .
Крис Дрю,
11

Ответ на этот вопрос также зависит от того, насколько дорого стоит создать тип значения, который вы храните на карте:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

Для типа значения, такого как int, приведенное выше будет более эффективным, чем поиск с последующей вставкой (при отсутствии оптимизации компилятора). Как указано выше, это связано с тем, что поиск по карте выполняется только один раз.

Однако для вызова insert требуется, чтобы у вас уже было сконструировано новое «значение»:

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

Чтобы вызвать 'insert', мы платим за дорогостоящий вызов для создания нашего типа значения - и, судя по тому, что вы сказали в вопросе, вы не будете использовать это новое значение в 20% случаев. В приведенном выше случае, если изменение типа значения карты не является вариантом, более эффективно сначала выполнить «поиск», чтобы проверить, нужно ли нам создавать элемент.

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

Ричард Корден
источник
8

Между ними не будет почти никакой разницы в скорости, find вернет итератор, insert сделает то же самое и все равно будет искать на карте, чтобы определить, существует ли уже запись.

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

gbjbaanb
источник
5

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

PiNoYBoY82
источник
1

Если вас беспокоит эффективность, вы можете проверить hash_map <> .

Обычно map <> реализуется как двоичное дерево. В зависимости от ваших потребностей hash_map может быть более эффективным.

Адам Теген
источник
Хотел бы. Но в стандартной библиотеке C ++ нет hash_map, и PHB не допускают кода за пределами этого.
Superpolock
1
std :: tr1 :: unordered_map - это хеш-карта, которую предлагается добавить в следующий стандарт, и она должна быть доступна в большинстве текущих реализаций STL.
beldaz
1

Кажется, у меня недостаточно очков, чтобы оставить комментарий, но помеченный ответ мне кажется длинным - если учесть, что insert все равно возвращает итератор, зачем искать lower_bound, когда вы можете просто использовать возвращенный итератор. Странный.

Стонки
источник
1
Поскольку (конечно, до C ++ 11) использование insert означает, что вам все равно нужно создать std::map::value_typeобъект, принятый ответ позволяет избежать даже этого.
KillianDS
-1

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

Марк Рэнсом
источник
1
Это не совсем так. STL отличается от большинства других библиотек тем, что предоставляет явные требования большого O для большинства своих операций. Существует гарантированная разница между 2 * O (log n) и 1 * O (log n), независимо от того, какую реализацию функции используют для достижения этого поведения O (log n). Другой вопрос, является ли эта разница значительной для вашей платформы. Но разница будет всегда.
srm
@srm, определяющий требования big-O, по-прежнему не говорит вам, сколько времени займет операция в абсолютном выражении. Гарантированной разницы, о которой вы говорите, не существует.
Марк Рэнсом
-2

map [key] - пусть stl разберется. Это наиболее эффективная передача вашего намерения.

Да, достаточно честно.

Если вы выполняете поиск, а затем вставку, вы выполняете 2 x O (log N), когда вы получаете промах, поскольку поиск только сообщает вам, нужно ли вам вставлять, а не туда, куда вставка должна идти (lower_bound может помочь вам там) . Просто прямая вставка, а затем изучение результата - вот путь, которым я бы пошел.


источник
Нет, если запись существует, она возвращает ссылку на существующую запись.
Крис Кумлер,
2
-1 за этот ответ. Как сказал Крис К., использование map [key] = value перезапишет существующую запись, а не «сохранит» ее, как требуется в вопросе. Вы не можете проверить существование с помощью map [key], потому что он вернет созданный по умолчанию объект, если ключ не существует, и создаст его как запись для ключа
netjeff
Дело в том, чтобы проверить, заполнена ли карта, и добавить / перезаписать только в том случае, если ее там нет. Использование map [key] предполагает, что значение уже существует всегда.
srm