вставка std :: map или поиск std :: map? - PullRequest
84 голосов
/ 19 сентября 2008

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

Ответы [ 9 ]

137 голосов
/ 19 сентября 2008

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

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
}
11 голосов
/ 19 сентября 2008

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

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.second->second = v;
  }
}

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

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

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.second->second = v;
  }
}

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

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

8 голосов
/ 19 сентября 2008

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

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

5 голосов
/ 19 сентября 2008

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

3 голосов
/ 21 июля 2009

Я потерялся в топ-ответе.

Find возвращает map.end (), если ничего не находит, что означает, что если вы добавляете новые вещи, тогда

iter = map.find();
if (iter == map.end()) {
  map.insert(..) or map[key] = value
} else {
  // do nothing. You said you did not want to effect existing stuff.
}

в два раза медленнее, чем

map.insert

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

1 голос
/ 28 июля 2011

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

1 голос
/ 19 сентября 2008

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

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

0 голосов
/ 19 сентября 2008

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

0 голосов
/ 19 сентября 2008

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

Да, достаточно справедливо.

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

...