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.
Related
Is there a standard algorithm in the library that does the job of the following for-loop?
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
int main( )
{
const char oldFillCharacter { '-' };
std::vector<char> vec( 10, oldFillCharacter ); // construct with 10 chars
// modify some of the elements
vec[1] = 'e';
vec[7] = 'x';
vec[9] = '{';
const char newFillCharacter { '#' };
for ( auto& elem : vec ) // change the fill character of the container
{
if ( elem == oldFillCharacter )
{
elem = newFillCharacter;
}
}
// output to stdout
std::copy( std::begin( vec ), std::end( vec ),
std::ostream_iterator<char>( std::cout, " " ) );
std::cout << '\n';
/* prints: # e # # # # # x # { */
}
I want to replace the above range-based for-loop with a one-liner if possible. Is there any function that does this? I looked at std::for_each but I guess it's not suitable for such a scenario.
This loop will replace every occurrence of oldFillCharacter with newFillCharacter. If you don't want to do something more fancy std::replace looks good:
std::replace(std::begin(vec), std::end(vec), oldFillCharacter, newFillCharacter);
Or a bit simpler with std::ranges::replace:
std::ranges::replace(vec, oldFillCharacter, newFillCharacter);
You can use std::for_each.
std::for_each(vec.begin(), vec.end(), [](char& elem) {
if ( elem == oldFillCharacter ) elem = newFillCharacter;
});
std::replace(vec.begin(), vec.end(), '_', '#');
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 >( ) );
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
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 );