C++
станд :: Карта
Поиск…
замечания
Чтобы использовать любой из
std::map
илиstd::multimap
должен быть включен заголовочный файл<map>
.std::map
иstd::multimap
обе сохраняют свои элементы отсортированными в соответствии с возрастающим порядком ключей. В случаеstd::multimap
сортировка не производится для значений одного и того же ключа.Основное различие между
std::map
иstd::multimap
заключается в том, чтоstd::map
не позволяет дублировать значения для того же ключа, гдеstd::multimap
.Карты реализованы как двоичные деревья поиска. Таким образом,
search()
,insert()
,erase()
занимает среднее время (log n). Для работы с постоянным временем используйтеstd::unordered_map
.Функции
size()
иempty()
имеют временную сложность Θ (1), количество узлов кэшируется, чтобы избежать ходьбы по дереву каждый раз, когда вызывается эти функции.
Доступ к элементам
std::map
принимает (key, value)
пары в качестве входных данных.
Рассмотрим следующий пример инициализации std::map
:
std::map < std::string, int > ranking { std::make_pair("stackoverflow", 2),
std::make_pair("docs-beta", 1) };
На std::map
элементы можно вставить следующим образом:
ranking["stackoverflow"]=2;
ranking["docs-beta"]=1;
В приведенном выше примере, если ключевой stackoverflow
уже присутствует, его значение будет обновлено до 2. Если он еще не присутствует, будет создана новая запись.
На std::map
элементы можно получить напрямую, указав ключ как индекс:
std::cout << ranking[ "stackoverflow" ] << std::endl;
Обратите внимание, что использование operator[]
на карте фактически введет новое значение с запрошенным ключом в карту. Это означает, что вы не можете использовать его на const std::map
, даже если ключ уже сохранен на карте. Чтобы предотвратить эту установку, проверьте, существует ли элемент (например, с помощью find()
) или используйте его at()
как описано ниже.
К элементам std::map
можно обращаться с помощью at()
:
std::cout << ranking.at("stackoverflow") << std::endl;
Обратите внимание, что at()
будет std::out_of_range
исключение std::out_of_range
если контейнер не содержит запрошенный элемент.
В обоих контейнерах std::map
и std::multimap
, элементы могут быть доступны с помощью итераторов:
// Example using begin()
std::multimap < int, std::string > mmp { std::make_pair(2, "stackoverflow"),
std::make_pair(1, "docs-beta"),
std::make_pair(2, "stackexchange") };
auto it = mmp.begin();
std::cout << it->first << " : " << it->second << std::endl; // Output: "1 : docs-beta"
it++;
std::cout << it->first << " : " << it->second << std::endl; // Output: "2 : stackoverflow"
it++;
std::cout << it->first << " : " << it->second << std::endl; // Output: "2 : stackexchange"
// Example using rbegin()
std::map < int, std::string > mp { std::make_pair(2, "stackoverflow"),
std::make_pair(1, "docs-beta"),
std::make_pair(2, "stackexchange") };
auto it2 = mp.rbegin();
std::cout << it2->first << " : " << it2->second << std::endl; // Output: "2 : stackoverflow"
it2++;
std::cout << it2->first << " : " << it2->second << std::endl; // Output: "1 : docs-beta"
Инициализация std :: map или std :: multimap
std::map
и std::multimap
оба могут быть инициализированы путем предоставления пар ключ-значение, разделенных запятой. Ключи-значения могут быть предоставлены либо {key, value}
либо могут быть явно созданы с помощью std::make_pair(key, value)
. Поскольку std::map
не позволяет дублировать ключи, а оператор запятой выполняет справа налево, пара справа будет перезаписана парой с одним и тем же ключом слева.
std::multimap < int, std::string > mmp { std::make_pair(2, "stackoverflow"),
std::make_pair(1, "docs-beta"),
std::make_pair(2, "stackexchange") };
// 1 docs-beta
// 2 stackoverflow
// 2 stackexchange
std::map < int, std::string > mp { std::make_pair(2, "stackoverflow"),
std::make_pair(1, "docs-beta"),
std::make_pair(2, "stackexchange") };
// 1 docs-beta
// 2 stackoverflow
Оба могут быть инициализированы итератором.
// From std::map or std::multimap iterator
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {6, 8}, {3, 4},
{6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 8}, {6, 7}, {8, 9}
auto it = mmp.begin();
std::advance(it,3); //moved cursor on first {6, 5}
std::map< int, int > mp(it, mmp.end()); // {6, 5}, {8, 9}
//From std::pair array
std::pair< int, int > arr[10];
arr[0] = {1, 3};
arr[1] = {1, 5};
arr[2] = {2, 5};
arr[3] = {0, 1};
std::map< int, int > mp(arr,arr+4); //{0 , 1}, {1, 3}, {2, 5}
//From std::vector of std::pair
std::vector< std::pair<int, int> > v{ {1, 5}, {5, 1}, {3, 6}, {3, 2} };
std::multimap< int, int > mp(v.begin(), v.end());
// {1, 5}, {3, 6}, {3, 2}, {5, 1}
Удаление элементов
Удаление всех элементов:
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
mmp.clear(); //empty multimap
Удаление элемента из где-то с помощью итератора:
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 7}, {8, 9}
auto it = mmp.begin();
std::advance(it,3); // moved cursor on first {6, 5}
mmp.erase(it); // {1, 2}, {3, 4}, {3, 4}, {6, 7}, {8, 9}
Удаление всех элементов в диапазоне:
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 7}, {8, 9}
auto it = mmp.begin();
auto it2 = it;
it++; //moved first cursor on first {3, 4}
std::advance(it2,3); //moved second cursor on first {6, 5}
mmp.erase(it,it2); // {1, 2}, {6, 5}, {6, 7}, {8, 9}
Удаление всех элементов, имеющих заданное значение в качестве ключа:
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
// {1, 2}, {3, 4}, {3, 4}, {6, 5}, {6, 7}, {8, 9}
mmp.erase(6); // {1, 2}, {3, 4}, {3, 4}, {8, 9}
Удаление элементов, которые удовлетворяют предикату pred
:
std::map<int,int> m;
auto it = m.begin();
while (it != m.end())
{
if (pred(*it))
it = m.erase(it);
else
++it;
}
Вставка элементов
Элемент может быть вставлен в std::map
только если его ключ еще не присутствует на карте. Дано, например:
std::map< std::string, size_t > fruits_count;
Пара ключ-значение вставляется в
std::map
через функцию-членinsert()
. Для этого требуетсяpair
в качестве аргумента:fruits_count.insert({"grapes", 20}); fruits_count.insert(make_pair("orange", 30)); fruits_count.insert(pair<std::string, size_t>("banana", 40)); fruits_count.insert(map<std::string, size_t>::value_type("cherry", 50));
Функция
insert()
возвращаетpair
состоящую из итератора и значенияbool
:- Если вставка прошла успешно, итератор указывает на вновь вставленный элемент, а значение
bool
равноtrue
. - Если уже был элемент с одним и тем же
key
, вставка не выполняется. Когда это происходит, итератор указывает на элемент, вызывающий конфликт, а значениеbool
равноfalse
.
Для комбинирования операций вставки и поиска можно использовать следующий метод:
auto success = fruits_count.insert({"grapes", 20}); if (!success.second) { // we already have 'grapes' in the map success.first->second += 20; // access the iterator to update the value }
- Если вставка прошла успешно, итератор указывает на вновь вставленный элемент, а значение
Для удобства контейнер
std::map
предоставляет оператору индексирования доступ к элементам на карте и вставлять новые, если они не существуют:fruits_count["apple"] = 10;
Хотя это проще, он не позволяет пользователю проверить, существует ли элемент уже. Если элемент отсутствует,
std::map::operator[]
неявно создает его, инициализируя его конструктором по умолчанию, прежде чем перезаписывать его с указанным значением.
insert()
может быть использована для добавления нескольких элементов одновременно с использованием скопированного списка пар. Эта версия insert () возвращает void:fruits_count.insert({{"apricot", 1}, {"jackfruit", 1}, {"lime", 1}, {"mango", 7}});
insert()
также может использоваться для добавления элементов с помощью итераторов, обозначающих значения начала и концаvalue_type
:std::map< std::string, size_t > fruit_list{ {"lemon", 0}, {"olive", 0}, {"plum", 0}}; fruits_count.insert(fruit_list.begin(), fruit_list.end());
Пример:
std::map<std::string, size_t> fruits_count;
std::string fruit;
while(std::cin >> fruit){
// insert an element with 'fruit' as key and '1' as value
// (if the key is already stored in fruits_count, insert does nothing)
auto ret = fruits_count.insert({fruit, 1});
if(!ret.second){ // 'fruit' is already in the map
++ret.first->second; // increment the counter
}
}
Сложность времени для операции вставки - O (log n), поскольку std::map
реализованы как деревья.
pair
может быть построена явно с использованием make_pair()
и emplace()
:
std::map< std::string , int > runs;
runs.emplace("Babe Ruth", 714);
runs.insert(make_pair("Barry Bonds", 762));
Если мы знаем, где будет вставлен новый элемент, мы можем использовать emplace_hint()
чтобы указать hint
итератора. Если новый элемент можно вставить непосредственно перед hint
, то вставка может быть выполнена в постоянное время. В противном случае он ведет себя так же, как emplace()
:
std::map< std::string , int > runs;
auto it = runs.emplace("Barry Bonds", 762); // get iterator to the inserted element
// the next element will be before "Barry Bonds", so it is inserted before 'it'
runs.emplace_hint(it, "Babe Ruth", 714);
Итерация по std :: map или std :: multimap
std::map
или std::multimap
может быть пройден следующими способами:
std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} };
//Range based loop - since C++11
for(const auto &x: mmp)
std::cout<< x.first <<":"<< x.second << std::endl;
//Forward iterator for loop: it would loop through first element to last element
//it will be a std::map< int, int >::iterator
for (auto it = mmp.begin(); it != mmp.end(); ++it)
std::cout<< it->first <<":"<< it->second << std::endl; //Do something with iterator
//Backward iterator for loop: it would loop through last element to first element
//it will be a std::map< int, int >::reverse_iterator
for (auto it = mmp.rbegin(); it != mmp.rend(); ++it)
std::cout<< it->first <<" "<< it->second << std::endl; //Do something with iterator
При повторении по std::map
или std::multimap
использование auto
предпочтительнее, чтобы избежать бесполезных неявных преобразований (подробнее см. Этот ответ SO ).
Поиск в std :: map или в std :: multimap
Существует несколько способов поиска ключа в std::map
или в std::multimap
.
Чтобы получить итератор первого вхождения ключа, можно использовать функцию
find()
. Он возвращаетend()
если ключ не существует.std::multimap< int , int > mmp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} }; auto it = mmp.find(6); if(it!=mmp.end()) std::cout << it->first << ", " << it->second << std::endl; //prints: 6, 5 else std::cout << "Value does not exist!" << std::endl; it = mmp.find(66); if(it!=mmp.end()) std::cout << it->first << ", " << it->second << std::endl; else std::cout << "Value does not exist!" << std::endl; // This line would be executed.
Другой способ определить, существует ли запись в
std::map
или вstd::multimap
, используется функцияcount()
, которая подсчитывает, сколько значений связано с данным ключом. Посколькуstd::map
связывает только одно значение с каждым ключом, функцияcount()
может возвращать только 0 (если ключ отсутствует) или 1 (если есть). Дляstd::multimap
count()
может возвращать значения, превышающие 1, поскольку может быть несколько значений, связанных с одним и тем же ключом.std::map< int , int > mp{ {1, 2}, {3, 4}, {6, 5}, {8, 9}, {3, 4}, {6, 7} }; if(mp.count(3) > 0) // 3 exists as a key in map std::cout << "The key exists!" << std::endl; // This line would be executed. else std::cout << "The key does not exist!" << std::endl;
Если вам все равно, существует ли какой-то элемент,
find
будет строго лучше: он документирует ваше намерение, и дляmultimaps
он может остановиться после обнаружения первого совпадающего элемента.В случае
std::multimap
может быть несколько элементов, имеющих один и тот же ключ. Чтобы получить этот диапазон, используетсяequal_range()
которая возвращаетstd::pair
имеющую нижнюю границу итератора (включительно) и верхнюю границу (исключая) соответственно. Если ключ не существует, оба итератора будут указывать наend()
.auto eqr = mmp.equal_range(6); auto st = eqr.first, en = eqr.second; for(auto it = st; it != en; ++it){ std::cout << it->first << ", " << it->second << std::endl; } // prints: 6, 5 // 6, 7
Проверка количества элементов
Контейнер std::map
имеет функцию-член empty()
, которая возвращает true
или false
, в зависимости от того, пуста или нет карта. Элемент функции size()
возвращает число элементов, хранящихся в std::map
:
std::map<std::string , int> rank {{"facebook.com", 1} ,{"google.com", 2}, {"youtube.com", 3}};
if(!rank.empty()){
std::cout << "Number of elements in the rank map: " << rank.size() << std::endl;
}
else{
std::cout << "The rank map is empty" << std::endl;
}
Типы карт
Регулярная карта
Карта представляет собой ассоциативный контейнер, содержащий пары ключ-значение.
#include <string>
#include <map>
std::map<std::string, size_t> fruits_count;
В приведенном выше примере std::string
- это тип ключа , а size_t
- значение .
Ключ действует как индекс на карте. Каждый ключ должен быть уникальным и должен быть заказан.
Если вам нужны mutliple элементы с одним и тем же ключом, рассмотрите возможность использования
multimap
(объяснено ниже)Если ваш тип значения не указывает какой-либо заказ или вы хотите переопределить порядок по умолчанию, вы можете указать его:
#include <string> #include <map> #include <cstring> struct StrLess { bool operator()(const std::string& a, const std::string& b) { return strncmp(a.c_str(), b.c_str(), 8)<0; //compare only up to 8 first characters } } std::map<std::string, size_t, StrLess> fruits_count2;
Если компаратор
StrLess
возвращаетfalse
для двух ключей, они считаются одинаковыми, даже если их фактическое содержимое отличается.
Multi-Map
Multimap позволяет хранить несколько пар ключ-значение с одним и тем же ключом на карте. В противном случае его интерфейс и создание очень похожи на обычную карту.
#include <string>
#include <map>
std::multimap<std::string, size_t> fruits_count;
std::multimap<std::string, size_t, StrLess> fruits_count2;
Хэш-карта (неупорядоченная карта)
В хэш-карте хранятся пары ключ-значение, аналогичные обычной карте. Однако он не упорядочивает элементы по отношению к ключу. Вместо этого хэш- значение для ключа используется для быстрого доступа к необходимым парам ключ-значение.
#include <string>
#include <unordered_map>
std::unordered_map<std::string, size_t> fruits_count;
Неупорядоченные карты обычно быстрее, но элементы не сохраняются в каком-либо предсказуемом порядке. Например, итерация по всем элементам в unordered_map
дает элементы в кажущемся случайном порядке.
Создание std :: map с определяемыми пользователем типами как ключ
Чтобы иметь возможность использовать класс в качестве ключа на карте, все, что требуется от ключа, состоит в том, что оно может быть copiable
и assignable
. Порядок в карте определяется третьим аргументом шаблона (и аргументом для конструктора, если он используется). По умолчанию используется std::less<KeyType>
, по умолчанию используется оператор <
, но нет необходимости использовать значения по умолчанию. Просто напишите оператор сравнения (желательно как функциональный объект):
struct CmpMyType
{
bool operator()( MyType const& lhs, MyType const& rhs ) const
{
// ...
}
};
В C ++ предикат «сравнить» должен быть строгим слабым порядком . В частности, compare(X,X)
должно возвращать false
для любого X
т.е. если CmpMyType()(a, b)
возвращает true, то CmpMyType()(b, a)
должен возвращать значение false, а если оба возвращают false, то элементы считаются равными (членами одного и того же класса эквивалентности).
Строгий слабый порядок
Это математический термин для определения отношения между двумя объектами.
Его определение:
Два объекта x и y эквивалентны, если оба f (x, y) и f (y, x) ложны. Заметим, что объект всегда (по инварианту нерефлексивности) эквивалентен самому себе.
В терминах C ++ это означает, что если у вас есть два объекта данного типа, вы должны вернуть следующие значения по сравнению с оператором <.
X a;
X b;
Condition: Test: Result
a is equivalent to b: a < b false
a is equivalent to b b < a false
a is less than b a < b true
a is less than b b < a false
b is less than a a < b false
b is less than a b < a true
То, как вы определяете эквивалент / меньше, полностью зависит от типа вашего объекта.