Reputation: 221
I am working with a data structure that looks like this:
map<string, set<string>> data;
By now, I had no problems with working with the map by using foreach cycle, however, now I need to print out the data from the map like this:
KEY: elem1, elem2, elem3
KEY2: elem1, elem2, elem3
Because of the missing comma at the end, I cannot quite use the foreach cycle anymore (can I?). Since I am new to C++, C++11 and all the fun it offers, I am quite lost. I came up with:
for ( auto i : data )
{
cout << i.first << ": ";
for ( size_t i = 0; i < /* size of the set */ - 1; i ++ )
cout << j << ", ";
cout << /* the last element of the set */ << endl;
}
I know what I want, I just have no idea about syntax and the C++ reference is not helping much. Thanks for answers, meanwhile I am going to browse the C++ reference myself.
Upvotes: 5
Views: 341
Reputation: 275385
template<typename R>
struct not_last_t {
R r;
not_last_t(R&& in):r(std::forward<R>(in)){}
not_last_t(not_last_t&&)=default;
void operator=(not_last_t&&)=delete;
// C++14 because lazy. Can be written in C++11:
auto begin() { using std::begin; return begin(r); }
auto end() {
using std::end; auto ret=end(r);
if(ret==begin()) return ret;
return std::next(ret,-1);
}
};
template<typename R>
not_last_t<R> not_last(R&&r){return {sts::forward<R>(r);}}
use:
for(auto x:not_last(s))
std::cout << x << ", ";
if (!s.empty())
std::cout << s.back();
efficient and clear at point of use. Bulky in library.
Uses some C++14 features, because I am lazy, and probably full of typos. If there is interest I can clean the code up.
Upvotes: 0
Reputation: 299810
A pattern I often use (with BOOST_FOREACH
), is:
bool first = true;
for (auto const& e: collection) {
if (first) { first = false; } else { out << ", "; }
...
}
There is a STL way of doing this though, using ostream_iterator
:
std::copy(collection.begin(), collection.end(),
std::ostream_iterator<value_type>(out, ", "));
and so your example becomes:
for (auto const& pair: map) {
out << pair.first << ": ";
std::copy(pair.second.begin(), pair.second.end(),
std::ostream_iterator<std::string>(out, ", "));
}
but honestly, I still feel like using the bool first = true
approach is more readable.
Upvotes: 4
Reputation: 42934
You can consider code like this (which uses a flag to print the first string in the set in a different way than the following strings, that are instead prefixed with ", "
).
Note that since you are observing the elements in the containers, you may want to use const auto&
in the range-for loops.
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
int main() {
// Fill the data structure with some test data
map<string, set<string>> data;
data["Key1"].emplace("hello");
data["Key1"].emplace("world");
data["Key1"].emplace("test");
data["Key2"].emplace("Ciao");
data["Key2"].emplace("Mondo");
// For each item in the outer map
for (const auto& x : data) {
// Print item's key
cout << x.first << ": ";
// Special output for the first string in the set
bool first = true;
// Note: x.first is the key, x.second is the associated set.
// For each string in the set:
for (const auto& s : x.second) {
// Prefix every string except the first one with ", "
if (!first) {
cout << ", ";
} else {
first = false;
}
// Print current string in the set
cout << s;
}
cout << endl;
}
return 0;
}
Output:
Key1: hello, test, world Key2: Ciao, Mondo
Upvotes: 2
Reputation: 27528
You won't find a "perfect" solution. Basically, you want to do the same thing for every element (by virtue of the range-based for loop) but still handle one case in a different way. That doesn't go together. One thing you could do is use an additional flag to handle the first element differently:
bool first_element = true;
for ( auto i : data )
{
if ( !first_element)
{
// print ","
}
// print i;
first_element = false;
}
Generally, don't worry too much about it if this is just a small piece of code in some isolated place somewhere in your project.
Upvotes: 1
Reputation: 37945
I typically (across several languages) use a delimiter that I initialize to an empty string and then change in the loop:
for (auto& i : data) {
std::cout << i.first << ": ";
std::string delim;
for (auto& s : i.second) {
std::cout << delim << s;
delim = ", ";
}
std::cout << std::endl;
}
KEY: elem1, elem2, elem3
KEY1: elem1, elem2, elem3
Another solution is to use ostream iterators
Upvotes: 2
Reputation: 50044
I typically use iterators to join strings.
for (auto& i : data) {
cout << i.first << ':';
auto p = i.second.begin();
auto q = i.second.end();
if (p != q) {
cout << ' ' << *p;
for (++p; p != q; ++p) {
cout << ", " << *p;
}
}
cout << '\n';
}
Upvotes: 1