I need to test to see if the number of extracted strings from a string_view is equal to a specific number (e.g. 4) and then execute some code.
This is how I do it:
#include <iostream>
#include <iomanip>
#include <utility>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <iterator>
int main( )
{
const std::string_view sv { " a 345353d& ) " }; // a sample string literal
std::stringstream ss;
ss << sv;
std::vector< std::string > foundTokens { std::istream_iterator< std::string >( ss ),
std::istream_iterator< std::string >( ) };
if ( foundTokens.size( ) == 4 )
{
// do some stuff here
}
for ( const auto& elem : foundTokens )
{
std::cout << std::quoted( elem ) << '\n';
}
}
As can be seen, one of the downsides of the above code is that if the count is not equal to 4 then it means that the construction of foundTokens was totally wasteful because it won't be used later on in the code.
Is there a way to check the number of std::strings stored in ss and then if it is equal to a certain number, construct the vector?
NO, a stringstream internally is just a sequence of characters, it has no knowledge of what structure the contained data may have. You could iterate the stringstream first and discover that structure but that wouldn't be any more efficient than simply extracting the strings.
You can do it something like the following
#include <iterator>
//...
std::istringstream is( ss.str() );
auto n = std::distance( std::istream_iterator< std::string >( is ),
std::istream_iterator< std::string >() );
After that comparing the value of the variable n you can decide whether to create the vector or not.
For example
std::vector< std::string > foundTokens;
if ( n == 4 ) foundTokens.assign( std::istream_iterator< std::string >( ss ), std::istream_iterator< std::string >( ) );
Related
I want to extract a maximum of N + 1 strings from a std::stringstream.
Currently, I have the following code (that needs to be fixed):
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <iterator>
#include <ranges>
#include <algorithm>
int main( )
{
const std::string_view sv { " #a hgs -- " };
const size_t expectedTokenCount { 4 };
std::stringstream ss;
ss << sv;
std::vector< std::string > foundTokens;
foundTokens.reserve( expectedTokenCount + 1 );
std::ranges::for_each( std::ranges::take_view { ss, expectedTokenCount + 1 }, [ &foundTokens ]( const std::string& token )
{
std::back_inserter( foundTokens );
} );
if ( foundTokens.size( ) == expectedTokenCount )
{
// do something
}
for ( const auto& elem : foundTokens )
{
std::cout << std::quoted( elem ) << '\n';
}
}
How should I fix it? Also, how should I use back_inserter to push_back the extracted strings into foundTokens?
Note that the following aliases are in effect:
namespace views = std::views;
namespace rng = std::ranges;
There are a few issues and oddities here. First of all:
std::ranges::take_view { ss, expectedTokenCount + 1 }
It's conventional to use the std::views API:
ss | views::take(expectedTokenCount + 1)
The more glaring issue here is that ss is not a view or range. You need to create a proper view of it:
auto tokens = views::istream<std::string>(ss) | views::take(expectedTokenCount + 1);
Now for the other issue:
std::back_inserter( foundTokens );
This is a no-op. It creates a back-inserter for the container, which is an iterator whose iteration causes push_back to be called, but doesn't then use it.
While the situation is poor in C++20 for creating a vector from a range or view, here's one way to do it:
rng::copy(tokens, std::back_inserter(foundTokens));
Putting this all together, you can see a live example, but note that it might not be 100% correct—it currently compiles with GCC, but not with Clang.
As noted below, you can also make use of views::split to split the source string directly if there's a consistent delimiter between the tokens:
std::string_view delim = " ";
auto tokens = views::split(sv, delim);
However, you might run into trouble if your standard library hasn't implemented this defect report.
I have a vector of char which looks something like
C:/Users/person/Desktop/Albedo.pngC:/Users/person/Desktop/Metallic.pngC:/Users/person/Desktop/Noice.pngC:/Users/person/Desktop/AO.png
How do I split the vector to individual paths?
That is, I want to have
std::string path1; // = C:/Users/person/Desktop/Albedo.png;
std::string path2; // = C:/Users/person/Desktop/Metallic.png;
std::string path3; // = C:/Users/person/Desktop/Noice.png;
std::string path4; // = C:/Users/person/Desktop/AO.png;
Any idea how can I do that?
Thanks in advance!
For example a straightforward approach provided that each path in the vector has the extension .png can look for example the following way as it is shown in the demonstrative program below.
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
#include <cstring>
int main()
{
const char *s ="C:/Users/person/Desktop/Albedo.png"
"C:/Users/person/Desktop/Metallic.png"
"C:/Users/person/Desktop/Noice.png"
"C:/Users/person/Desktop/AO.png";
std::vector<char> v( s, s + std::strlen( s ) );
std::vector<std::string> paths;
const char *ext = ".png";
size_t n = std::strlen( ext );
for ( auto first = std::begin( v ), last = std::end( v ), it = first;
( it = std::search( first, last, ext, ext + n ) ) != last;
first = it
)
{
std::advance( it, n );
paths.push_back( { first, it } );
}
for ( const auto &path : paths )
{
std::cout << path << '\n';
}
return 0;
}
The program output is
C:/Users/person/Desktop/Albedo.png
C:/Users/person/Desktop/Metallic.png
C:/Users/person/Desktop/Noice.png
C:/Users/person/Desktop/AO.png
I tried to write programm inserting elements into a vector, sorting them in alphabetical order. The element before to be inserted compares with another ones till it more than element already inserted. After it was assumed to add compared element using .insert(). I want to realize it without using sort algorithmes.
std::string name;
std::vector<std::string> students;
std::vector<std::string>::iterator beg = students.begin();
while (std::cin>>name){
for (std::vector<std::string>::iterator e = students.end() ; beg !=e ; ) {
if (!name.compare(*beg))
{
students.insert(beg, name);
break;
}
else
beg++;
}
}
To avoid invalidate of iterator pointed to the last element I renew it each iteration.
The problem is after this part of code I check the vector but it's empty.
This comparison
if (!name.compare(*beg))
does not make sense. It checks only that two strings are equal.
Consider for example the following code snippet
std::string s1 = "one";
std::string s2 = "one";
std::cout << !s1.compare( s2 ) << '\n';
Its output is 1. It means that the two objects are equal.
Moreover the for loop can ends without finding the position where a string can be inserted for example when initially the vector is empty.
And this statement
std::vector<std::string>::iterator beg = students.begin();
must be inside the outer while loop. That is the iterator shall be initialized anew in each iteration of the loop.
Here is a demonstrative program that shows how the inner loop can be implemented.
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
void insert( std::vector<std::string> &v, const std::string &s )
{
auto it = std::begin( v );
while ( it != std::end( v ) && not( s < *it ) ) ++it;
v.insert( it, s );
}
int main()
{
std::string names[] = { "One", "Two", "Three" };
std::vector<std::string> v;
for ( const auto &s : names )
{
insert( v, s );
}
for ( const auto &s : v ) std::cout << s << ' ';
std::cout << '\n';
return 0;
}
The program output is
One Three Two
That is the strings are inserted in the ascending order.
Relative to your code snippet the loops can look like
while ( std::cin >> name )
{
auto it = std::begin( students ); // or students.begin()
while ( it != std::end( students ) && not( name < *it ) ) ++it;
students.insert( it, name );
}
Also instead of the inner while loop you could use the standard algorithm std::find_if. For example
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <iterator>
#include <algorithm>
//...
while ( std::cin >> name )
{
using namespace std::placeholders;
auto it = std::find_if( std::begin( students ), std::end( students ),
std::bind( std::greater_equal<>(), _1, name ) );
students.insert( it, name );
}
For an empty vector begin and end are the same, hence you never insert anything.
It is not clear why you do not want to use a sorting algorithm, hence I would propose the following:
std::string name;
std::vector<std::string> students;
while (std::cin>>name){
students.push_back(name);
}
std::sort(students.begin(),students.end());
Alternatively, replace the last line with your favourite sorting routine.
Perhaps anyone have an efficient way to remove consecutive duplications of specific characters preferably using built-in string operations, and without explicitly going through the string characters.
For example, when I have wildcard pattern and I'd like to remove consecutive asterisks only (*)
/aaaa/***/bbbb/ccc/aa/*****/dd --> /aaaa/*/bbbb/ccc/aa/*/dd
For all characters repetitive duplications I can use std::unique in the following manner :
str.erase( std::unique(str.begin(), str.end()), str.end());
but what about specific chars only.
You can use the same algorithm std::unique with a lambda expression.
For example
#include <iostream>
#include <string>
#include <functional>
#include <iterator>
#include <algorithm>
int main()
{
std::string s = "/aaaa/***/bbbb/ccc/aa/*****/dd";
char c = '*';
s.erase( std::unique( std::begin( s ), std::end( s ),
[=]( const auto &c1, const auto &c2 ) { return c1 == c && c1 == c2; } ),
std::end( s ) );
std::cout << s << '\n';
}
The program output is
/aaaa/*/bbbb/ccc/aa/*/dd
Or you can remove a set of duplicate characters. For example
#include <iostream>
#include <string>
#include <functional>
#include <iterator>
#include <algorithm>
#include <cstring>
int main()
{
std::string s = "/aaaa/***/bbbb/ccc/aa/*****/dd";
const char *targets = "*b";
auto remove_chars = [=]( const auto &c1, const auto &c2 )
{
return strchr( targets, c1 ) && c1 == c2;
};
s.erase( std::unique( std::begin( s ), std::end( s ), remove_chars ),
std::end( s ) );
std::cout << s << '\n';
}
The program output is
/aaaa/*/b/ccc/aa/*/dd
In the last example I suppose that the character '\0' is not included in the string. Otherwise you have to add one more subexpression to the logical expression in the lambda.
sorry, for my bad english
i have char *c and I need insert in "i" position in a vector <string>
Can someone help?
char * can be (implicitly) converted to std::string, then insert it into proper position:
vec.insert( vec.begin() + i, std::string( c ) );
of course you need to be sure that vec size is bigger or equal than i. Details can be found in documentation
To insert something in a vector you should use one of its methods insert. For example
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
int main()
{
std::vector<std::string> v = { "Christian", "Assis" };
const char *s = "Hello";
size_t i = 0;
v.insert( std::next( v.begin(), i ), s );
for ( const auto &s : v ) std::cout << s << ' ';
std::cout << std::endl;
return 0;
}
The program output is
Hello Christian Assis
You can add a check whether the value of the position i is less than or equal to the number of elements in the vector.
For example
v.insert( std::next( v.begin(), v.size() < i ? v.size() : i ), s );