How to implement auto iterators correctly - c++

I decided to replace
for (auto messageIterator = message.begin(); messageIterator != message.end(); ++messageIterator)
with
for (auto &messageIterator : message)
and it works. Then I decided to apply similar approach to this loop
for (auto alphabetIterator = alphabet.begin(), rotorIterator = rotor.begin(); alphabetIterator != alphabet.end(), rotorIterator != rotor.end(); ++alphabetIterator, ++rotorIterator)
and my code looks like this but it doesn't work.
for (auto &alphabetIterator : alphabet, &rotorIterator : rotor)
How do I fix it?

With range-v3, you might do:
std::vector<int> vi{1, 2, 3};
std::vector<std::string> vs{"one", "two", "three"};
for (const auto& [i, s] : ranges::view::zip(vi, vs)) {
std::cout << i << " " << s << std::endl;
}
Demo

Related

std::partition to partition a a vector of string

I am trying to use std::partition to partition a vector into multiple part based on whitespace.
void solution2()
{
std::vector<string> v{ "10","20","","30","40","50","","60","70" };
auto i = begin(v);
while (i != end(v)-1)
{
auto it = std::partition(i, end(v)-1, [](auto empty) {return empty != ""; });
std::copy(i, it, std::ostream_iterator<int>(std::cout, " "));
i = it;
}
}
For example in the above code I want to partition it into multiple and condition to partition is whitespace ""
so the vector v should partition to 3 groups
"10" "20"
"30" "40" "50"
"60" "70"
The problem I am facing is in line
auto it = std::partition(begin(v), end(v)-1, [](string empty) {return empty != ""; });
Error
Severity Code Description Project File Line Suppression State
Error C2679 binary '=': no operator found which takes a right-hand operand of type 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>' (or there is no acceptable conversion) C:\source\repos\out\build\x64-debug\sample C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\include\xutility 3919
Any suggestion what can be changed to fix the error.
Any other efficient way to do the same using std::range or std::view
With C++20, this can be done trivially with ranges::split_view:
std::vector<std::string> v{ "10","20","","30","40","50","","60","70" };
for(auto part : v | std::views::split(""))
{
for(auto num : part) std::cout << num << ',';
std::cout << '\n';
}
Demo
Using std::find to get the next empty string would be more appropriate here.
auto i = begin(v), e = end(v);
while (i != e) {
auto it = std::find(i, e, "");
std::copy(i, it, std::ostream_iterator<std::string>(std::cout, " "));
std::cout << '\n';
if (it == e) break;
i = it + 1;
}

for loop with auto type over vector of vectors

In the new versions of c++ it's very convenient to use auto as type and range base for loops to do
// instead of
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
}
// to do
for (auto vi: v) {
}
How can I use it for vector<vector<int>>?
When I try
for (const auto& vi: vvi) {
}
compilers complains: declaration of variable 'vi' with deduced type 'const auto' requires an initializer.
update:
turned out everything works perfectly, I just made a silly typo and put '&' after a variable name instead of a type for (const auto vi&: vvi); I used & to avoid creation of new variable every iteration of the loop.
You might use 2 for range:
for (const auto& inner: vvi) { // auto is std::vector<int>
for (auto e: inner) { // auto is int
std::cout << e << " ";
}
std::cout << std::endl;
}

Range-based for loop with special case for the first item

I find myself often with code that looks like this:
bool isFirst = true;
for(const auto &item: items)
{
if(!isFirst)
{
// Do something
}
// Normal processing
isFirst = false;
}
It seems that there ought to be a better way to express this, since it's a common pattern in functions that act like a "join".
Maybe a for_first_then_each is what you're looking for? It takes your range in terms of iterators and applies the first function to the first element and the second function to the rest.
#include <iostream>
#include <vector>
template<typename BeginIt, typename EndIt, typename FirstFun, typename OthersFun>
void for_first_then_each(BeginIt begin, EndIt end, FirstFun firstFun, OthersFun othersFun) {
if(begin == end) return;
firstFun(*begin);
for(auto it = std::next(begin); it != end; ++it) {
othersFun(*it);
};
}
int main() {
std::vector<int> v = {0, 1, 2, 3};
for_first_then_each(v.begin(), v.end(),
[](auto first) { std::cout << first + 42 << '\n'; },
[](auto other) { std::cout << other - 42 << '\n'; }
);
// Outputs 42, -41, -40, -39
return 0;
}
You can't know which element you are visiting in a range based for loop unless you are looping over a container like an array or vector where you can take the address of the object and compare it to the address of the first item to figure out where in the container you are. You can also do this if the container provides lookup by value, you can see if the iterator returned from the find operation is the same as the begin iterator.
If you need special handling for the first element then you can fall back to a traditional for loop like
for (auto it = std::begin(items), first = it, end = std::end(items); it != end; ++it)
{
if (it == first)
{
// do something
}
// Normal processing
}
If what you need to do can be factored out of the loop then you could use a range based for loop and just put the processing before the loop like
// do something
for(const auto &item: items)
{
// Normal processing
}
With Ranges coming in C++20, you can split this in two loops:
for (auto const& item : items | view::take(1)) {
// first element only (or never executed if items is empty)
}
for (auto const& item : items | view::drop(1)) {
// all after the first (or never executed if items has 1 item or fewer)
}
If you don't want to wait for C++20, check out range-v3 which supports both of these operations.
This won't work like this with an Input range (like if items is really a range that reads from cin) but will work just fine with any range that is Forward or better (I'm guessing items is a container here, so that should be fine).
A more straightforward version is actually to use enumerate (which only exists in range-v3, not in C++20):
for (auto const& [idx, item] : view::enumerate(items)) {
if (idx == 0) {
// first element only
}
// all elements
}
A fun alternative solution, that I would not use in production without great care, would be to use custom iterator.
int main() {
std::vector<int> v{1,2,3,4};
for (const auto & [is_first,b] : wrap(v)) {
if (is_first) {
std::cout << "First: ";
}
std::cout << b << std::endl;
}
}
A toy implementation could look like this:
template<typename T>
struct collection_wrap {
collection_wrap(T &c): c_(c) {}
struct magic_iterator {
bool is_first = false;
typename T::iterator itr;
auto operator*() {
return std::make_tuple(is_first, *itr);
}
magic_iterator operator++() {
magic_iterator self = *this;
itr++;
//only works for forward
is_first = false;
return self;
}
bool operator!=(const magic_iterator &o) {
return itr != o.itr;
}
};
magic_iterator begin() {
magic_iterator itr;
itr.is_first = true;
itr.itr = c_.begin();
return itr;
}
magic_iterator end() {
magic_iterator itr;
itr.is_first = false;
itr.itr = c_.end();
return itr;
}
T &c_;
};
template<typename Collection>
collection_wrap<Collection>
wrap(Collection &vec) {
return collection_wrap(vec);
}
Check the object address to see if it's the first item:
for(const auto &item: items)
{
if (&item != &(*items.begin())
{
// do something for all but the first
}
// Normal processing
}
An approach still valid in C++ is to use a macro:
#include <iostream>
#include <vector>
#define FOR(index, element, collection, body) { \
auto &&col = collection; \
typeof(col.size()) index = 0; \
for(auto it=col.begin(); it!=col.end(); index++, it++) { \
const auto &element = *it; \
body; \
} \
}
using namespace std;
int main() {
vector<int> a{0, 1, 2, 3};
FOR(i, e, a, {
if(i) cout << ", ";
cout << e;
})
cout << endl;
FOR(i, e, vector<int>({0, 1, 2, 3}), {
if(i) cout << ", ";
cout << e;
})
cout << endl;
return 0;
}
Prints:
0, 1, 2, 3
0, 1, 2, 3
This solution is succinct compared to alternative options. On the downside, index is being tested and incremented on each iteration of the loop - this can be avoided by increasing the complexity of the macro and by using bool first instead of index, but using index in the macro covers more use cases than bool first.
Since C++20, you can slightly improve your range-based for loop by using an init-statement. The init-statement allows you to move your isFirst flag into the scope of the loop so that this flag is no longer visible outside the loop:
std::vector<int> items { 1, 2, 3 };
for(bool isFirst(true); const auto &item: items) {
if(!isFirst) {
std::cout << "Do something with: " << item << std::endl;
}
std::cout << "Normal processing: " << item << std::endl;
isFirst = false;
}
Output:
Normal processing: 1
Do something with: 2
Normal processing: 2
Do something with: 3
Normal processing: 3
Code on Wandbox
I assume you want to know how to retrieve the first element, you could do this with an array and a vector.
I'm going to show the array here.
First include this in your code:
#include <array>
Then convert your array accordingly:
std::array<std::string, 4> items={"test1", "test2", "test3", "test4"};
for(const auto &item: items)
{
if(item == items.front()){
// do something
printf("\nFirst: %s\n", item.c_str()); //or simply printf("\nFirst:"); since you gonna output a double element
}
// Normal processing
printf("Item: %s\n", item.c_str());
}
return 0;
}

How to delete specific elements in a vector using struct data type

i'm new to C++. My program is a quiz game which user can choose category and level for the questions. At first, i use the struct data type
struct QuestionInfo
{
string category;
string level;
string question;
string answer;
};
then
vector<QuestionInfo> vec;
The idea of this part is to store the info of the question include (category, level, question and answer) to each element.
Then after building menu and the output questions UI, i go to the filters
void category_filter()
{
for (unsigned int i = 0; i < vec.size(); i ++)
{
if (category_choice != vec[i].category)
vec.erase(vec.begin() + i );
}
}
Void level_filter()
{
for (unsigned int i = 0; i < vec.size(); i ++)
{
if (level_choice != vec[i].level)
vec.erase(vec.begin() + i );
}
}
So the idea of the filters is to delete the elements which not contain the matched category and level. But the output questions did not match with the category and the level i had choose before. I'm not sure what I'm doing wrong.
Let me explain you the problem with my example. Suppose you have a vector of 10 elements, valid indexes are 0 till 9 elements. You have to erase 5th element i == 4. You erase it, then 6th element with index 5 moves to place of 5th elements with index 4. After that you increase i in for, it becomes 5. Thus you skip previous 6th element, that is now 5th with index 4.
You may fix your code like below, moving i ++ to the condition.
for (unsigned int i = 0; i < vec.size(); ) {
if (category_choice != vec[i].category)
vec.erase(vec.begin() + i );
else
i ++;
}
The preferable solution in C++ way is demonstrated by #Jonathan.
You're getting tripped up by not accounting for the indexing shift that occurs when you erase an element. I personally would rely on remove_if and erase with a lambda to accomplish this:
vec.erase(remove_if(begin(vec), end(vec), [&](const auto& i) { return category_choice != i.category; }, end(vec));
vec.erase(remove_if(begin(vec), end(vec), [&](const auto& i) { return level_choice != i.level; }, end(vec));
Alternatively you might consider combining them for a bit of speed improvement:
vec.erase(remove_if(begin(vec), end(vec), [&](const auto& i) { return category_choice != i.category || level_choice != i.level; }, end(vec));
You might want to remove_if + erase:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int main()
{
struct QuestionInfo
{
std::string category;
std::string level;
std::string question;
std::string answer;
QuestionInfo(std::string category, std::string level, std::string question, std::string answer) :
category(category), level(level), question(question), answer(answer) {}
};
std::vector<QuestionInfo> vec;
std::string category_choice = "cat1";
std::string level_choice = "lev1";
vec.push_back(QuestionInfo("cat1", "lev1", "q1", "a1"));
vec.push_back(QuestionInfo("cat1", "lev2", "q2", "a2"));
vec.push_back(QuestionInfo("cat2", "lev1", "q3", "a3"));
vec.push_back(QuestionInfo("cat2", "lev2", "q4", "a4"));
std::cout << "\nNot filered" << std::endl;
for (auto const &info : vec)
std::cout << "Category:" << info.category << " Level:" << info.level << std::endl;
auto filter_category = std::remove_if(vec.begin(), vec.end(), [&](auto const &info) {return category_choice != info.category; });
vec.erase(filter_category, vec.end());
std::cout << "\nFilered by category" << std::endl;
for (auto const &info : vec)
std::cout << "Category:" << info.category << " Level:" << info.level << std::endl;
auto filter_level = std::remove_if(vec.begin(), vec.end(), [&](auto const &info) {return level_choice != info.level; });
vec.erase(filter_level, vec.end());
std::cout << "\nFiltered by level" << std::endl;
for (auto const &info : vec)
std::cout << "Category:" << info.category << " Level:" << info.level << std::endl;
system("pause");
return 0;
}
As mentioned by others, the remove_if + erase is a standard and expressive way to achieve what you want. But you may also consider non-destructive filtering with a copy_if into a new container, or even without using any additional storage with Boost.Range adaptor boost::adaptors::filtered or boost::filter_iterator. Look here for examples.

How can I check if I'm on the last element when iterating using foreach syntax [duplicate]

This question already has answers here:
How can I print a list of elements separated by commas?
(34 answers)
Closed 7 years ago.
For example:
for( auto &iter: item_vector ) {
if(not_on_the_last_element) printf(", ");
}
or
for( auto &iter: skill_level_map ) {
if(not_on_the_last_element) printf(", ");
}
You can't really. That's kind of the point of range-for, is that you don't need iterators. But you can just change your logic on how you print your comma to print it if it's not first:
bool first = true;
for (auto& elem : item_vector) {
if (!first) printf(", ");
// print elem
first = false;
}
If that's the intent of the loop anyway. Or you could compare the addresses:
for (auto& elem : item_vector) {
if (&elem != &item_vector.back()) printf(", ");
// ...
}
There's no great method. But if we have easy access to the last element of the container...
std::vector<int> item_vector = ...;
for (auto & elem : item_vector) {
...
if (&elem != &item_vector.back())
printf(", ");
}
These type of loops are best written using the "Loop and a Half" construct:
#include <iostream>
#include <vector>
int main()
{
auto somelist = std::vector<int>{1,2,3,4,5,6,6,7,8,9,6};
auto first = begin(somelist), last = end(somelist);
if (first != last) { // initial check
while (true) {
std::cout << *first++;
if (first == last) break; // check in the middle
std::cout << ", ";
}
}
}
Live Example that prints
1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 6
i.e. without a separator at the end of the last element.
The check in the middle is what makes this different from do-while (check up front) or for_each / range-based for (check at the end). Trying to force a regular for loop on these loops will introduce either extra conditional branches or duplicate program logic.
This is like a State Pattern.
#include <iostream>
#include <vector>
#include <functional>
int main() {
std::vector<int> example = {1,2,3,4,5};
typedef std::function<void(void)> Call;
Call f = [](){};
Call printComma = [](){ std::cout << ", "; };
Call noPrint = [&](){ f=printComma; };
f = noPrint;
for(const auto& e:example){
f();
std::cout << e;
}
return 0;
}
Output:
1, 2, 3, 4, 5
The first time through f points to noPrint which only serves to make f then point to printComma, so commas are only printed before the second and subsequent items.
store this code away safely in a header file in your little bag of utilities:
namespace detail {
template<class Iter>
struct sequence_emitter
{
sequence_emitter(Iter first, Iter last, std::string sep)
: _first(std::move(first))
, _last(std::move(last))
, _sep(std::move(sep))
{}
void write(std::ostream& os) const {
bool first_element = true;
for (auto current = _first ; current != _last ; ++current, first_element = false)
{
if (!first_element)
os << _sep;
os << *current;
}
}
private:
Iter _first, _last;
std::string _sep;
};
template<class Iter>
std::ostream& operator<<(std::ostream& os, const sequence_emitter<Iter>& se) {
se.write(os);
return os;
}
}
template<class Iter>
detail::sequence_emitter<Iter>
emit_sequence(Iter first, Iter last, std::string separator = ", ")
{
return detail::sequence_emitter<Iter>(std::move(first), std::move(last), std::move(separator));
}
then you can emit any range of any container without a trailing separator like this:
vector<int> x { 0, 1, 2, 3, 4, 5 };
cout << emit_sequence(begin(x), end(x)) << endl;
set<string> s { "foo", "bar", "baz" };
cout << emit_sequence(begin(s), end(s), " comes before ") << endl;
expected output:
0, 1, 2, 3, 4, 5
bar comes before baz comes before foo
Range based for loop are made to iterate over the whole range. If you do not want that, why don't you just do a regular for loop?
auto end = vector.end() - 1;
for (auto iter = vector.begin(); iter != end; ++iter) {
// do your thing
printf(", ");
}
// do your thing for the last element
If you do not want to repeat the code twice to "do your thing", as I would, then create a lambda that does it a call it:
auto end = vector.end() - 1;
// create lambda
for (auto iter = vector.begin(); iter != end; ++iter) {
lambda(*iter);
printf(", ");
}
lambda(vector.back());