问题
Given the minimal C++11 STL example:
set<int> S = {1,2,3,4};
for(auto &x: S) {
cout << x;
cout << ",";
}
Is there a way to check if x
is the one right before the end? The goal in this example is to output 1,2,3,4
and not the final comma at the end. Currently I use a standard for loop with two iterators,
set<int>::const_iterator itr;
set<int>::const_iterator penultimate_end_itr = --S.end();
for(itr=S.begin(); itr!=penultimate_end_itr;++itr)
cout << (*itr) << ',';
cout << (*penultimate_end_itr);
Which works, but is terribly cumbersome. Is there a way to do the check within the range-based for loop?
EDIT: The point of the question is not to print out a comma separated list. I want to know if a range-based for loop has any knowledge of the penultimate element in the list (i.e. is it one before the end). The minimal example was presented so we all have a common code block to talk about.
回答1:
The very purpose of range-based for loops is to forget the iterator. As such, they only allow you access to the current value and not the iterator. Would the following code do it for you?
set<int> S = {1,2,3,4};
std::string output;
for(auto &x: S) {
if (!output.empty())
output += ",";
output += to_string(x);
}
cout << output;
EDIT
Another solution: Instead of comparing iterators (as one would do with "normal" for loops), you could compare the addresses of the values:
set<int> S = {1,2,3,4};
auto &last = *(--S.end());
for (auto &x : S)
{
cout << x;
if (&x != &last)
cout << ",";
}
回答2:
Boost.Range can help out here:
if (std::begin(S) != std::end(S)) {
std::cout << *std::begin(S);
for (const auto &x: boost::make_iterator_range(std::next(std::begin(S)), std::end(S))) {
std::cout << ", " << x;
}
}
A much more flexible approach is to index the range, using boost::adaptors::indexed (since Boost 1.56):
for (const auto &element: boost::adaptors::index(S)) {
std::cout << (element.index() ? ", " : "") << element.value();
}
In versions of Boost prior to 1.56 boost::adaptors::indexed
won't work but you can easily write a work-alike:
template <typename... T>
auto zip(const T&... ranges) -> boost::iterator_range<boost::zip_iterator<decltype(boost::make_tuple(std::begin(ranges)...))>>
{
auto zip_begin = boost::make_zip_iterator(boost::make_tuple(std::begin(ranges)...));
auto zip_end = boost::make_zip_iterator(boost::make_tuple(std::end(ranges)...));
return boost::make_iterator_range(zip_begin, zip_end);
}
template<typename T>
auto enumerate(const T &range) -> boost::iterator_range<boost::zip_iterator<boost::tuple<
boost::counting_iterator<decltype(boost::distance(range))>, decltype(std::begin(range))>>>
{
return zip(boost::make_iterator_range(boost::make_counting_iterator(0),
boost::make_counting_iterator(boost::distance(range))), range);
}
for (const auto &tup: enumerate(S)) {
std::cout << (tup.get<0>() ? ", " : "") << tup.get<1>();
}
This is using the zip
function from Sequence-zip function for c++11?
来源:https://stackoverflow.com/questions/12200313/can-range-based-for-loops-be-aware-of-the-end