C++ map of objects erase when key not present - c++

I have the following map in C++ (gcc):
map<int, EdgeExtended> myMap;
where the definition of EdgeExtended is:
struct EdgeExtended {
int neighborNodeId;
int weight;
int arrayPointer;
bool isCrossEdge;
EdgeExtended(Edge _edge, int _arrayPointer) {
neighborNodeId = _edge.neighborNodeId;
weight = _edge.weight;
arrayPointer = arrayPointer;
isCrossEdge = _edge.isCrossEdge;
}
EdgeExtended(const EdgeExtended & _edge) {
neighborNodeId = _edge.neighborNodeId;
weight = _edge.weight;
arrayPointer = _edge.arrayPointer;
isCrossEdge = _edge.isCrossEdge;
}
EdgeExtended(int _neighborNodeId, int _weight, bool _isCrossEdge, int _arrayPointer) {
neighborNodeId = _neighborNodeId;
weight = _weight;
arrayPointer = _arrayPointer;
isCrossEdge = _isCrossEdge;
}
void setValues(int _neighborNodeId, int _weight, bool _isCrossEdge, int _arrayPointer) {
neighborNodeId = _neighborNodeId;
weight = _weight;
arrayPointer = _arrayPointer;
isCrossEdge = _isCrossEdge;
}
EdgeExtended() {
neighborNodeId = -1;
weight = -1;
arrayPointer = -1;
isCrossEdge = false;
}
};
I want to do this (plain example):
EdgeMap edge;
int nodeId=18;
edge=map.erase(nodeId);
a) Is this code correct, does erase return the object that corresponds to the key? b) If yes, what does erase return when the key is not present? c) if this code is wrong, how can I check if a key is present, the object mapped to the key and then erase the pair from map. Keep in mind that performance is rather crucial, so I need the most efficient way.

a) No, it is not correct. The std::map::erase method you call returns the number of erased elements.
What you can do is use std::map::find to check if the an element with the given key is in the map. This returns an iterator to the element if it exists, to end() if it doesn't. You can pass this iterator to the relevant std::map::erase overload.
EdgeMap edge;
int nodeId=18;
ExtendedEdge removedEdge;
....
EdgeMap::iterator it = edge.find(nodeId);
if( it != edge.end() )
{
// found element.
removedEdge = it->second; // or removeEdge = std::move(it->second) in C++11
edge.erase(it);
}

a) Is this code correct, does erase return the object that corresponds
to the key?
No, this returns the number of elements that were removed.
b) If yes, what does erase return when the key is not present?
N/A. It simply returns 0.
c) if this code is wrong, how can I check if a key is present, the
object mapped to the key and then erase the pair from map. Keep in
mind that performance is rather crucial, so I need the most efficient
way.
auto it = yourMap.find(nodeId);
if (it != yourMap.end()) {
EdgeExtended theObjectToRemove = *it;
yourMap.erase(it);
}
The complexity of this is the same as for a plain erase(nodeId);, as erase(it) takes amortized constant time.

I would do it with an iterator, like this:
typedef map<int, EdgeExtended> EdgeMap;
bool RemoveEdge( EdgeMap & myMap, int nodeId, EdgeExtended &edge )
{
EdgeMap::iterator e = myMap.find(nodeId);
if( e == myMap.end() ) return false;
edge = e->second;
myMap.erase(e);
return true;
}

Related

How do I Optimize my C++ key-value program to have a faster runtime?

This is a2.hpp, and is the program that can be edited, as far as I know the code is correct, just too slow. I am honestly lost here, I know my for loops are probably whats slowing me down so much, maybe use an iterator?
// <algorithm>, <list>, <vector>
// YOU CAN CHANGE/EDIT ANY CODE IN THIS FILE AS LONG AS SEMANTICS IS UNCHANGED
#include <algorithm>
#include <list>
#include <vector>
class key_value_sequences {
private:
std::list<std::vector<int>> seq;
std::vector<std::vector<int>> keyref;
public:
// YOU SHOULD USE C++ CONTAINERS TO AVOID RAW POINTERS
// IF YOU DECIDE TO USE POINTERS, MAKE SURE THAT YOU MANAGE MEMORY PROPERLY
// IMPLEMENT ME: SHOULD RETURN SIZE OF A SEQUENCE FOR GIVEN KEY
// IF NO SEQUENCE EXISTS FOR A GIVEN KEY RETURN 0
int size(int key) const;
// IMPLEMENT ME: SHOULD RETURN POINTER TO A SEQUENCE FOR GIVEN KEY
// IF NO SEQUENCE EXISTS FOR A GIVEN KEY RETURN nullptr
const int* data(int key) const;
// IMPLEMENT ME: INSERT VALUE INTO A SEQUENCE IDENTIFIED BY GIVEN KEY
void insert(int key, int value);
}; // class key_value_sequences
int key_value_sequences::size(int key) const {
//checks if the key is invalid or the count vector is empty.
if(key<0 || keyref[key].empty()) return 0;
// sub tract 1 because the first element is the key to access the count
return keyref[key].size() -1;
}
const int* key_value_sequences::data(int key) const {
//checks if key index or ref vector is invalid
if(key<0 || keyref.size() < static_cast<unsigned int>(key+1)) {
return nullptr;
}
// ->at(1) accesses the count (skipping the key) with a pointer
return &keyref[key].at(1);
}
void key_value_sequences::insert(int key, int value) {
//checks if key is valid and if the count vector needs to be resized
if(key>=0 && keyref.size() < static_cast<unsigned int>(key+1)) {
keyref.resize(key+1);
std::vector<int> val;
seq.push_back(val);
seq.back().push_back(key);
seq.back().push_back(value);
keyref[key] = seq.back();
}
//the index is already valid
else if(key >=0) keyref[key].push_back(value);
}
#endif // A2_HPP
This is a2.cpp, this just tests the functionality of a2.hpp, this code cannot be changed
// DO NOT EDIT THIS FILE !!!
// YOUR CODE MUST BE CONTAINED IN a2.hpp ONLY
#include <iostream>
#include "a2.hpp"
int main(int argc, char* argv[]) {
key_value_sequences A;
{
key_value_sequences T;
// k will be our key
for (int k = 0; k < 10; ++k) { //the actual tests will have way more than 10 sequences.
// v is our value
// here we are creating 10 sequences:
// key = 0, sequence = (0)
// key = 1, sequence = (0 1)
// key = 2, sequence = (0 1 2)
// ...
// key = 9, sequence = (0 1 2 3 4 5 6 7 8 9)
for (int v = 0; v < k + 1; ++v) T.insert(k, v);
}
T = T;
key_value_sequences V = T;
A = V;
}
std::vector<int> ref;
if (A.size(-1) != 0) {
std::cout << "fail" << std::endl;
return -1;
}
for (int k = 0; k < 10; ++k) {
if (A.size(k) != k + 1) {
std::cout << "fail";
return -1;
} else {
ref.clear();
for (int v = 0; v < k + 1; ++v) ref.push_back(v);
if (!std::equal(ref.begin(), ref.end(), A.data(k))) {
std::cout << "fail 3 " << A.data(k) << " " << ref[k];
return -1;
}
}
}
std::cout << "pass" << std::endl;
return 0;
} // main
If anyone could help me improve my codes efficiency I would really appreciate it, thanks.
First, I'm not convinced your code is correct. In insert, if they key is valid you create a new vector and insert it into sequence. Sounds wrong, as that should only happen if you have a new key, but if your tests pass it might be fine.
Performance wise:
Avoid std::list. Linked lists have terrible performance on today's hardware because they break pipelineing, caching and pre-fetching. Always use std::vector instead. If the payload is really big and you are worried about copies use std::vector<std::unique_ptr<T>>
Try to avoid copying vectors. In your code you have keyref[key] = seq.back() which copies the vector, but should be fine since it's only one element.
Otherwise there's no obvious performance problems. Try to benchmark and profile your program and see where the slow parts are. Usually there's one or two places that you need to optimize and get great performance. If it's still too slow, ask another question where you post your results so that we can better understand the problem.
I will join Sorin in saying don't use std::list if avoidable.
So you use key as direct index, where does it say it is none-negative? where does it say its less than 100000000?
void key_value_sequences::insert(int key, int value) {
//checks if key is valid and if the count vector needs to be resized
if(key>=0 && keyref.size() < static_cast<unsigned int>(key+1)) {
keyref.resize(key+1); // could be large
std::vector<int> val; // don't need this temporary.
seq.push_back(val); // seq is useless?
seq.back().push_back(key);
seq.back().push_back(value);
keyref[key] = seq.back(); // we now have 100000000-1 empty indexes
}
//the index is already valid
else if(key >=0) keyref[key].push_back(value);
}
Can it be done faster? depending on your key range yes it can. You will need to implement a flat_map or hash_map.
C++11 concept code for a flat_map version.
// effectively a binary search
auto key_value_sequences::find_it(int key) { // type should be iterator
return std::lower_bound(keyref.begin(), keyref.end(), [key](const auto& check){
return check[0] < key; // key is 0-element
});
}
void key_value_sequences::insert(int key, int value) {
auto found = find_it(key);
// at the end or not found
if (found == keyref.end() || found->front() != key) {
found = keyref.emplace(found, key); // add entry
}
found->emplace_back(value); // update entry, whether new or old.
}
const int* key_value_sequences::data(int key) const {
//checks if key index or ref vector is invalid
auto found = find_it(key);
if (found == keyref.end())
return nullptr;
// ->at(1) accesses the count (skipping the key) with a pointer
return found->at(1);
}
(hope I got that right ...)

Use existing object in vector or create new if not exists in C++

Let's assume that I have a class named Store which contains products. Functions are inlined for simplicity.
class Store
{
public:
Store(string name)
: _name(name)
{}
string getName() const
{ return _name; };
const std::vector<string> getProducts()
{ return _products; };
void addProduct(const string& product)
{ _products.push_back(product); }
private:
const string _name;
std::vector<string> _products;
};
Then I have a two dimensional string array which contains store-product -pairs. Same store can be multiple times in array.
string storeListing[4][2] = {{"Lidl", "Meat"},
{"Walmart", "Milk"},
{"Lidl", "Milk"},
{"Walmart", "Biscuits"}};
Now I want to iterate through array, create Store-object for each store in array and add products of it to object. So I need to use existing Store-object or create a new if there is no any with correct name yet. What is a way to implement this? Currently I'm trying to use pointer and set it to relevant object, but I'm getting sometimes segmentation faults and sometimes other nasty problems when I modify code slightly. I guess I'm calling some undefined behavior here.
std::vector<Store> stores;
for (int i = 0; i < 4; ++i) {
string storeName = storeListing[i][0];
string productName = storeListing[i][1];
Store* storePtr = nullptr;
for (Store& store : stores) {
if (store.getName() == storeName) {
storePtr = &store;
}
}
if (storePtr == nullptr) {
Store newStore(storeName);
stores.push_back(newStore);
storePtr = &newStore;
}
storePtr->addProduct(productName);
}
Most likely, because you insert "Store" copies into your vector:
if (storePtr == nullptr) {
Store newStore(storeName); //create Store on stack
stores.push_back(newStore); //Make a COPY that is inserted into the vec
storePtr = &newStore; // And this is where it all goes wrong.
}
newStore goes out of scope at the end of the if and StorePtr is lost.
Try it with:
storePtr = stores.back();
Or make your vector a std::vector<Store*>.
And then:
if (storePtr == nullptr) {
Store * newStore = new Store(storeName); //create Store on stack
stores.push_back(newStore); //Make a COPY that is inserted into the vec
storePtr = newStore; // And this is where it all goes wrong.
}
And of course, as the comments suggest, a std::map would be better suited here.
In short, std::map stores key-value pairs. The key would most likely be your store name, and the value the product.
Quick example:
std::map<std::string, std::string> myMap;
myMap["Lidl"] = "Milk";
myMap["Billa"] = "Butter";
//check if store is in map:
if(myMap.find("Billa") != myMap.end())
....
Note, you can of course use your Store object as value. To use it as key, you have to take care of a few things:
std::maps with user-defined types as key
For your specific example i would suggest you use a std::string as key, and a vector of Products as value.
Use a std::unordered_set<Store>, where the hash type is the string name of the store. Using a map-like type would lead to duplicated storage of the store name (one time as a key to the map and one time inside the Store object itself).
template <>
struct std::hash<Store> {
using Store = argument_type;
using result_type = std::size_t;
result_type operator()(const argument_type& s) const noexcept {
return result_type{ std::hash<std::string>{}(s._name) }();
}
};
std::unordered_set<Store> stores;
for (int i = 0; i < 4; ++i) {
string storeName = storeListing[i][0];
string productName = storeListing[i][1];
auto iter = stores.find(storeName);
if(iter == stores.end()) iter = stores.emplace(storeName);
iter->addProduct(productName);
}
There are a few problems in your approach.
Problem 1:
Store has a const data member. This will make it impossible to reorder the vector of stores. That needs to be corrected.
Problem 2:
You need to point at the right Store after insertion. Here's one approach:
// decompose the problem:
// first step - get a pointer (iterator) to a mutable store *in the vector*
auto locate_or_new(std::vector<Store>& stores, std::string const& storeName)
-> std::vector<Store>::iterator
{
auto iter = std::find_if(begin(stores), end(stores),
[&](Store& store)
{
return store.getName() == storeName;
});
if (iter == end(stores))
{
iter = stores.emplace(end(stores), storeName);
}
return iter;
}
//
// 2 - insert the product in terms of the above function.
auto addProduct(std::vector<Store>& stores, std::string const& storeName, std::string const& productName)
-> std::vector<Store>::iterator
{
auto istore = locate_or_new(stores, storeName);
istore->addProduct(productName);
return istore;
}
Note:
Since inserting objects into a vector can cause iterator invalidation, you will need to be careful to not hold references to objects inside the vector across sections of code that could create new stores.
if (storePtr == nullptr) {
Store newStore(storeName);
stores.push_back(newStore);
storePtr = &newStore;
}
Once the if ends newStore is gone and you are left with dangling pointer storePtr.
You could use an std::set<Store *> here
std::set<Store *> myset;
Store *c = new Store("store3");
std::set<Store *>::iterator iter = myset.find(c);
if(iter!=myset.end())
{
(*iter)->addProduct("Product1");
}
else
{
c->addProduct("Product1");
myset.insert(c);
}
There is a solution using vectors and iterators. Dont forget to include the "algorithm" header!
std::vector<Store> stores;
for (int i = 0; i < 4; ++i) {
string storeName = storeListing[i][0];
string productName = storeListing[i][1];
auto storeIter = std::find_if(stores.begin(), stores.end(), [storeName](Store store) -> bool {
return store.getName() == storeName;
}); //Find the store in the vector
if (storeIter == stores.end()) //If the store doesn't exists
{
stores.push_back(Store(storeName)); //Add the store to the vector
storeIter = prev(stores.end()); //Get the last element from the vector
}
Store* storePtr = &(*storeIter); //You can convert the iterator into a pointer if you really need it
storeIter->addProduct(productName);
//storePtr->addProduct(productName);
}

How to delete an object from a map which contains a vector as value in C++

I have a map which contains a of vector of type Messages.
std::map<std::string, std::vector<Message>> storage;
class Message has 3 member variables.
class Message
{
private:
std::string msg;
std::string msg_type;
int priority;
}
Now i am trying to delete an object which has priority(say 3) from the map. i am using the following function for it. But it doesn't work.
void deleteByMessagePriority(int priority)
{
if (checkPriorityOfMessage(priority))
{
for (std::map<std::string, std::vector<Message>>::iterator it = storage.begin(); it != storage.end(); it++)
{
std::vector<Message> listOfMsgs = it->second;
for (std::vector<Message>::iterator vec_it = listOfMsgs.begin(); vec_it != listOfMsgs.end(); vec_it++)
//for(int index = 0;index < listOfMsgs.size();index++)
{
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
}
}
}
}
Look carefully at this:
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
You're looking at the priority of one message (the one referred to by vec_it), but then what are you deleting if it matches?
Instead of writing your own loop, I'd use erase and std::remove_if to remove all the items you care about in that vector at once.
for (auto & item : storage) {
auto &vec = item.second;
auto start_junk = std::remove_if(
vec.begin(), vec.end(),
[=](Message const &m) { return m.priority == priority; });
vec.erase(start_junk, vec.end());
}
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
pop_back() removes the last element of the vector which you don't want.You want to check erase
Also remember erase() invalidates the iterators so you need iterator to the next element after a deleted element for which we can fortunately use return value of erase
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it); //returns iterator to the element after vec_it which can also be `listOfMsgs.end()`
std::vector<Message> listOfMsgs = it->second;
.
.
.
listOfMsgs.pop_back();
You're copying the list, only to modify the copy. What you meant is:
std::vector<Message>& listOfMsgs = it->second;
Then you can proceed erasing elements. As Gaurav Sehgal says, use the return value of erase:
std::vector<Message>::iterator vec_it = listOfMsgs.begin();
while (vec_it != listOfMsgs.end())
{
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it);
}
else
{
++vec_it;
}
}

C++ Bimap Left unordered_map Right sorted mutable multimap

I need to implement the following datastructure for my project. I have a relation of
const MyClass*
to
uint64_t
For every pointer I want to save a counter connected to it, which can be changed over time (in fact only incremented). This would be no problem, I could simply store it in a std::map. The problem is that I need fast access to the pointers which have the highest values.
That is why I came to the conclusion to use a boost::bimap. It is defined is follows for my project:
typedef boost::bimaps::bimap<
boost::bimaps::unordered_set_of< const MyClass* >,
boost::bimaps::multiset_of< uint64_t, std::greater<uint64_t> >
> MyBimap;
MyBimap bimap;
This would work fine, but am I right that I can not modify the uint64_t on pair which were inserted once? The documentation says that multiset_of is constant and therefore I cannot change a value of pair in the bimap.
What can I do? What would be the correct way to change the value of one key in this bimap? Or is there a simpler data structure possible for this problem?
Here's a simple hand-made solution.
Internally it keeps a map to store the counts indexed by object pointer, and a further multi-set of iterators, ordered by descending count of their pointees.
Whenever you modify a count, you must re-index. I have done this piecemeal, but you could do it as a batch update, depending on requirements.
Note that in c++17 there is a proposed splice operation for sets and maps, which would make the re-indexing extremely fast.
#include <map>
#include <set>
#include <vector>
struct MyClass { };
struct store
{
std::uint64_t add_value(MyClass* p, std::uint64_t count = 0)
{
add_index(_map.emplace(p, count).first);
return count;
}
std::uint64_t increment(MyClass* p)
{
auto it = _map.find(p);
if (it == std::end(_map)) {
// in this case, we'll create one - we could throw instead
return add_value(p, 1);
}
else {
remove_index(it);
++it->second;
add_index(it);
return it->second;
}
}
std::uint64_t query(MyClass* p) const {
auto it = _map.find(p);
if (it == std::end(_map)) {
// in this case, we'll create one - we could throw instead
return 0;
}
else {
return it->second;
}
}
std::vector<std::pair<MyClass*, std::uint64_t>> top_n(std::size_t n)
{
std::vector<std::pair<MyClass*, std::uint64_t>> result;
result.reserve(n);
for (auto idx = _value_index.begin(), idx_end = _value_index.end() ;
n && idx != idx_end ;
++idx, --n) {
result.emplace_back((*idx)->first, (*idx)->second);
}
return result;
}
private:
using map_type = std::map<MyClass*, std::uint64_t>;
struct by_count
{
bool operator()(map_type::const_iterator l, map_type::const_iterator r) const {
// note: greater than orders by descending count
return l->second > r->second;
}
};
using value_index_type = std::multiset<map_type::iterator, by_count>;
void add_index(map_type::iterator iter)
{
_value_index.emplace(iter->second, iter);
}
void remove_index(map_type::iterator iter)
{
for(auto range = _value_index.equal_range(iter);
range.first != range.second;
++range.first)
{
if (*range.first == iter) {
_value_index.erase(range.first);
return;
}
}
}
map_type _map;
value_index_type _value_index;
};

I want to perform in place modification to a std::map while iterating over it

I need to know the best way to do the in place modification to the map without taking a local copy of the values modified and then pushing it again into the original map.
I have detailed the snippet below that explains the problem:
#include <string>
#include <map>
struct EmployeeKey
{
std::string name;
int amount;
int age;
};
struct EmployeeDetail
{
std::string dept;
int section;
int salary;
};
bool compareByNameAge(const std::string& name,
const int& age,
const EmployeeKey& key )
{
return name > key.name && age > key.age;
}
typedef std::map<EmployeeKey, EmployeeDetail> EmployeeMap;
int main()
{
EmployeeMap eMap;
// insert entries to the map
int age = 10;
std::string name = "John";
EmployeeMap transformMap;
foreach( iter, eMap )
{
if ( compareByNameAge(name, age, iter->first) )
{
//**This is what i want to avoid.......
// take a copy of the data modified
// push it in a new map.
EmployeeDetail det = iter->second;
det.salary = 1000;
transformMap[iter->first] = det;
}
}
//** Also, i need to avoid too...
// do the cpy of the modified values
// from the transform map to the
// original map
foreach( iter1, transformMap )
eMap[iter1->first] = iter1->second;
}
You can simply modifiy the element directly through the iterator (which points directly to the corresponding item):
foreach(iter, eMap)
{
if (compareByNameAge(name, age, iter->first))
iter->second.salary = 1000;
}
for more complex modifications you could take the value by reference:
EmployeeDetail& det = iter->second;
det.salary = 1000;
In c++ you can typically not modify a collection while iterating, but that only means that you can't remove/add items. Modifying existing items is typically fine in C++11. What you can't modify is the key in a map and any part of the element in set, but those are const in c++11 anyways, so you can't modify those. In C++03 you need to remember not to change the keypart of an element in a set.
iter->second is a reference to the EmployeeDetail object, which you can modify directly - e.g.
foreach( iter, eMap )
{
if ( compareByNameAge(name, age, iter->first) )
{
iter->second.salary = 1000;
}
}
No need for the transformMap
Just take a reference to the value.
EmployeeDetail& det = iter->second; // notice new '&' character.
det.salary = 1000; // modifies the 'EmployeeDetail' object in-place.
Can't you just do the following?
it->second.salary = 1000;
It's fine to alter the values of the map objects (the second part of the value_type) during foreach iteration. You just can't add or remove any keys--no insert or erase.
Wouldn't just iterating over the map and doing
iter->second.salary = 1000;
solve your problem?
You can't use std::transform because it assigns iterators, and the first element of a map iterator is always const.
Additionally your code doesn't show us the comparison for your employee key, so I'll assume you have one that implements strict weak ordering. A basic outline:
You can use for_each though, since the predicate can be stateless:
class SalaryUpdater
{
public:
SalaryUpdater(const std::string& name, int age) : name_(name), age_(age) { }
void operator()(EmployeeMap::value_type& item)
{
if(compareByNameAge(name_, age_, item.first))
{
item.second.salary = 1000;
}
}
private:
std::string name_;
int age_;
};
int main()
{
EmployeeMap eMap;
// insert entries to the map
std::for_each(eMap.begin(), eMap.end(), SalaryUpdater("John", 10));
}
Get a reference to the EmployeeDetail.
If you have C++11, try this:
for (auto& pair : eMap )
if (pair.first.name == "Rob")
pair.second.salary *= 1000;
Note: you can only change pair.second. pair.first is const, and must not be changed (it is, after all, the key to the map.)
If you don't have C++11, try this:
for(EmployeeMap::iterator it = eMap.begin(); it != eMap.end(); ++it)
if(pair.first.name == "Rob")
pair.second.hours /= 2;
C++17 update with structured bindings:
for (auto &[key, detail] : employeeMap)
if (compareByNameAge(name, age, key))
detail.salary = 1000;