I have a container that looks like this:
std::map<std::string, std::vector<double>> id_values;
I will iterate through other pairs of doubles and strings, and I want to add a new element to the map if it doesn't exist, or append to the vector if it does. Is there a more succinct solution than the following?
auto loc = id_values.find(key);
if (loc != id_values.end()) {
loc->second.push_back(val);
} else {
loc.insert({key, {val}});
}
I suppose I could do a ternary operator but I feel that will make the code less readable, I'm more wondering if there is a better pattern for what I'm trying to achieve rather than conditional.
You can just use operator[]. It will return a reference to the object with the specified key if it is in the map, or if the lookup fails, it will create a new value-initialized object (an empty vector in your case) and return a reference to the new object.
id_values[key].push_back(val);
Alternatively, if you need to use different constructor arguments instead of default-constructing the mapped_type, you can use try_emplace (or regular emplace if you can't use C++17):
auto [itr, inserted] = id_values.try_emplace(key, vector_constructor_args...);
itr->second.push_back(val);
Hello I think what you want to do is ether to insert an element in your container using the method insert or the operator[].
By using the operator [] what you want to do is simply:
for(auto it: map1)
if(map2.find(it.first) == map2.end() )
map2.insert(map2.end(),it);
Related
I have a std::map object.
std::map<std::string, std::string> m;
m.insert({ "abcd", "foo" });
m.insert({ "1234", "bar" });
and I want to get and remove the first element, like:
auto iter = m.begin();
auto [key, value] = std::move(*iter);
m.erase(iter);
do_something_with(key, value);
Is this considered safe?
(Moving from the iterator should make the key an empty string, which makes the m an invalid map.)
You can use std::map::extract like this:
auto nh = m.extract(m.begin());
and then use the key and value like this:
do_something(nh.key(), nh.mapped());
This has the needed property that no extra copies are made.
Is this considered safe?
On the condition that the map isn't empty, yes.
However, note that the key will be a deep copy; not moved one. This is because the the key of the map element is const.
How to safely “pop-front” from std::map without extra copy?
It is possible to move from the key too, if you use the extract member function:
auto handle = m.extract(m.begin());
// if you need separate objects:
auto key = std::move(handle.key());
auto mapped = std::move(handle.mapped());
Use std map extract. Using the resulting node handle, move the key/value to your key/value variables.
Prior to std map extract, this isn't fully possible. std map extract was added to let you do this, and similar operations like splicing maps.
I have a map<std::string, std::unique_ptr<Cls>> my_map. I would like to move out some value from this map, to have the following:
std::unique_ptr<Cls> cls = my_map.get_and_erase("some str");
erase doesn't return a value unfortunately. What is my best approach here?
Since C++17 you have std::map::extract:
// if the key exists in the map, it'll be deleted after this:
auto cls_node = my_map.extract("some str");
if(not cls_node.empty()) // cls_node.mapped() gives access to the mapped value
You can use the following algorithm:
Use find to get an iterator to the element.
Move from the element.
Erase using the iterator.
I need to change the "key" of a multiset:
multiset<IMidiMsgExt, IMidiMsgExtCompByNoteNumber> playingNotes;
such as that when I use the .find() function it search and return the first object (iterator) with that NoteNumber property value.
I said "first" because my multiset list could contains objects with the same "key". So I did:
struct IMidiMsgExtCompByNoteNumber {
bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) {
return lhs.NoteNumber() < rhs.NoteNumber();
}
};
but when I try to do:
auto it = playingNotes.find(60);
the compiler says no instance of overloaded function "std::multiset<_Kty, _Pr, _Alloc>::find [with _Kty=IMidiMsgExt, _Pr=IMidiMsgExtCompByNoteNumber, _Alloc=std::allocator<IMidiMsgExt>]" matches the argument list
Am I misunderstanding the whole thing? What's wrong?
I do believe that you have some misunderstandings here:
Part of an associative container's type is it's key type and comparator. Because C++ is strongly typed the only way to change the comparator on a container is to create a new container, copying or moving all the elements into it
Creating a copy of all the elements in a container is a potentially expensive process
By creating a copy you are violating the Single Source of Truth best practice
multiset is used infrequently, I have used it once in my career, others have pointed out it's shortcomings and recommended that you use another container, write your own container, or in my case I'd suggests simply using vector and sorting it how you want when you have to
I'm going to catalog your comments to show how the answer I've already given you is correct:
We're going to assume that the multiset<IMidiMsgExt, IMidiMsgExtCompByNoteNumber> that you've selected is necessary and cannot be improved upon by using vector as suggested in 4, where:
struct IMidiMsgExtCompByNoteNumber {
bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) {
return lhs.NoteNumber() < rhs.NoteNumber();
}
};
You cannot use multiset::find because that requires you tospecify the exact IMidiMsgExt you are searching for; so you'll need to use find_if(cbegin(playingNotes), cend(playingNotes), [value = int{60}](const auto& i){return i.mNote == value;}) to search for a specific property value. Which will be fine to use on to use directly on PlayingNotes without changing the sorting, because you say:
I want to delete the first note that has mNote of 60. No matter the mTime when deleting.
You'll need to capture the result of the [find_if], check if it is valid, and if so erase it as demonstrated in my answer, because you say:
The first element find will find for that, erase. [sic]
I would roll the code from my answer into a function because you say:
Ill recall find if I want another element, maybe with same value, to get deleted [sic]
Your final solution should be to write a function like this:
bool foo(const multiset<IMidiMsgExt, IMidiMsgExtCompByNoteNumber>& playingNotes, const int value) {
const auto it = find_if(cbegin(playingNotes), cend(playingNotes), [=](const auto& i){return i.mNote == value;});
const auto result = it != cend(playingNotes);
if(result) {
playingNotes.erase(it);
}
return result;
}
And you'd call it something like this: foo(playingNotes, 60) if you wish to know whether an element was removed you may test foo's return.
I have the following:
std::map<std::string, std::vector<std::string>> container;
To add new items, I do the following:
void add(const std::string& value) {
std::vector<std::string> values;
values.push_back(value);
container.insert(key, values);
}
Is there a better way to add the value?
Thanks
First of all, std::map holds std::pairs of key-value. You need to insert one of these pairs:. Second, you don't need to make a temporary vector.
container.insert(make_pair(key, std::vector<std::string>(1, value)));
You can express the above using brace-enclosed initializers:
container.insert({key, {value}});
Note that std::map::insert only succeeds if there isn't already an element with the same key. If you want to over-write an existing element, use operator[]:
container[key] = {value};
With intializer lists (C++11), you could just do container.insert({key, { value }}); where {value} will build a std::vector and {key, {value}} will build a std::pair.
What is the difference between the index overloaded operator and the insert method call for std::map?
ie:
some_map["x"] = 500;
vs.
some_map.insert(pair<std::string, int>("x", 500));
I believe insert() will not overwrite an existing value, and the result of the operation can be checked by testing the bool value in the iterator/pair value returned
The assignment to the subscript operator [] just overwrites whatever's there (inserting an entry if there isn't one there already)
Either of the insert and [] operators can cause issues if you're not expecting that behaviour and don't accommodate for it.
Eg with insert:
std::map< int, std::string* > intMap;
std::string* s1 = new std::string;
std::string* s2 = new std::string;
intMap.insert( std::make_pair( 100, s1 ) ); // inserted
intMap.insert( std::make_pair( 100, s2 ) ); // fails, s2 not in map, could leak if not tidied up
and with [] operator:
std::map< int, std::string* > intMap;
std::string* s1 = new std::string;
std::string* s2 = new std::string;
intMap[ 100 ] = s1; // inserted
intMap[ 100 ] = s2; // inserted, s1 now dropped from map, could leak if not tidied up
I think those are correct, but haven't compiled them, so may have syntax errors
For a map, the former (operator[]) expression will always replace the value part of the key-value pair with the new supplied value. A new key-value pair will be inserted if one doesn't already exist.
In contrast, insert will only insert a new key-value pair if a key-value pair with the supplied key part does not already exist in the map.
In addition to the fact that map::operator[] will replace an existing value is that operator[] map::will create and add to the map a default existing value to replace before the replacement occurs (the map::operator[]() call has to return a reference to something). For items that are expensive to create this could be a performance issue.
See "Item 24: Choose carefully between map::operator[] and map::insert when efficiency is important" in Scott Meyers' Effective STL.
The insert method inserts into the map, while the overloaded index operator will return the element with the key key_value if it is in the map, if it is not already in the map then it will insert it.
Just to add to Michael Burr's answer, the book you should be looking for is Scott Meyer's <Effective STL> not the <Effective C++>, as Michael has linked it wrongly.
the expression
m[k] = v;
checks to see if the key k is already in the map. If not, it’s
added, along with v as its corresponding value. If k is already in the
map, its associated value is updated to v.
(when adding new k-v)
We first default-construct a <value obj>, then we immediately assign
it a new value. If it’s measurably more efficient to construct a
<value obj> with the value we want instead of default-constructing the
<value obj> and then doing the assignment, we’d be better off replacing our
use of operator[] (including its attendant construction plus
assignment) with a straightforward call to insert:
(updating existing k's v)
we now understand that when an “add” is performed, insert is more
efficient than operator[]. The situation is reversed when we do an
update, i.e., when an equivalent key is already in the
map.