In a C++ program, I have a std::list and std::map (although I am actually using boost:unordered_map) and I would like to know an elegant way of inserting all the elements in the list into the map. I would like the key to be the result of a method call on the elements on in the list.
So for example I have:
std::list<Message> messages = *another list with elements*;
std::map<std::string, Message> message_map;
And I want to insert all the elements from the list into the map with the key being message.id(), for every message in messages.
Is there a way to do this without looping over the list and doing it manually? I can't use C++11 but I would still be interested in C++11 solutions for interests sake. I am able to use boost.
Thank you.
A C++11 solution: You can use std::transform to transform from std::list elements to std::map elements:
std::transform(message.begin(), messages.end(),
std::inserter(message_map, message_map.end()),
[](const Message& m) { return std::make_pair(m.id(), m); });
The equivalent can be done with C++03 by passing a function pointer instead of a lambda.
Not sure if it is better than a for loop but you can use a functor
struct map_inserter {
std::map<string,Message>& t_map;
map_inserter(std::map<string,Message>& t_map) : t_map(t_map) {}
void operator()(Message& m) {
t_map.insert(std::pair<string,Message>(m.get_id(),m));
}
};
You can use it like this
std::map<string,Message> t_map;
std::for_each(vec.begin(), vec.end(), map_inserter(t_map));
If u can get iterators to the begining and end of the lis u can use for_each()
You could use accumulate:
typedef std::map<std::string, Message> MessageMap;
MessageMap& addIdAndMessage(MessageMap& messageMap, const Message& message) {
messageMap[message.id()] = message;
return messageMap;
}
int main() {
std::list<Message> messages;
//...
MessageMap message_map =
accumulate(messages.begin(), message.end(),
MessageMap(),
addIdAndMessage);
}
Related
Let's have
class InputClass;
class OutputClass;
OutputClass const In2Out(InputClass const &in)
{
//conversion implemented
}
and finally
std::vector<OutputClass> Convert(std::vector<InputClass> const &input)
{
std::vector<OutputClass> res;
res.reserve(input.size());
//either
for (auto const &in : input)
res.emplace_back(In2Out(in));
return res;
//or something like
std::transform(input.begin(), input.end(), std::back_inserter(res), [](InputClass const &in){return In2Out(in);});
return res;
}
And now my question:
Can I rewrite the Convert function somehow avoiding the need to name the new container? I. e. is there a way to construct a vector directly using something roughly like std::transform or std::for_each?
As in (pseudocode, this unsurprisingly does not work or even build)
std::vector<OutputClass> Convert(std::vector<InputClass> const &input)
{
return std::transform(input.begin(), input.end(), std::back_inserter(std::vector<OutputClass>()), [](InputClass const &in){return In2Out(in);});
}
Searched, but did not find any elegant solution. Thanks!
Starting in C++ 20 you can use the new std::ranges::transform_view to accomplish what you want. It will call your transformation function for each element in the container that it is adapting and you can use that view to invoke std::vector's iterator range constructor which will allocate the memory for the entire vector once and then populate the elements. It still requires you to create a variable in the function but it becomes much more streamlined. That would give you something like
std::vector<OutputClass> Convert(std::vector<InputClass> const &input)
{
auto range = std::ranges::transform_view(input, In2Out);
return {range.begin(), range.end()};
}
Do note that this should optimize to the exact same code your function generates.
Yes it is possible, and quite simple when using boost:
struct A
{
};
struct B
{
};
std::vector<B> Convert(const std::vector<A> &input)
{
auto trans = [](const A&) { return B{}; };
return { boost::make_transform_iterator(input.begin(), trans), boost::make_transform_iterator(input.end(), trans) };
}
https://wandbox.org/permlink/ZSqt2SbsHeY8V0mt
But as other mentioned this is weird and doesn't provide any gain (no performance gain or readability gain)
Can I rewrite the Convert function somehow avoiding the need to name the new container?
Not using just std::transform. std::transform itself never creates a container. It only inserts elements to an output iterator. And in order to both get output iterator to a container, and return the container later, you pretty much need a name (unless you allocate the container dynamically, which would be silly and inefficient).
You can of course write a function that uses std::transform, creates the (named) vector, and returns it. Then caller of that function doesn't need to care about that name. In fact, that's pretty much what your function Convert is.
I have some C++11 code like
std::vector<std::string> names;
std::map<std::string, std::string> first_to_last_name_map;
std::transform(names.begin(), names.end(), std::inserter(first_to_last_name_map, first_to_last_name_map.begin()), [](const std::string& i){
if (i == "bad")
return std::pair<std::string, std::string>("bad", "bad"); // Don't Want This
else
return std::pair<std::string, std::string>(i.substr(0,5), i.substr(5,5));
});
where I'm transforming a vector to a map using std::transform with a lambda function. My problem is that sometimes, as shown, I don't want to return anything from my lambda function, i.e. I basically want to skip that i and go to the next one (without adding anything to the map).
Is there any way to achieve what I'm thinking about? I can use boost if it helps. I want to avoid a solution where I have to do a pre-process or post-process on my vector to filter out the "bad" items; I should only need to look at each item once. Also, my actual logic is a bit more complicated than the if/else as written, so I think it would be nice to keep things encapsulated in this std::transform/lambda model if possible (though maybe what I'm trying to achieve isn't possible with this model).
EDIT: Just to emphasize, I'm looking to perform this operation (selectively processing vector elements and inserting them into a map) in the most efficient way possible, even if it means a less elegant solution or a big rewrite. I could even use a different map data type depending on what is most efficient.
template<class Src, class Sink, class F>
void transform_if(Src&& src, Sink&& sink, F&& f){
for(auto&& x:std::forward<Src>(src))
if(auto&& e=f(decltype(x)(x)))
*sink++ = *decltype(e)(e);
}
Now simply get a boost or std or std experiental optional. Have your f return an optional<blah>.
auto sink = std::inserter(first_to_last_name_map, first_to_last_name_map.begin());
using pair_type = decltype(first_to_last_name_map)::value_type;
transform_if(names, sink,
[](const std::string& i)->std::optional<pair_type>{
if (i == "bad")
return {}; // Don't Want This
else
return std::make_pair(i.substr(0,5), i.substr(5,5));
}
);
My personal preferred optional actually has begin end defined. And we get this algorithm:
template<class Src, class Sink, class F>
void polymap(Src&& src, Sink&& sink, F&& f){
for(auto&& x:std::forward<Src>(src))
for(auto&& e:f(decltype(x)(x)))
*sink++ = decltype(e)(e);
}
which now lets the f return a range, where optional is a model of a zero or one element range.
You can simply have a first/last pass with std::remove_if. E.g.
std::vector<std::string> names;
std::map<std::string, std::string> first_to_last_name_map;
std::transform(names.begin(),
std::remove_if(names.begin(),
names.end(),
[](const std::string &str){
return str=="bad";
}),
std::inserter(first_to_last_name_map,
first_to_last_name_map.begin()),
[](const std::string& i){
return std::pair<std::string, std::string>(i.substr(0,5), i.substr(5,5));
});
Note that remove_if simply shifts the removed items past the iterator it returns.
You can use boost::adaptors::filtered to first filter the vector of the elements you don't want, before passing it to transform.
using boost::adaptors::filtered;
boost::transform(names | filtered([](std::string const& s) { return s != "bad"; }),
std::inserter(first_to_last_name_map, first_to_last_name_map.begin()),
[](std::string const& i) { return std::make_pair(i.substr(0,5), i.substr(5,5)); });
Live demo
I find myself often in the situation where I write the following code:
std::map<int, std::vector<int>> dict;
void insert(int key, int val) {
if (dict.find(key) == dict.end()) {
dict[key] = std::vector<int>();
}
dict[key].push_back(val)
}
Is there a less wordy way (in C++11) of writing this insert function?
I don't think your function is particularly wordy, but in this scenario it could simply be replaced by dict[key].push_back(val) because operator[] on a map default constructs the value if it doesn't exist. You don't need the if block.
I have a vector of objects, and I'd like to count how many of them contain a certain property.
I'm fairly sure this can be done with the STL but I couldn't find an example. I could of course use a loop and count myself, but I need to do this many times and I'd prefer a concise way of doing this.
I'm looking to do something like the pseudo code below
class MyObj {
public:
std::string name;
}
std::vector<MyObj> objects
int calledJohn = count(objects,this->name,"jonn")
If you're looking to count how many objects have a certain property, std::count_if is the way to go. std::count_if takes a range to iterate over and the functor object that will determine if the object has the value:
auto calledJohn = std::count_if(std::begin(objects), std::end(objects),
[] (const MyObj& obj) { return obj.name == "John"; });
Use std::count_if
auto n = std::count_if(objects.begin(), objects.end(),
[](const MyObj& o) { return o.name == "jonn";});
There is a function std::count_if in the algorithm header that does exactly that for you. You have to provide an iterator range (so in your case objects.begin and objects.end) and a predicate that could be a lambda function or any other callable object:
auto number = std::count_if(objects.begin(), objects.end(), [](const MyObj &object){if(/*your condition*/){return true;}});
I want to have in a google protocol buffer repeated field only unique elements. In other words, need to use it as a std::set instead of std::vector.
Any idea which is the simplest and most efficient way to do that?
EDIT: I wouldn't want to use any iterators to loop through all the elements if possible.
Ok, as the comments from the question stated, there isn't any way of doing this without using iterators.
However, maybe someone else is interested in this, here is the function i coded to achieve this. This will take as parameters a RepeatedPtrField< T >*(the list) and a std::string(key of the new object that we intend to add to the list) and will return the element that matches the id, or NULL if there isn't any entry with this key in the RepeatedField list.
This way, you can easy keep a list of unique elements directly in a RepeatedField without using any other std structure:
template <class T>
T* repeatedFieldLookup( google::protobuf::RepeatedPtrField< T >* repeatedPtrField, std::string id)
{
google::protobuf::internal::RepeatedPtrOverPtrsIterator<T> it = repeatedPtrField->pointer_begin();
for ( ; it != repeatedPtrField->pointer_end() ; ++it )
{
CommonFields * commonMessage = (CommonFields*) (*it)->GetReflection()->
MutableMessage ((*it), (*it)->GetDescriptor()->FindFieldByName ("common"));
if(commonMessage->id() == id)
{
return *it;
}
}
return NULL;
}
NOTE: in the example above, the proto message will ALWAYS have a field called common(which in my case is also a proto message). You can replace that by anything that you want to make the comparison from your proto messages.
In the case where I had this class:
class Description : public ::google::protobuf::Message {
// ...
inline void add_field(const ::std::string& value);
inline const ::google::protobuf::RepeatedPtrField< ::std::string>& field() const;
// ...
};
I used std::find to only add a value if it didn't exist in the list:
#include <algorithm>
void addField(Description& description, const std::string& value) {
const auto& fields = description.field();
if (std::find(fields.begin(), fields.end(), value) == fields.end()) {
description.add_field(value);
}
}