std :: map insert або std :: map find?


90

Припустимо карту, де ви хочете зберегти існуючі записи. 20% випадків запис, який ви вставляєте, - це нові дані. Чи є перевага робити std :: map :: find, а потім std :: map :: insert, використовуючи цей повернутий ітератор? Або швидше зробити спробу вставки, а потім діяти, виходячи з того, вказує ітератор запис чи не був?


4
Я був виправлений і мав намір використовувати std :: map :: lower_bound замість std :: map :: find.
Суперблок

Відповіді:


147

Відповідь - ви не робите ні того, ні іншого. Натомість ви хочете зробити щось, запропоноване пунктом 24 Ефективного 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
}

2
Дійсно так працює find, фокус у тому, що він поєднує пошук, необхідний для пошуку та вставки. Звичайно, як і використання вставки, а потім перегляд другого поверненого значення.
puetzk

1
Два питання: 1) Чим відрізняється використання lower_bound від використання пошуку для карти? 2) Що стосується "карти", чи не так це, що права опція && завжди відповідає дійсності, коли 'lb! = Mymap.end ()'?
Річард Корден,

11
@Richard: find () повертає end (), якщо ключ не існує, lower_bound повертає позицію, де повинен бути елемент (що, в свою чергу, може бути використано як підказка для вставки). @puetzek: Чи не просто "вставити" замінить значення референта для існуючих ключів? Не впевнений, чи бажає це OP.
peterchen

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.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;
  }
}

Для того, щоб зателефонувати "вставити", ми платимо за дорогий дзвінок, щоб побудувати наш тип значення - і з того, що ви сказали у питанні, ви не будете використовувати це нове значення 20% часу. У наведеному вище випадку, якщо зміна типу значення карти не є можливістю, то ефективніше спочатку виконати "пошук", щоб перевірити, чи потрібно нам створювати елемент.

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


8

Між двома значеннями швидкості майже не буде різниці, find поверне ітератор, вставка зробить те саме і все одно здійснить пошук на карті, щоб визначити, чи вже існує запис.

Отже .. це залежить від особистих переваг. Я завжди намагаюся вставити, а потім оновити, якщо це необхідно, але деяким людям не подобається обробляти пару, яка повертається.


5

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


3

Я загубився на верхній відповіді.

Знайти повертає 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
Одна версія вставки STL повертає пару, що містить ітератор і bool. Бул вказує, знайшов він це чи ні, ітератором є або знайдений запис, або вставлений запис. Це важко перевершити за ефективністю; неможливо, я б сказав.
Zan Lynx

4
Ні, використана перевірена відповідь lower_bound, ні find. Як результат, якщо ключа не було знайдено, він повернув ітератор до точки вставки, а не до кінця. Як результат, це швидше.
Стівен Судіт,

1

Якщо ви стурбовані ефективністю, ви можете перевірити hash_map <> .

Зазвичай map <> реалізується як двійкове дерево. Залежно від ваших потреб, hash_map може бути більш ефективним.


Я б дуже хотів. Але в стандартній бібліотеці C ++ немає hash_map, і PHB не дозволяють використовувати код поза цим.
Суперблок

1
std :: tr1 :: unordered_map - це хеш-карта, яку пропонується додати до наступного стандарту, і вона повинна бути доступна в більшості поточних реалізацій STL.
beldaz

1

Здається, у мене недостатньо балів, щоб залишити коментар, але поставлена ​​галочка відповіді здається мені довгою - коли ви вважаєте, що вставка все одно повертає ітератор, навіщо йти шукати lower_bound, коли ви можете просто використовувати повернутий ітератор. Дивно.


1
Оскільки (звичайно до C ++ 11) за допомогою вставки означає, що вам все одно потрібно створити std::map::value_typeоб’єкт, прийнята відповідь уникає навіть цього.
KillianDS

-1

Будь-які відповіді щодо ефективності залежатимуть від точної реалізації вашого STL. Єдиний спосіб точно знати - це порівняти його в обох напрямках. Я припускаю, що різниця навряд чи буде суттєвою, тому приймайте рішення, виходячи зі стилю, який ви віддаєте перевагу.


1
Це не зовсім так. STL на відміну від більшості інших бібліотек тим, що він забезпечує чіткі вимоги до великого O для більшості своїх операцій. Існує гарантована різниця між 2 * O (log n) та 1 * O (log n), незалежно від того, яку реалізацію використовують функції для досягнення цієї поведінки O (log n). Чи є ця різниця суттєвою на вашій платформі - це інше питання. Але різниця буде завжди.
srm

@srm, що визначає вимоги до великих O, все ще не говорить вам, скільки часу триватиме операція в абсолютному вираженні. Гарантована різниця, про яку ви говорите, не існує.
Марк Ренсом

-2

map [ключ] - нехай stl це розбере. Це найбільш ефективно передає ваш намір.

Так, справедливо.

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


Ні, якщо запис існує, він повертає посилання на існуючий запис.
Kris Kumler

2
-1 за цю відповідь. Як сказав Кріс К, використання map [key] = value перезапише існуючий запис, а не "збереже" його, як вимагається у питанні. Ви не можете перевірити існування за допомогою map [key], оскільки він поверне побудований за замовчуванням об’єкт, якщо ключ не існує, і створить це як запис для ключа
netjeff

Суть полягає в тому, щоб перевірити, чи карта вже заповнена, і лише додавати / перезаписувати, якщо її там немає. Використання map [key] передбачає, що значення вже є завжди.
srm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.