Zawartość artykułu
Intro
W C++ posiadamy standardowo dostępne pewne operacje na tekście (w pierwszej chwili nasuwa się klasa
std::string), są również dostępne funkcje operujące na łańcuchach znakowych z języka C. Niestety przy bardziej skomplikowanej pracy z tekstem standardowo dostępne metody okazują się niewystarczające.
Postaram się przybliżyć w tym artykule ponad-C++-standardowe operacje na tekście, często znane z innych języków programowania, których niestety nie ma w standardzie języka C++, natomiast są dostępne w bibliotece boost. W artykule tym pominę tematykę wyrażeń regularnych, które są dobrze opisane
na tym serwisie.
Dodam jeszcze że opis funkcji jest przedstawiony bez poprzedzania każdej możliwej funkcji, czy obiektu, przestrzenią nazw, w której dana funkcja, czy obiekt, się znajduje, dlatego kopiując z artykułu fragmenty kodu trzeba o tym pamiętać (na szczęście zdecydowana większość funkcji z biblioteki boost, które używam znajduje się w przestrzeni nazw
boost::algorithm
Funkcje i obiekty operujące na tekście dostępne w standardzie
Zanim przejdę do opisywania biblioteki boost i dostępnych w niej operacji tekstowych warto poświęcić parę chwil na przypomnienie i przeanalizowanie tego co mamy dostępne w standardzie, oraz co możemy w łatwy sposób dzięki temu osiągnąć, a także czego nam najbardziej brakuje brakuje.
Powtórzenie funkcji z std::string
Artykuł jest nastawiony pod C++, dlatego zacznę od powtórzenia możliwości standardowych funkcji klasy
#include<string>
.
std::string jest klasą przechowywującą w sobie łańcuch znaków typu char zmiennej długości, posiada również metody pomagające w częstej pracy z tekstem takie jak konkatenacja (łączenie napisów), wyszukiwanie itp.
Jako ciekawostkę można dodać że klasa string to tak naprawdę:
typedef basic_string<char> string; dzięki temu gdy będziemy chcieli operować na innych typach znakowych niż char (np. wchar_t) będziemy mogli dokonać tego z taką samą łatwością jak czynimy to w znanym nam C++-sowym
std::string.
Metody klasy std::string
Klasa string jest dobrze opisana w Synfonii C++ z mnóstwem przykładów, nie ma sensu abym powtarzał coś co jest już świetnie zrobione, tak samo nie ma sensu abym przepisywał dokumentację, więc zrobię mini przypomnienie, oraz opiszę pewne rzeczy, które nie były w Symfonii opisane.
Konstruktory -wyjątkowo szczegółowo je opiszęexplicit string();
string( const string & str );
string( const string & str, size_t kopiuj_od, size_t dlugosc_do_skopiowania = npos ) throw( out_of_range );
string( const char * s );
string( const char * s, size_t N );
string( size_t N, char c );
template < class InputIterator >
string( InputIterator first, InputIterator last );
string( initializer_list < char > il );
string( string && str ) noexcept;
Teraz opisowy przegąd funkcjonalności klasy std::stringMając już skonstruowany string możemy przypisać mu nową zawartość na różne sposoby przy użyciu
operatora=, lub metody
assign(). (*Należy pamiętać że wywołując obiekt2 = obiekt nie zawsze jest wołany operator=). Możemy też w łatwy sposób dodawać na koniec istniejącego stringa nowe znaki
operator+=, oraz
string::append(), a także
push_back(). Metoda
insert() pozwala wstawić pewne znaki w dowolnym miejscu, analogicznie do tej metody możemy usunąć jeden lub więcej znaków z dowolnej pozycji metodą
erase(), z tymi dwoma metodami ma coś wspólnego
replace(), które zastępuje dany ciąg znaków innym ciągiem znaków, długość tych dwóch ciągów znaków może się różnić. Gdy chcemy możemy wyczyścić całą zawartość metodą
clear(), aby sprawdzić że string jest wtedy pusty mamy metodę
empty() zwracająca true w przypadku pustego stringa. Skoro mowa o usuwaniu: standard C++11 dostarczył nam metodę
pop_back() usuwającą ostatni lement stringa. Do znaków w klasie string możemy się odnosić tak jak w zwykłej tablicy znakowej, jednak oprócz tego mamy metodę
at(), która w przeciwieństwie do zwykłego
operatora[] w razie sięgnięcia poza zakres wyrzuci
out_of_range. Nowy standard dostarczył nam również kolejnych metod na dostanie się do pierwszego (metoda
front()) i ostatniego (metoda
back()) znaku, jednak w przypadku pustego stringa grozi nam niezdefiniowane zachowanie. Mając dwa różne stringi możemy zamienić je zawartością przy pomocy globalnej metody
swap(). Mając instancje stringa można bez problemu utworzyć nowego stringa zawierającego fragment źródłowego napisu przy pomocy metody
substr().
Klasa std::string pamięta rozmiar i długość ciągu znaków, są one dostępne po wywołaniu funkcji odpowiednio
size() i
lenght(), jednak zwracają one taką sama wartość. Sprawdzić możemy sobie również maksymalną możliwą długość stringa, wystarczy wywołać metodę
max_size(). Poza sprawdzaniem obecnej długości i maksymalnej długości możemy ją ustawić metodą
resize(), dzięki temu możmy zarówno przyciąć nasz string, jak i rozszerzyć go. Jak wiemy w przypadku tablicy mamy pewną zaalokowaną przestrzeń (std::string ma metodę
capacity() informującą ile na chwile obecną zaalokowaliśmy), możemy zmienić ta zaalokowaną przestrzeń metodą
reserve(). W temacie zmiany zaalokowanej pamięci dodam że nowy standard dorzucił metodę
shrink_to_fit(), która umożliwia zredukowanie zaalokowanej pamięci tak aby optymalnie mieściła się aktualna zawartość danego stringa.
Czasami bywa że chcemy dostać się do tablicy znaków wewnątrz instancji std::string, mamy taką możliwość dzięki funkcji
data(), niestety ta funkcja może zwrócić const char * niezakończony NULL, więc aby zwrócić pełnowartościowy cstring posłuży nam metoda
c_str(). Niestety te funkcje czasami nie wystarczą, gdyż zwracają const, więc aby operować modyfikująco na tekście musimy sobie skopiować do tablicy za pomocą pomocnej
copy().
Klasa std::string posiada jeszcze pewne dodatkowe metody ułatwiające nam prace z nią, mam na myśli
operator>> i
operator<<, dzięki którym bez problemu możemy operować na strumieniach z/do stringa. Łatwe zczytywanie ze strumieni jest możliwe dzięki globalnej metodzie
getline(), dzięki której możemy wczytać do stringa całą linię lub ciąg znaków do określonego znaku. Wcześniej wspomniałem o
operatorze+=, jest również dostępny globalny operator+, dzięki czemu z lewej strony równania nie musi stać instancja stringa, lecz może również znak i łańcuch znakowy.
Klasa <string> zawiera też wszystkie potrzebne operatory porównujące (
==, !=, <, <=, >, >=) między innym z innym stringiem, a nawet między łańcuchem znaków, są one globalne więc porównywanie może odbywać się z obydwu stron. Do porównywanie mamy też dostępną metodę
compare(), która daje nam możliwość porównywania fragmentów stringów/łańcuchów znakowych. Oprócz porównywania mamy też
funkcje wyszukujące; do wyszukiwania służy metoda
find() , która zwraca pozycje pierwszego wystąpienie szukanego wzorca,
rfind() dla odmiany wyszukuje ostatnie wystąpienie; gdy chcemy wyszukać pozycję pierwszego/ostatniego wystąpienia dowolnej litery z danego ciągu możemy użyć
find_first_of() lub
find_last_of(), analogicznie możmy chcieć znalezienia pierwszej pozycji, która nie należy do ciągu znaków, mamy wtedy pomocne
find_first_not_of() i
find_last_not_of(). W przypadku gdy funkcje wyszukujące nie znajdą tego czego szukały zwrócą pozycję
string::npos.
Poza tym wszystkim w klasie std::string są dostępne równiez iteratory zarówno od początku do końca (
string::begin() i
string::end()), od końca do początku (
string::rbegin() i
string::rend()), dzięki standardowi C++11 mamy jeszcze iteratory stałe od początku (
string::cbegin() i
string::cend()), oraz dla równowagi iteratory stałe od końca (
string::crbegin() i
string::crend()).
Mogą się pojawić wyjątki w niektórych metodach z std::string.
length_error gdy wynikowy string będzie miał większą niż maksymalna dozwolona dla stringa długość string::max_size(). Gdy pojawią się problemy z alokacją możliwy jest wyjątek:
bad_alloc. W sytuacji gdy pozycja rozpoczęcia kopiowania będzie większa niż długość tego stringa/ łańcucha znakowego, lub gdy metodą at() odniesiemy się poza zakres stringa zostanie wyrzucony:
out_of_range.
Nowe rzeczy z C++11 dostępne w nagłówku<string>, ale poza klasą std::string
W tym właśnie momencie dobrze byłoby wymienić pewne nowe, lecz jakże przydatne, rzeczy dostępne po włączeniu
#include <string>
, są one dostępne w standardzie C++11.
int stoi( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 );
long stol( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 );
unsigned long stoul( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 );
float stof( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 );
double stod( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 );
long double stold( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 );
poza tym każda z tych funkcji ma przeładowaną wersję dla
std::wstringaBardzo przydatne są jeszcze kolejne funkcje z C++11:
template < typename TypLiczby >
string to_string( TypLiczby liczba );
template < typename TypLiczby >
wstring to_wstring( TypLiczby liczba );
zamieniają one argument będący liczbą z typów wbudowanych na odpowiednio string lub wstring.
Przydatne funkcje z języka C
Nagłówek #include<cstring>
W języku C w
#include<cstring>
jest wiele funkcji do przetwarzania tekstu, które po zapoznaniu z możliwościami std::string nie wprowadzają nic innego, są jednak 2 funkcje dające pewne, dodatkowe możliwości, a mianowicie:
size_t strspn( const char * str1, const char * str2 );
char * strtok( char * napis, const char * ciag_ogranicznikow );
vector < string > split( char * napis, const char * ograniczniki = "\n\t " ) {
vector < string > podzielony_napis;
for( char * pch = strtok( napis, ograniczniki ); pch != NULL; pch = strtok( NULL, ograniczniki ) )
podzielony_napis.push_back( pch );
return podzielony_napis;
}
Nagłówek #include<cctype> -pomocnicze funkcje operujące na pojedyńczych znakach
Z języka C bardzo przydatne są funkcje z biblioteki
#include<cctype>
, opis tych funkcji jest dostępny w ramach
tego serwisu.
Są to głównie funkcje sprawdzające czy podany w argumencie znak jest pewnego rodzaju, np. czy jest liczbą. W tej bibliotece są również takie funkcje jak
int tolower(int c); i
int toupper(int c);, które zmieniają podaną w argumencie literę odpowiednio na małą lub dużą.
Nagłówek #include<cstdlib> -przydatne funkcje konwertujące napis na liczbę
Przydatnymi rzeczami z języka C, dostępnymi w
#include<cstdlib>
są funkcje konwertujące napis na liczbę odpowiedniego typu, np:
int atoi( const char * napis );
double atof( const char * napis );
long int atol( const char * str );
long long int atoll( const char * str );
działają one w taki sposób że pomijają od lewej strony tak dużo białych znaków jak się tylko da, potem obejmują tyle znaków liczbowych ile zdołają i konwertują na liczbę, w przypadku niepowodzenia zwracają 0, co może być niepożądanie dwuznaczne.
W tej samej bibliotece mamy jeszcze podobne do powyższych funkcji konwertujących:
double strtod( const char * str, char ** endptr );
unsigned long int strtoul( const char * str, char ** endptr, int base );
unsigned long int strtoul( const char * str, char ** endptr, int base );
Standard C++11 dodał parę analogicznych do powyższych funkcji w nagłówku
#include<cstdlib>
:
Strumienie piszące z i do std::string dostępne w C++
W temacie przypomnienia warto wspomnieć o C++-owym sposobie łatwego tworzenia napisów ze zmiennych:
std::ostringstream - przy użyciu operatora<< "tworzy" obiekt std::string, do którego można się dostać metodą:
string ostringstream::str(), przykład:
std::ostringstream os;
os << napis1 << '\t' << liczba << '\t' << obiekt << endl;
cout << os.str();
W drugą stronę mamy
std::istringstream, który przy użyciu operatora>> zapisuje do zmiennych zawartość z obiektu
std::string, przykład:
std::istringstream is( " 10 czesc 100.1" );
is >> liczba_calkowita >> tablica_znakowa >> liczba_zmiennoprzecinkowa;
Funkcjonalności ostringstream i istringstream są połączone w klasie
std::stringstream.
Algorytmy uogólnione zaimplementowane w C++ zastosowane do napisów
Algorytmy uogólnione, dostępne w
#include<algorithm>
umożliwiają pewne, przydatne operacje na kolekcjach przy użyciu iteratorów, mogą one posłużyć do uzupełnienia braku przydatnej funkcjonalności klasy std::string. Tutaj zamieszczam przykłady, które przyszły mi do głowy. Myślę że dobrym rozwiązaniem jest przedstawienie tych przykładów w rodzaju problem-proponowane rozwiązanie. Zależy mi tutaj na przedstawieniu łatwości z jaką dzięki algorytmom uogólnionym możemy ułatwić sobie operowanie na tekście wg zapotrzebowania, a nie na napisaniu wszystkich funkcji tekstowych, których ktokolwiek będzie kiedykolwiek potrzebował.
Zanim zaczniemy dodam pewne wyjaśnienie błędu, który może nam zasygnalizować kompilator, gdy spróbujemy używać funkcji z języka C w algorytmach uogólnionych w "czysty soposób"; komunikat kompilatora może mieć taką formę:
error: no matching function for call to ‘algorytm_jakis(std::basic_string<char>::iterator, std::basic_string<char>::iterator, <unresolved overloaded function type>)’ note: candidate is: /usr/include/c++/4.6/bits/stl_algo.h:4419:5: note: template<class _IIter, class _Predicate> _IIter ‘algorytm_jakis(_IIter, _IIter, _Predicate) z czymś takim poradzimy sobie mówiąc kompilatorowi, że o to nam chodzi; aby tego dokonać na funkcji int f( int ); możemu:
|
Przykładowe problemy z przykładowymi rozwiązaniami
1. Sprawdzenie czy napis (z założenia długi) zawiera jakieś znaki interpunkcyjne.
Przykładowe rozwiązanie:
bool hasPunct( string & s )
{
return find_if( s.begin(), s.end(), ptr_fun < int, int >( ispunct ) ) != s.end();
}
2. Zliczenie wszystkich liter 'a' w napisie.
rozwiązanie:
string s( "Ciekawe ile tutaj jest perwszych liter alfabetu?" );
cout << count( s.begin(), s.end(), 'a' ) << endl;
3. Zliczenie cyfr w napisie:
Przykładowe rozwiązanie:
unsigned digitsPerString( string & s )
{
return count_if( s.begin(), s.end(), ptr_fun < int, int >( isdigit ) );
}
4. Sprawdzenie czy zawartość napisu się wydrukuje w całości na ekranie:
Przykładowe rozwiązanie:
bool isPrintable( string & s )
{
return find_if_not( s.begin(), s.end(),[]( int i ) { return isprint( i ); } ) == s.end();
}
5. Zmiana wszystkich liter w stringu na duże/małe.
Przykładowe rozwiązanie:
string to_lower( string napis )
{
transform( napis.begin(), napis.end(), napis.begin(),::tolower );
return napis;
}
string to_upper( string napis )
{
transform( napis.begin(), napis.end(), napis.begin(),::toupper );
return napis;
}
6. Zamiana WSZYSTKICH liter 'ą' na 'a', string::replace() zastępuje jedynie pierwsze wystąpienie:
proponowane rozwiazanie:
wstring replaceOccurencesOfTheLetter( wstring ws, wchar_t letter_to_replace, wchar_t letter_instead )
{
replace( ws.begin(), ws.end(), letter_to_replace, letter_instead );
return ws;
}
wstring ws( L"Tą jesianią grzmiące niebo ..." );
wcout << ws << endl;
wcout << replaceOccurencesOfTheLetter( ws, L'ą', L'a' ) << endl;
7. Zastąpienie wszystkich białych znaków na spacje.
proponowane rozwiazanie:
string whitespacesToSpaces( string s )
{
replace_if( s.begin(), s.end(),::isspace, ' ' );
return s;
}
8. Czy każdy znak w całej zawartości std::string odpowiada podanej funkcji -ogólny adapter funkcji z cctype na cały std::string:
proponowane rozwiązanie (przy używaniu szablonów nie musimy używać ani rzutowań ani ptr_fun<>):
template < int( * F )( int ) >
bool isLike( const string & s )
{
return count_if( s.begin(), s.end(), F ) == s.size();
}
proponowane rozwiązanie 2 (dla zakresu od-do):
template < int( * F )( int ) >
bool isLike( const string & s, unsigned pos_from, unsigned pos_to )
{
return count_if( s.c_str() + pos_from, s.c_str() + pos_to, F ) == pos_to - pos_from;
}
9. Czy std::string zaczyna/kończy się stringiem:
podpowiedź: standardowe operacje klasy std::string wystarczą na 1 linię kodu, lub użycie ... find_end(...)
|
przykładowe rozwiązanie:
bool startsWith( const string & s, const string & prefix )
{
return s.find( prefix ) == 0;
}
bool endsWith( const string & s, const string & suffix )
{
return s.rfind( suffix ) ==( s.size() - suffix.size() );
}
przykładowe rozwiązanie 2 (ignorując białe znaki):
bool compareCaseInsensitive( char a, char b )
{
return tolower( a ) == tolower( b );
}
bool endsWithIgnoreCases( const string & s, const string & suffix )
{
return std::find_end( s.begin(), s.end(), suffix.begin(), suffix.end(), compareCaseInsensitive ) != s.end();
}
bool startsWithIgnoreCases( const string & s, const string & suffix )
{
return std::find_end( s.rbegin(), s.rend(), suffix.rbegin(), suffix.rend(), compareCaseInsensitive ) != s.rend();
}
10. Numer pozycji w dwóch instancjach std::string, na której się różnią znakami:
proponowane rozwiązanie (gdy wiemy że dwa napisy są różne i że mają taką samą długość):
unsigned positionOfMismatch( const string & s1, const string & s2 )
{
if( s1.size() != s2.size() )
throw invalid_argument( "s1.size() != s2.size(), but should be equal" );
return mismatch( s1.c_str(), s1.c_str() + s1.size(), s2.c_str() ).first - s1.c_str();
}
11. Przycinanie znaków (operacja trim):
Poniższe, przykładowe rozwiązanie jest rozwiązaniem ze strony:
http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstringnapisanym przez użytkownika Evan Teran dnia 20 października 2008 roku o 5:46, zamieszczam je ponieważ jest to najlepsze rozwiązanie z pul mojego wymyślania i znalezionych w internecie. Zamieszczam to na prawie cytatu, zgodnie z jego warunkami(
http://pl.wikipedia.org/wiki/Prawo_cytatu):
static inline std::string & ltrim( std::string & s ) {
s.erase( s.begin(), std::find_if( s.begin(), s.end(), std::not1( std::ptr_fun < int, int >( std::isspace ) ) ) );
return s;
}
static inline std::string & rtrim( std::string & s ) {
s.erase( std::find_if( s.rbegin(), s.rend(), std::not1( std::ptr_fun < int, int >( std::isspace ) ) ).base(), s.end() );
return s;
}
static inline std::string & trim( std::string & s ) {
return ltrim( rtrim( s ) );
}
12. Sprawdzenie czy cały string składa się z ciągu takich samich znaków (np: "aaaa", ale już nie: "aab")
przykładowe rozwiązanie:
bool hasLineTheSameSymbols( const string & line )
{
return count_if( line.begin(), line.end(), bind2nd( equal_to < char >(), line[ 0 ] ) ) == line.size();
}
13. Hardcorowy przykład na zakończenie -wstawienie między każdą literę stringa podanych znaków:
string insertAfterEveryLetterInString( const string & s, const char * letters )
{
ostringstream os;
copy( s.begin(), s.end(), ostream_iterator < char >( os, letters ) );
return os.str();
}
Podsumowanie własnych operacji tekstowych
Mimo iż za chwilę przedstawię ciekawe/popularne operacje tekstowe z biblioteki boost, chciałem przedstawić parę przykładów jak sobie radzić gdy z jakiegoś powodu nie chcemy/nie możemy użyć gotowych rozwiązań z biblioteki boost. Poza tym w konkretnych przypadkach użytkownik może potrzebować jeszcze bardziej wyszukanych rozwiązań niż nawet biblioteka dostarcza, więc warto aby były przykłady jak takie rzeczy robić.
Operacje z boost:string
Wiadomości wstępne
W STLu część operacji na std::string oraz algorytmów uogólnionych zwraca raz kopię, raz operuje na orygialnych danych, poza tym nie są standardowo obsługiwane niewrażliwości na wielkość znaków. W
boost::algorithm::string są najczęściej dostępne wszystkie te warianty:
Ponadto używając stl-owych algorytmów uogólnionych na stringach musieliśmy podawać itetarory do funkcji [ begin(), end() ), przy użyciu boosta wystarczy sama instancja klasy std::string, co jest niewątpliwie ułatwieniem dla palca i oka, a także pamięci, oraz jest mniej podatne na błędy programisty.
Aby tej superowości stało się zadość, dodam że wszystkie te operacje nie dotyczą jedynie
std::string (czyli basic_string<char>), ale też innych specjalizacji basic_string, a nawet innych rodzajów kolekcji pod warunkiem że spełniają one pewne warunki.
Przykłady prostych funkcji operujących na tekście z biblioteki boost
Zmiana wielkości znaków
Pamiętamy ile trzeba było się natrudzić żeby zmienić wielkości znaków w całym stringu, teraz wystarczy coś takiego:
#include<boost/algorithm/string/case_conv.hpp>
string napis( "ZacZyNaMy ZAbAWE" );
to_upper( napis );
to_lower( napis );
napis = "ZacZyNaMy ZAbAWE";
to_upper_copy( napis );
to_lower_copy( napis );
Czy string zaczyna/kończy się innym stringiem
#include<boost/algorithm/string/predicate.hpp>
...
string napis( "ZacZyNaMy ZAbAWE" );
starts_with( napis, "Zac" );
ends_with( napis, "AWE" );
istarts_with( napis, "zAc" );
iends_with( napis, "awE" );
Czy jeden string zawiera/równa się drugiemu
#include<boost/algorithm/string/predicate.hpp>
...
contains( string( "Ala ma kangury" ), string( "Ala" ) );
icontains( string( "Ala ma kangury" ), string( "alA" ) );
equals( string( "Ala" ), string( "Ala" ) );
iequals( string( "Ala" ), string( "alA" ) );
lexicographical_compare( string( "aLe_baran" ), string( "Ale_gazda" ) );
ilexicographical_compare( string( "aLe_baran" ), string( "Ale_gazda" ) )
all( string( "napis" ), is_alpha() );
Specjalne własności (z ang "Predicates") dotyczące operacji tekstowych
Chwilę temu przedstawiłem wiele funkcji, część z nich wydała się niezbyt potrzebna (np. lexicographical_compare, który roby w przedstawionej wersji to co operator<()). Niemniej jednak zataiłem jeden fakt -że powyższe funkcje mają przeciążoną wersje, które przyjmują jeszcze jeden argument -jako ostatni argument przyjmuje jeden lub więcej własności, które modyfikują działanie danej funkcji.
Czym są ów własności?
Są to m.in. operacje znane nam z
#include<cctype>
(z inną konwencją nazewniczą np. is_alnum, is_space, is_lower, ...), poza analogicznymi do tych z cctype są też inne bardzo ułatwiające operacje z tekstem przy użyciu boost::algorithm::string:
template < typename RangeT > unspecified is_any_of( const RangeT & Set );
template < typename CharT > unspecified is_from_range( CharT From, CharT To );
boost::algorithm::detail::is_classifiedF alpha = is_alpha();
cout << alpha( 'z' ) << endl;
Jak widać na pierwszy rzut oka robią to co funkcje z cctype, tylko ciężej (+ są to szablony funktorów języka C++ a nie zwykłe funkcje), ich zaletą jest natomiast to że mozna je łączyć przy pomocy operatorów logicznych, np:
is_digit() || is_from_range( 'A', 'Z' );
.
Duża część funkcji z boost::algotitm, które się pojawią w ramach tego artykułu, jako jeden z parametrów przyjmują właśnie te własności.
Warto dodać że funkcje znane z #include<cctype> mają troszeczkę zmodyfikowaną wersję zależną od kultury danego kraju/regionu w nagłówku #include<locale> ; boostowe wersje tych funkcji też używają ustawień kulturowych. |
Przykłady użycia własności zamieszczę gdy opiszę więcej funkcji ich używających.
Przycinaie znaków (operacja trim)
#include<boost/algorithm/string/trim.hpp>
...
template < typename SequenceT >
void trim_left( SequenceT & Input );
template < typename SequenceT >
void trim_right( SequenceT & Input );
template < typename SequenceT >
SequenceT trim_left_copy( const SequenceT & Input );
template < typename SequenceT >
SequenceT trim_right_copy( const SequenceT & Input );
template < typename SequenceT, typename PredicateT >
void trim_left_if( SequenceT & Input, PredicateT IsSpace );
template < typename SequenceT, typename PredicateT >
void trim_right_if( SequenceT & Input, PredicateT IsSpace );
template < typename SequenceT, typename PredicateT >
SequenceT trim_left_copy_if( const SequenceT & Input, PredicateT IsSpace );
template < typename SequenceT, typename PredicateT >
SequenceT trim_right_copy_if( const SequenceT & Input, PredicateT IsSpace );
template < typename SequenceT >
void trim( SequenceT & Input );
template < typename SequenceT >
SequenceT trim_copy( const SequenceT & Input );
template < typename SequenceT, typename PredicateT >
void trim_if( SequenceT & Input, PredicateT IsSpace );
template < typename SequenceT, typename PredicateT >
SequenceT trim_copy_if( const SequenceT & Input, PredicateT IsSpace );
Mały przykład ilustrujący zastosowanie przyciniania
#include <iostream>
#include <string>
#include<boost/algorithm/string/trim.hpp>
#include<boost/algorithm/string/predicate.hpp>
using namespace std;
int main( int argc, char * argv[] )
{
string s( " Niech to bedzie nasz wzorowy napis " );
cout << "0) '" << s << "'" << endl;
cout << "1) '" << boost::algorithm::trim_right_copy( s ) << "'" << endl;
cout << "2) '" << boost::algorithm::trim_left_copy( s ) << "'" << endl;
cout << "3) '" << boost::algorithm::trim_copy( s ) << "'\n" << endl;
cout << "a0) '" << s << "'" << endl;
boost::algorithm::trim_right( s );
cout << "a1) '" << s << "'" << endl;
boost::algorithm::trim( s );
cout << "a2) '" << s << "'\n" << endl;
cout << "b1) '" << boost::algorithm::trim_copy_if( s, boost::algorithm::is_from_range( 'A', 'Z' ) || boost::algorithm::is_any_of( "poise" ) ) << "'" << endl;
return 0;
}
Wydruk z powyższego programiku:
0) ' Niech to bedzie nasz wzorowy napis '
1) ' Niech to bedzie nasz wzorowy napis'
2) 'Niech to bedzie nasz wzorowy napis '
3) 'Niech to bedzie nasz wzorowy napis'
a0) ' Niech to bedzie nasz wzorowy napis '
a1) ' Niech to bedzie nasz wzorowy napis'
a2) 'Niech to bedzie nasz wzorowy napis'
b1) 'ch to bedzie nasz wzorowy na'
Na uwagę zasługuję ostatnia operacja
trim_copy_if, dzięki podanym własnościom przycina litery z zakresu [A, Z] oraz należąca do zbioru {p, o, i, s, e} i zwraca zmieniony string bez modyfikacji oryginalnego.
Funkcje biblioteki boost do operowania na tekście, operujące na iteratorach
Tytuł brzmi odstraszająco, jednak zacznę od użycia, dzięki któremu zapomnimy (na chwilę) o tym opieraniu się na iteratorach. Potem jednak pozwolę sobie na dopowiedzenie jaką wygode daje nam właśnie to oparcie działania tych funkcji na iteratorach.
Do tych funkcji należą operacje takie jak:
Wyszukiwanie
Pominę typ zwracany przez te funkcje (który jest iteratorem).
#include<boost/algorithm/string/find.hpp>
template < typename Range1T, typename Range2T >
...find_first( Range1T & napis, const Range2T & igla );
template < typename Range1T, typename Range2T >
...ifind_first( Range1T & napis, const Range2T & igla );
template < typename Range1T, typename Range2T >
...find_last( Range1T & napis, const Range2T & igla );
template < typename Range1T, typename Range2T >
...ifind_last( Range1T & napis, const Range2T & igla );
template < typename Range1T, typename Range2T >
...find_nth( Range1T & napis, const Range2T & igla, int Nty );
template < typename Range1T, typename Range2T >
...ifind_nth( Range1T & napis, const Range2T & igla, int Nty );
template < typename RangeT >
...find_head( RangeT & napis, int N_znakow );
template < typename RangeT >
...find_tail( RangeT & napis, int N_znakow );
template < typename RangeT, typename PredicateT >
...find_token( RangeT & Input, PredicateT wlasnosci, token_compress_mode_type eCompress = token_compress_off );
template < typename RangeT, typename FinderT >
...find( RangeT & napis, const FinderT & wyszukiwacz );
Żeby zobaczyć więcej plusów powyższych funkcji wyszukiwania, nie wspominając o iteratorach, dodam że każda z nich ma konwersje do boola, dzięki czemu można m.in. łatwo sprawdzić czy dana fraza występuje w tekście:
if( ifind_first( napis, "DOPLATA" ) ) cout << "Pisze ze darmowe, ale trzeba bedzie im doplacac!" << endl;
Poza tym zwrócony rezultat wyszukiwania można z łatwością wyświetlić na strumień np:
cout << ifind_first( string( "dzisiaj swieci slonce, ale pada deszcz" ), "Ale" ) ) << endl;
|
Przykładowy program ilustrujący użycie funkcji wyszukujących
#include <iostream>
#include <string>
#include<boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
main( int argc, char * argv[] )
{
string napis( "Dzisiaj zdarzylo mi sie dac dziewczynie szczeniaczka. Szczeniaczka dala jej tez siostra." );
string igla( "szczeniaczka" );
cout << "Nasz napis: '" << napis << "'\n";
iterator_range < string::iterator > szukany;
if( szukany = find_last( napis, igla ) )
cout << "1) znaleziono wyraz: " << igla << " szukajac ostatniego jego wystapienia: " << szukany << endl;
cout << "2) szukanie pierwszego wystapienia 'dzisiaj': " << find_first( napis, "dzisiaj" ) << endl;
cout << "3) szukanie pierwszego wystapienia 'dzisiaj' ale ignorujac wielkosc znakow: " <<( szukany = ifind_first( napis, "dzisiaj" ) ) << endl;
string znaleziony( szukany.begin(), szukany.end() );
cout << "4) string powstaly z wyszukanej igly: " << znaleziony << endl;
cout << "5) pierwsze 4 znaki napisu: " << find_head( napis, 4 ) << endl;
cout << "6) ostatnie 4 znaki napisu: " << find_tail( napis, 4 ) << endl;
cout << "7) pierwsze -4 znaki napisu: " << find_head( napis, - 4 ) << endl;
cout << "8) ostatnie -4 znaki napisu: " << find_tail( napis, - 4 ) << endl;
cout << "9) wyszukiwanie tokenu: " << find_token( napis, not is_space() && not is_alpha() ) << endl;
}
Wydruk z programu:
Nasz napis: 'Dzisiaj zdarzylo mi sie dac dziewczynie szczeniaczka. Szczeniaczka dala jej tez siostra.'
1) znaleziono wyraz: szczeniaczka szukajac ostatniego jego wystapienia: szczeniaczka
2) szukanie pierwszego wystapienia 'dzisiaj':
3) szukanie pierwszego wystapienia 'dzisiaj' ale ignorujac wielkosc znakow: Dzisiaj
4) string powstaly z wyszukanej igly: Dzisiaj
5) pierwsze 4 znaki napisu: Dzis
6) ostatnie 4 znaki napisu: tra.
7) pierwsze -4 znaki napisu: Dzisiaj zdarzylo mi sie dac dziewczynie szczeniaczka. Szczeniaczka dala jej tez sios
8) ostatnie -4 znaki napisu: iaj zdarzylo mi sie dac dziewczynie szczeniaczka. Szczeniaczka dala jej tez siostra.
9) wyszukiwanie tokenu: .
Opis paru ciekawch/trudniejszych/kluczowych fragmentów w programie:
Dzielenie napisów na kolekcje i łączenie kolekcji w napisy
Czyli znane nam z innych języków programowania operacje
split() i
join().
Funkcje te wyglądają w następujący sposób:
#include<boost/algorithm/string/split.hpp>
template < typename SequenceSequenceT, typename RangeT, typename PredicateT >
SequenceSequenceT & split( SequenceSequenceT & Result, RangeT & Input, PredicateT Pred,
token_compress_mode_type eCompress = token_compress_off );
#include<boost/algorithm/string/join.hpp>
template < typename SequenceSequenceT, typename Range1T >
range_value < SequenceSequenceT >::type
join( const SequenceSequenceT & Input, const Range1T & Separator );
template < typename SequenceSequenceT, typename Range1T, typename PredicateT >
range_value < SequenceSequenceT >::type
join_if( const SequenceSequenceT & Input, const Range1T & Separator,
PredicateT Pred );
Pierwsza z funkcji -
split() zapisuje wyniki operacji podziału napisu do odpowiedniego kontenera. Podział odbywa się po znalezieniu odpowiednich dzielników napisu (wg podanych w argumencie własności tekstu). Parametr eCompress decyduje czy wlasnosci podane w parametrach sa laczone w jeden, czy nie.
Druga z funkcji -
join() łączy kolekcje w jeden napis. Elementy kolekcji są łączone w taki sposób że między nie idzie podany separator.
Kolejna z funkcji -
join_if() łączy kolekcje do postaci napisu, ale konkatenuje tylko te elementy, które spełniają własności podane w argumentach.
Przykład łączenie i dzielenia wyrazów
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
vector < string >& operator <<( vector < string >& v, const string & s )
{
v.push_back( s );
return v;
}
bool is_string_alphaOrSpace( const string & s )
{
return all( s, is_alpha() || is_space() );
}
main( int argc, char * argv[] )
{
string napis( "Jablko, gruszka, banan, winne grono,,, marchewka" );
cout << "Nasz napis: '" << napis << "'\n";
vector < string > owoce_warzywa;
split( owoce_warzywa, napis, is_punct() || is_space() );
cout << "1) Operacja split przy wylaczonej kompresji 'token_compress_off' daje: ";
copy( owoce_warzywa.begin(), owoce_warzywa.end(), ostream_iterator < string >( cout, "/" ) );
cout << endl;
split( owoce_warzywa, napis, is_punct() || is_space(), token_compress_on );
cout << "2) Operacja split przy wlacznonej kompresji 'token_compress_off' daje: ";
copy( owoce_warzywa.begin(), owoce_warzywa.end(), ostream_iterator < string >( cout, "/" ) );
cout << endl;
vector < string > pseudonimy_zwyciezcow_konkursu;
pseudonimy_zwyciezcow_konkursu << "Zimny1" << "Krzywy90" << "PostrachOceanow" << "ProPlayer13" << "Wymiatasz";
cout << "3) Zwyciescy gracze w konkursie: " << join( pseudonimy_zwyciezcow_konkursu, "/" ) << endl;
cout << "4) Gracze o fajniejszych pseudonimach: " << join_if( pseudonimy_zwyciezcow_konkursu, "/", is_string_alphaOrSpace ) << endl;
}
Wydruk z powyższego programiku jest następujący:
Nasz napis: 'Jablko, gruszka, banan, winne grono,,, marchewka'
1) Operacja split przy wylaczonej kompresji 'token_compress_off' daje: Jablko//gruszka//banan//winne/grono////marchewka/
2) Operacja split przy wlacznonej kompresji 'token_compress_off' daje: Jablko/gruszka/banan/winne/grono/marchewka/
3) Zwyciescy gracze w konkursie: Zimny1/Krzywy90/PostrachOceanow/ProPlayer13/Wymiatasz
4) Gracze o fajniejszych pseudonimach: PostrachOceanow/Wymiatasz
Opis ciekawych/trudniejszych/kluczowych fragmentów programiku:
Zastępowanie i wymazywanie odpowiednich podciągów w napisie
Zacznę od zastępowania podciągów w ciągu wyrazów.
1. Zastępowanie i wymazywanie pierwszego wystąpienia szukanego podciągu na nowy w podanym napisie:
#include<boost/algorithm/string/replace.hpp>
template < typename SequenceT, typename Range1T, typename Range2T >
void replace_first( SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
template < typename SequenceT, typename Range1T, typename Range2T >
SequenceT replace_first_copy( const SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
template < typename SequenceT, typename Range1T, typename Range2T >
void ireplace_first( SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
template < typename SequenceT, typename Range2T, typename Range1T >
SequenceT ireplace_first_copy( const SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
#include<boost/algorithm/string/erase.hpp>
template < typename SequenceT, typename RangeT >
void erase_first( SequenceT & napis, const RangeT & szukany );
template < typename SequenceT, typename RangeT >
SequenceT erase_first_copy( const SequenceT & napis, const RangeT & szukany );
template < typename SequenceT, typename RangeT >
void ierase_first( SequenceT & napis, const RangeT & szukany );
template < typename SequenceT, typename RangeT >
SequenceT ierase_first_copy( const SequenceT & napis, const RangeT & szukany );
2. Zastępowanie i wymazywanie ostatniego wystąpienia szukanego podciągu na nowy w podanym napisie.
Wszystkie te funkcje wyglądają i zachowują się tak jak powyższe z tą różnicą, że w nazwie zamiast "first" mają "last", w związku z tym nie ma sensu opisywanie ich wszystkich, dlatego je tylko wypiszę.
void replace_last( SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
SequenceT replace_last_copy( const SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
void ireplace_last( SequenceT & napis, const Range1T & szukany, const Range2T & nowy );
OutputIteratorT ireplace_last_copy( OutputIteratorT Output, const Range1T & napis, const Range2T & szukany, const Range3T & nowy );
void erase_last( SequenceT & napis, const RangeT & szukany );
SequenceT erase_last_copy( const SequenceT & napis, const RangeT & szukany );
void ierase_last( SequenceT & napis, const RangeT & szukany );
SequenceT ierase_last_copy( const SequenceT & napis, const RangeT & szukany );
3. Zastępowanie i wymazywanie N-tego wystąpienia szukanego podciągu na nowy w podanym napisie.
Funkcje do tego służące wyglądają analogicznie do funkcji zastępujących i wymazujących pierwsze i ostatnie wystąpienie, mają jedynie dodatkowy parametr wskazujący Numer wystąpienia do zastąpienie/wymazania. W związku z tym tylko wypisze te funkcje.
void replace_nth( SequenceT & Input, const Range1T & Search, int Nth, const Range2T & Format );
SequenceT replace_nth_copy( const SequenceT & Input, const Range1T & Search, int Nth, const Range2T & Format );
void ireplace_nth( SequenceT & Input, const Range1T & Search, int Nth, const Range2T & Format );
SequenceT ireplace_nth_copy( const SequenceT & Input, const Range1T & Search, int Nth, const Range2T & Format );
void erase_nth( SequenceT & Input, const RangeT & Search, int Nth );
SequenceT erase_nth_copy( const SequenceT & Input, const RangeT & Search, int Nth );
void ierase_nth( SequenceT & Input, const RangeT & Search, int Nth );
SequenceT ierase_nth_copy( const SequenceT & Input, const RangeT & Search, int Nth );
4. Zastępowanie i wymazywanie wszystkich wystąpień szukanego podciągu na nowy w podanym napisie.
Funkcje to robiące są analogiczne do wcześniejszych z grupy zastępowanie/wymazywanie, w związku z tym tylko je wypiszę.
void replace_all( SequenceT & Input, const Range1T & Search, const Range2T & Format );
SequenceT replace_all_copy( const SequenceT & Input, const Range1T & Search, const Range2T & Format );
void ireplace_all( SequenceT & Input, const Range1T & Search, const Range2T & Format, const std::locale & Loc = std::locale() );
SequenceT ireplace_all_copy( const SequenceT & Input, const Range1T & Search, const Range2T & Format, const std::locale & Loc = std::locale() );
void erase_all( SequenceT & Input, const RangeT & Search );
SequenceT erase_all_copy( const SequenceT & Input, const RangeT & Search );
void ierase_all( SequenceT & Input, const RangeT & Search, const std::locale & Loc = std::locale() );
SequenceT ierase_all_copy( const SequenceT & Input, const RangeT & Search, const std::locale & Loc = std::locale() );
5. Zastępowanie/wymazywanie N znaków z głowy, lub z ogona w danym napisie. Jeżeli N>napis.size() wtedy cała zawartość jest wymazywana/zastępowana przez nową zawartość. Gdy N<0 wtedy zostaje wymazanych/zastąpionych znaków w ilości napis.size()-|N|. Wypiszę te funkcje:
void replace_head( SequenceT & Input, int N, const RangeT & Format );
SequenceT replace_head_copy( const SequenceT & Input, int N, const RangeT & Format );
template < typename SequenceT > void erase_head( SequenceT & Input, int N );
SequenceT erase_head_copy( const SequenceT & Input, int N );
void replace_tail( SequenceT & Input, int N, const RangeT & Format );
SequenceT replace_tail_copy( const SequenceT & Input, int N, const RangeT & Format );
template < typename SequenceT > void erase_tail( SequenceT & Input, int N );
SequenceT erase_tail_copy( const SequenceT & Input, int N );
Przykład użycia funkcji zastępowania i wymazywania
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
main( int argc, char * argv[] )
{
string napis( "Twoje wypowiedz zawiera wyraz: niedozwolone slowo, wiec nie moze byc rozpowszechniana!" );
cout << "Nasz napis: '" << napis << "'\n";
cout << "1) Napis z zamienionym 'niedozwolone'->'Pewne': " << replace_first_copy( napis, "niedozwolone", "Pewne" ) << endl;
erase_first( napis, "wyraz: " );
replace_last( napis, "niedozwolone", "pewne" );
cout << "2) Napis po wymazaniu 'wyraz: ' i zamianie 'niedozwolone': " << napis << "'\n";
napis = "Dr Kowalski wlasnie sie skonczyl egzamin. Dr Kowalski prowadzi informatyke. Z dr Kowalskim przyjemnie sie pracje!!!";
cout << "\nNasz napis: '" << napis << "'\n";
replace_all( napis, "dr", "dr hab." );
cout << "3) Nasz napis po zamianie 'dr'->'dr hab.':\n\t'" << napis << "'\n";
ireplace_nth( napis, "dr", 1, "dr hab." );
cout << "5) po zamianie 1-szego 'dr'->'dr hab. z pominieciem wielkosci liter':\n\t'" << napis << "'\n";
erase_head( napis, 49 );
replace_tail( napis, 2, ":)" );
cout << "6) Napis po usunieciu 49 znakow z poczatku i zastapieniu 2 znakow z konca:\n\t'" << napis << "'\n";
cout << endl;
}
I do kompletu wydruk z programu:
Nasz napis: 'Twoje wypowiedz zawiera wyraz: niedozwolone slowo, wiec nie moze byc rozpowszechniana!'
1) Napis z zamienionym 'niedozwolone'->'Pewne': Twoje wypowiedz zawiera wyraz: Pewne slowo, wiec nie moze byc rozpowszechniana!
2) Napis po wymazaniu 'wyraz: ' i zamianie 'niedozwolone': Twoje wypowiedz zawiera pewne slowo, wiec nie moze byc rozpowszechniana!'
Nasz napis: 'Dr Kowalski wlasnie sie skonczyl egzamin. Dr Kowalski prowadzi informatyke. Z dr Kowalskim przyjemnie sie pracje!!!'
3) Nasz napis po zamianie 'dr'->'dr hab.':
'Dr Kowalski wlasnie sie skonczyl egzamin. Dr Kowalski prowadzi informatyke. Z dr hab. Kowalskim przyjemnie sie pracje!!!'
5) po zamianie 1-szego 'dr'->'dr hab. z pominieciem wielkosci liter':
'Dr Kowalski wlasnie sie skonczyl egzamin. dr hab. Kowalski prowadzi informatyke. Z dr hab. Kowalskim przyjemnie sie pracje!!!'
6) Napis po usunieciu 49 znakow z poczatku i zastapieniu 2 znakow z konca:
' Kowalski prowadzi informatyke. Z dr hab. Kowalskim przyjemnie sie pracje!:)'
W tym przykładzie zrobiłem tylko to co trzeba -pokazałem działanie funkcji, przez co przykład jest bardzo prosty, w związku z tym opis pewnych fragmentów jest zbędny:).
lexical_cast - łatwa konwersja typów liczbowych, tekstowych innych
Czas na chwilową przerwę od boost/algorithm/string, przedstawię więc krótkie narzędzie z biblioteki boost, dzięki któremu konwersja liczb na napis i na odwróc jest o wiele prostrza. Jak wiemy nie ma łatwego sposobu na takie konwersje, w C++ mamy std::stringstream, ale żeby coś przekonwertować trzeba: utworzyć odpowiednio obiekt std::stringstream, wykonać przekierowanie do/z ostringstreama i dopiero wtedy mamy naszą konwersję. Żeby mieć konwersję w miejscu musimy napisać własną funkcję, ale po co pisać własną gdy mamy świetną już napisaną:
#include<boost/lexical_cast.hpp>
template < typename Target, typename Source >
Target lexical_cast( const Source & arg );
I od razu przykład:
#include <iostream>
#include <string>
#include <typeinfo>
#include <limits>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/find.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
main( int argc, char * argv[] )
{
string napis( "11" );
cout << "Nasz napis: '" << napis << "' jest typu: '" << typeid( napis ).name() << "'\n";
int liczba = lexical_cast < int >( napis );
cout << "Liczba int: '" << liczba << "' jest typu: '" << typeid( liczba ).name() << "'\n";
double liczba_double = lexical_cast < double >( napis );
cout << "Liczba double: '" << liczba_double << "' jest typu: '" << typeid( liczba_double ).name() << "'\n";
liczba_double += 1.3242 * 2.324;
cout << "Liczba double ponownie: '" << liczba_double << "' jest typu: '" << typeid( liczba_double ).name() << "'\n";
napis = lexical_cast < string >( napis );
cout << "Napis ponownie: '" << liczba_double << "' jest typu: '" << typeid( napis ).name() << "'\n";
cout << endl;
napis = "a";
char litera = lexical_cast < char >( napis );
cout << "ze stringa: '" << napis << "' na char: '" << litera << "'\n";
cout << "string przedstawiajacy maksymalna liczbe calkowita: '" << lexical_cast < string >( numeric_limits < long long unsigned >::max() ) << "'\n";
napis = "aaabBbccc";
string napis2 = lexical_cast < string >( ifind_first( napis, "bbb" ) );
cout << "String otrzymany z iterator_range, otrzymany z wyszukiwania bragmentu 'bbb' w: " << napis << " a wynikowy string to: '" << napis2 << "'\n";
napis = "aaa1234.32ccc";
liczba_double = lexical_cast < double >( find_token( napis, is_digit() || is_punct(), token_compress_on ) );
cout << "Liczba wyciagnieta ze stringa: " << liczba_double << endl;
}
Wydruk z programu:
asz napis: '11' jest typu: 'Ss'
Liczba int: '11' jest typu: 'i'
Liczba double: '11' jest typu: 'd'
Liczba double ponownie: '14.0774' jest typu: 'd'
Napis ponownie: '14.0774' jest typu: 'Ss'
ze stringa: 'a' na char: 'a'
string przedstawiajacy maksymalna liczbe calkowita: '18446744073709551615'
String otrzymany z iterator_range, otrzymany z wyszukiwania bragmentu 'bbb' w: aaabBbccc a wynikowy string to: 'bBb'
Liczba wyciagnieta ze stringa: 1234.32
Wyjaśnienie pewnych miejsc w programie:
W tym temacie dodam jeszcze jeden przykład, ilustrujący sytuację nieudanego rzutowania:
#include <iostream>
#include <boost/lexical_cast.hpp>
using namespace std;
using namespace boost;
main( int argc, char * argv[] )
{
for( int i = 1; i < argc; ++i )
try
{
short liczba = lexical_cast < short >( argv[ i ] );
cout << "podano liczbe: " << liczba << endl;
}
catch( bad_lexical_cast & e )
{
cerr << "!!! To co podano to na pewno nie liczba: " << argv[ i ] << endl;
}
}
I wydruk dla podanych argumentow: 23, 453, d:
podano liczbe: 23
podano liczbe: 453
!!! To co podano to na pewno nie liczba: d
Często zastanawiało mnie jak w łatwy sposób zamienić vector<string> w vector<int>, dlatego zamieszczam przykładowe rozwiązanie tego problemu, przy użyciu lexical_last:
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <string>
#include <boost/lexical_cast.hpp>
template < typename C1, typename C2 >
void castContainer( const C1 & source, C2 & destination )
{
typedef typename C1::value_type source_type;
typedef typename C2::value_type destination_type;
destination.resize( source.size() );
std::transform( source.begin(), source.end(), destination.begin(), boost::lexical_cast < destination_type, source_type > );
}
template < typename T, typename T2 >
std::vector < T >& operator <<( std::vector < T >& v, T2 t )
{
v.push_back( T( t ) );
return v;
}
main( int argc, char * argv[] )
{
std::vector < std::string > v1;
v1 << "11" << "22" << "33" << "44";
std::cout << "vector<string>: ";
std::copy( v1.begin(), v1.end(), std::ostream_iterator < std::string >( std::cout, ", " ) );
std::cout << std::endl;
std::vector < int > v2;
castContainer( v1, v2 );
std::cout << "vector<int>: ";
std::copy( v2.begin(), v2.end(), std::ostream_iterator < int >( std::cout, ", " ) );
std::cout << std::endl;
}
Iteratory w funkcjach z boost/algorithm-zalety i zastosowanie
Teraz pewne plusy budowy na iteratorach, najlepiej pokazać w przykładach:
#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
main( int argc, char * argv[] )
{
string napis( "Czesc Karol, czesc Magda, czesc Michal. Czesc wszystkim. I tobie rowniez CZESC!" );
iterator_range < string::iterator > pozycja = find_last( napis, "tobie" );
to_upper( pozycja );
for( string::iterator it = pozycja.begin(); it != pozycja.end(); ++it )
cout << "-" << * it;
cout << endl;
iterator_range < string::iterator > pozycja_wewnatrz = ifind_first( pozycja, "obi" );
transform( pozycja_wewnatrz.begin(), pozycja_wewnatrz.end(), pozycja_wewnatrz.begin(),::tolower );
cout << "Poczatkowy napis: " << napis << endl;
}
Wydruk z programu:
-T-O-B-I-E
Poczatkowy napis: Czesc Karol, czesc Magda, czesc Michal. Czesc wszystkim. I TobiE rowniez CZESC!
Teraz czas na ogólny opis funkcji. Jak wiemy wyszukiwanie zwraca
iterator_range<>, jak iterator (i jak zgodny z konceptem iteratora w C++) to posiada metody begin() i end(), przez co możemy iterować po tym co jest wskazywane przez ten iterator. Ponadto iterator wskazuje na element w kolekcji, więc jeżeli nie jest to const iterator to możemy zmieniać zawartość obiektów -w naszym przypadku zmieniamy oryginalny napis. Wewnątrz "znaleziska" możemy szukać ponownie i otrzymać inny iterator_range<>. Na iteratorach działają algorytmy uogólnione.
Finders & Formatters
Tej nazwy pozwoliłem sobie nie tłumaczyć. Przed pokazaniem następnych przykładów opiszę Findery i Formattery.
Finder to funktor, który wie czego szuka i uwzględnia własności wyszukiwanego tekstu w swoim wyszukiwaniu.
Mamy dostępne następujące findery:
#include<boost/algorithm/string/finder.hpp>
template < typename RangeT >
unspecified first_finder( const RangeT & Search );
template < typename RangeT, typename PredicateT >
unspecified first_finder( const RangeT & Search, PredicateT Comp );
template < typename RangeT >
unspecified last_finder( const RangeT & Search );
template < typename RangeT, typename PredicateT >
unspecified last_finder( const RangeT & Search, PredicateT Comp );
template < typename RangeT >
unspecified nth_finder( const RangeT & Search, int Nth );
template < typename RangeT, typename PredicateT >
unspecified nth_finder( const RangeT & Search, int Nth, PredicateT Comp );
unspecified head_finder( int N );
unspecified tail_finder( int N );
template < typename PredicateT >
unspecified token_finder( PredicateT Pred, token_compress_mode_type eCompress = token_compress_off );
template < typename ForwardIteratorT >
unspecified range_finder( ForwardIteratorT Begin, ForwardIteratorT End );
template < typename ForwardIteratorT >
unspecified range_finder( iterator_range < ForwardIteratorT > Range );
Jak widać formatery mają wersję, która przyjmuje funktor porównujący napisy (przyjmuje on 2 napisy, porównuje na swój sposób i zwraca boola), oto dostępne funktory porównujące:
#include<boost/algorithm/string/compare.hpp>
struct is_iequal;
struct is_less;
struct is_iless;
struct is_not_greater;
struct is_not_igreater;
Czas na Formattery -są to sposoby zastępowania znalezionego tekstu. Jak finder wie co znaleźć i jak to znaleźć, tak formatter wie na co to zastąpić.
Mamy dostępne następujące formattery:
#include<boost/algorithm/string/formatter.hpp>
template < typename RangeT >
unspecified const_formatter( const RangeT & Format );
template < typename RangeT >
unspecified identity_formatter();
template < typename RangeT >
unspecified empty_formatter( const RangeT & );
Przedstawiłem obiekty wyszukujące, które na pierwszy rzut oka robią to co wcześniej przedstawione funkcje wyszukujące; przedstawiłem "jakieś formattery" i co dalej -czas na przedstawienie funkcji, które z tego korzystają (a potem obiecuje przykłady):
#include <boost/algorithm/string/find.hpp>
template < typename RangeT, typename FinderT >
iterator_range < typename range_iterator < RangeT >::type >
find( RangeT & Input, const FinderT & Finder );
Poniższe funkcje z rodziny find_format* wyszukują przy użyciu findera i zastępują formatterem.
#include<boost/algorithm/string/find_format.hpp>
template < typename SequenceT, typename FinderT, typename FormatterT >
void find_format( SequenceT & Input, FinderT Finder, FormatterT Formatter );
template < typename SequenceT, typename FinderT, typename FormatterT >
SequenceT find_format_copy( const SequenceT & Input, FinderT Finder, FormatterT Formatter );
template < typename SequenceT, typename FinderT, typename FormatterT >
void find_format_all( SequenceT & Input, FinderT Finder, FormatterT Formatter );
template < typename SequenceT, typename FinderT, typename FormatterT >
SequenceT find_format_all_copy( const SequenceT & Input, FinderT Finder, FormatterT Formatter );
Poza tym kolejne funkcje -iterujące po wynikach wyszukiwania:
#include <boost/algorithm/string/iter_find.hpp>
template < typename SequenceSequenceT, typename RangeT, typename FinderT >
SequenceSequenceT & iter_find( SequenceSequenceT & Result, RangeT & Input, FinderT Finder );
template < typename SequenceSequenceT, typename RangeT, typename FinderT >
SequenceSequenceT & iter_split( SequenceSequenceT & Result, RangeT & Input, FinderT Finder );
Mamy również funkcje tworzące find_iterator i split_iterator, dzięki którym możemy iterować po wystąpieniach szukanego fragmentu w napisie:
template < typename SequenceSequenceT, typename RangeT, typename FinderT >
find_iterator < SequenceSequenceT::iterator > make_find_iterator( SequenceSequenceT & napis, FinderT Finder );
template < typename SequenceSequenceT, typename RangeT, typename FinderT >
split_iterator < SequenceSequenceT::iterator > make_split_iterator( SequenceSequenceT & napis, FinderT Finder );
Przykłady użycia F&F
Zacznę od przykładu użycia funkcji make_*_iterator:
#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
main( int argc, char * argv[] )
{
string napis( "Dla mamy kwiatek, dla taty gitara, dla siostry lalka..." );
cout << "Oryginalny napis: " << napis << endl;
for( find_iterator < string::iterator > it = make_find_iterator( napis, first_finder( "dla" ) ), itEnd; it != itEnd; ++it )
napis.replace( it->begin(), it->end(), "od" );
cout << "Napis po zmianach: " << napis << endl;
for( find_iterator < string::iterator > it = make_find_iterator( napis, token_finder( is_punct() ) ), itEnd; it != itEnd; ++it )
napis.replace( it->begin(), it->end(), ". A" );
cout << "Napis po zmianach: " << napis << endl;
for( split_iterator < string::iterator > it = make_split_iterator( napis, first_finder( ". " ) ), itEnd; it != itEnd; ++it )
cout << "- " << copy_range < string >( * it ) << endl;
}
Oraz wydruk z programu:
Oryginalny napis: Dla mamy kwiatek, dla taty gitara, dla siostry lalka...
Napis po zmianach: Dla mamy kwiatek, od taty gitara, od siostry lalka...
Napis po zmianach: Dla mamy kwiatek. A od taty gitara. A od siostry lalka...
- Dla mamy kwiatek
- A od taty gitara
- A od siostry lalka...
I opis paru wartych opisania fragmentów programu:
Myślę że dzięki powyższemu przykładowi operacje na wszystkich wystąpieniach danego tekstu zostały w miarę przybliżone. Powyższy przykład ilustruję również różnicę między find_iteratorem a split_iteratorem: ten pierwszy iteruje po wynikach wyszukiwania, ten drugi po fragmentach tekstu pomiędzy wynikami wyszukiwania.
W rozpisie finderów przedstawiłem po 2 wersje każdego z nich, natomiast w poprzednim przykładzie pokazałem tylko wersję bez drugiego parametru, czas więc na drugi parametr (przedstawię tylko fragment kodu):
for( find_iterator < string::iterator > it = make_find_iterator( napis, first_finder( "dla", is_iequal() ) ), itEnd; it != itEnd; ++it )
napis.replace( it->begin(), it->end(), "od" );
, a początkowy wydruk będzie miał formę:
Oryginalny napis: Dla mamy kwiatek, dla taty gitara, dla siostry lalka...
Napis po zmianach: od mamy kwiatek, od taty gitara, od siostry lalka...
- jak widać wyszukujemy z pominięciem wielkości liter.
Kolejny przykład użycia F&F -operacje find() i find_format*
Bez zbędnych komentarzy, lecimy z przykładem:
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <iterator>
#include <string>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost::algorithm;
main( int argc, char * argv[] )
{
string napis( ">> O szyby Deszcz dzwoni, dEszcz dzwoni jesienny. To o szyby DESZCZ dzwoni" );
cout << "Nasz napis: " << napis << endl;
boost::iterator_range < string::iterator > deszcz = find( napis, last_finder( "DESZCZ" ) );
cout << "Wyszukanie ostatniego wystąpienia slowa 'DESZCZ', z uwzglednieniem wielkosci liter: " << deszcz << endl;
to_upper( deszcz );
cout << "Napis po modufikacjach: " << napis << endl;
cout << "Wyszukanie pierwszego wystąpienia slowa 'deszcz', z pominieciem wielkosci liter: " << find( napis, first_finder( "deszcz", is_iequal() ) ) << endl;
cout << "Wyszukanie wystąpienia slowa 'deszcz' nr 2, z pominieciem wielkosci liter: " << find( napis, nth_finder( "deszcz", 1, is_iequal() ) ) << endl;
cout << "Poczatkowe 10 liter: " << find( napis, head_finder( 10 ) ) << endl;
cout << "Wyszukany token (znak interpunkcyjny lub liczba): " << find( napis, token_finder( is_punct() || is_digit(), token_compress_on ) ) << endl;
cout << endl;
find_format_all( napis, first_finder( "deszcz", is_iequal() ), const_formatter( "grad" ) );
cout << "Nasz napis bez deszczy: " << napis << endl;
cout << "Nasz napis bez deszczy: " << find_format_copy( napis, first_finder( " jesienny", is_iequal() ), empty_formatter( "argument podany dla dedukcji typu" ) ) << endl;
cout << "Sprawdzamy rowosc po uzyciu formattera identity_formatter: " << boolalpha <<( napis == find_format_copy( napis, first_finder( napis ), identity_formatter < string >() ) ) << endl << endl;
cout << "const_formatter('dzwoni')(napis): " << const_formatter( "dzwoni" )( napis ) << endl;
cout << "identity_formatter<string>(napis): " << identity_formatter < string >()( napis ) << endl;
detail::empty_container < char > e_container = empty_formatter( "dzwoni" )( napis );
cout << "string( empty_formatter('dzwoni')(napis).begin(), ... ): " << string( e_container.begin(), e_container.end() ) << endl;
}
Wydruk z programu:
Nasz napis: >> O szyby Deszcz dzwoni, dEszcz dzwoni jesienny. To o szyby DESZCZ dzwoni
Wyszukanie ostatniego wystąpienia slowa 'DESZCZ', z uwzglednieniem wielkosci liter: DESZCZ
Napis po modufikacjach: >> O szyby Deszcz dzwoni, dEszcz dzwoni jesienny. To o szyby DESZCZ dzwoni
Wyszukanie pierwszego wystąpienia slowa 'deszcz', z pominieciem wielkosci liter: Deszcz
Wyszukanie wystąpienia slowa 'deszcz' nr 2, z pominieciem wielkosci liter: dEszcz
Poczatkowe 10 liter: >> O szyby
Wyszukany token (znak interpunkcyjny lub liczba): >>
Nasz napis bez deszczy: >> O szyby grad dzwoni, grad dzwoni jesienny. To o szyby grad dzwoni
Nasz napis bez deszczy: >> O szyby grad dzwoni, grad dzwoni. To o szyby grad dzwoni
Sprawdzamy rowosc po uzyciu formattera identity_formatter: true
const_formatter('dzwoni')(napis): dzwoni
identity_formatter<string>(napis):
string( empty_formatter('dzwoni')(napis).begin(), ... ):
Oraz oczywiście opis pewnych miejsc w powyższym programie:
Przykład własnego Formattera
Przedstawiłem wiele przykładów finderów i formatterów, ale nie wygląda żeby one wniosły coś więcej niż wcześniej wpisane funkcje, dlatego pokuszę się o napisanie własnego formattera zgodnie z
konceptem finderów i formatterów w bibliotece boost, aby przedstawić zalety pary finder-formater.
W poniższym programiku są napisane proste 2 formattery, jeden z nich zamienia otrzymaną literę na wielką, drugi zamienia cyfrę w formie cyfry na opis cyfry (np. 1->"jeden"):
#include <iostream>
#include <map>
#include <string>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost::algorithm;
struct ToUpperFormatter
{
template < typename ResultOfFind >
string operator ()( const ResultOfFind & match ) const
{
return to_upper_copy( string( match.begin(), match.end() ) );
}
};
struct DigitFormatter
{
static map < string, string > numbers_in_text;
DigitFormatter()
{
if( numbers_in_text.size() == 0 )
{
numbers_in_text[ "0" ] = "zero";
numbers_in_text[ "1" ] = "jedna";
numbers_in_text[ "2" ] = "dwie";
numbers_in_text[ "3" ] = "trzy";
numbers_in_text[ "4" ] = "cztery";
numbers_in_text[ "5" ] = "piec";
}
}
template < typename ResultOfFind >
string operator ()( const ResultOfFind & match ) const
{
string str( match.begin(), match.end() );
if( numbers_in_text.count( str ) > 0 )
return numbers_in_text[ str ];
else
return str;
}
};
map < string, string > DigitFormatter::numbers_in_text;
main( int argc, char * argv[] )
{
string napis( "A tutaj mamy duzo samoglosek!" );
cout << "Nasz napis: " << napis << endl;
find( napis, token_finder( is_any_of( "aeiouy" ) ) );
find_format_all( napis, token_finder( is_any_of( "aeiouy" ) ), ToUpperFormatter() );
cout << "Nasz napis po uzyciu ToUpperFormatter na samogloskach: " << napis << endl;
napis.assign( "Mam zwierzat w ilosci: 1 owca, 2 krowy, 3 kozy, 4 kury, 0 czasu na nauke C++!" );
cout << "Nasz napis: " << napis << endl;
find_format_all( napis, token_finder( is_digit() ), DigitFormatter() );
cout << "Nasz napis po uzyciu DigitFormatter na liczbach: " << napis << endl;
}
I wydruk z programu:
Nasz napis: A tutaj mamy duzo samoglosek!
Nasz napis po uzyciu ToUpperFormatter na samogloskach: A tUtAj mAmY dUzO sAmOglOsEk!
Nasz napis: Mam zwierzat w ilosci: 1 owca, 2 krowy, 3 kozy, 4 kury, 0 czasu na nauke C++!
Nasz napis po uzyciu DigitFormatter na liczbach: Mam zwierzat w ilosci: jedna owca, dwie krowy, trzy kozy, cztery kury, zero czasu na nauke C++!
Użycie własności tekstowych
Na początku opisu funkcji z boost::algorithm wymieniłem pewne własności tekstowe, przez większość przykładów, przy okazji przedstawiania działania funkcji tekstowych, używałem tych własności, a czas na przykład skupiający się na własnościach. W przykładzie pokusiłem się o napisanie własnej własność tekstu, która przyjmuje znak i porównuje z wcześniej podanym parametrem szablonu, zwracając true jeżeli obydwa znaki pasują:
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <functional>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost::algorithm;
template < char letter >
struct is_letter
: public predicate_facade < is_letter < letter > >
{
template < typename CharT >
bool operator ()( CharT Ch ) const
{
return letter == Ch;
}
};
main( int argc, char * argv[] )
{
string napis( "aaaa! Super! Aaaaa!" );
cout << "Nasz napis: " << napis << endl;
cout << "Napis po przycieciu z obydwu stron liter 'a', interpunkcji i spacji: " << trim_copy_if( napis, is_from_range( 'a', 'a' ) || is_punct() || is_space() ) << endl;
cout << std::boolalpha;
napis = "Ale mi dzisiaj zimno";
cout << "czy napis: '" << napis << "' sklada sie wylacznie z liter i spacji: " << all( napis, is_alpha() || is_space() ) << endl;
napis = "Koniec spania! Wstawaj!";
cout << "czy napis: '" << napis << "' sklada sie wylacznie z liter i spacji: " << all( napis, is_alpha() || is_space() ) << endl;
napis = "ieoiayou";
cout << "czy w napisie: '" << napis << "' sa spolgloski: " << all( napis, not is_any_of( "aeiouy" ) && is_alpha() ) << endl;
napis = "jan.kowalski@wspaniala_polska_domena.pl";
cout << "znak interpunkcyjny, nie bedacy '.' w napisie: '" << napis << "': " << find_token( napis, is_punct() && not is_letter < '.' >() ) << endl;
vector < string > podzial_maila;
split( podzial_maila, napis, is_letter < '@' >() );
cout << "Nazwa uzytkownika mailowego: " << podzial_maila[ 0 ] << ", domena: " << podzial_maila[ 1 ] << endl;
}
Wydruk z programu:
Nasz napis: aaaa! Super! Aaaaa!
Napis po przycieciu z obydwu stron liter 'a', interpunkcji i spacji: Super! A
czy napis: 'Ale mi dzisiaj zimno' sklada sie wylacznie z liter i spacji: true
czy napis: 'Koniec spania! Wstawaj!' sklada sie wylacznie z liter i spacji: false
czy w napisie: 'ieoiayou' sa spolgloski: false
znak interpunkcyjny, nie bedacy '.' w napisie: 'jan.kowalski@wspaniala_polska_domena.pl': @
Nazwa uzytkownika mailowego: jan.kowalski, domena: wspaniala_polska_domena.pl
Tokenizer
Została jeszcze jedna rzecz, którą chciałem któciótko przedstawić w tym artykule -boost::tokenizer. W skrócie (i tym razem będę się tego trzymał) dzieli ona podany tekst na wyrazy, a wygląda tak:
template <
class TokenizerFunc = char_delimiters_separator < char >,
class Iterator = std::string::const_iterator,
class Type = std::string
>
class tokenizer
, a znajduje się w:
#include<boost/tokenizer.hpp>
.
Przykład jak z tego korzystać:
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
main( int argc, char * argv[] )
{
string napis( "aaaa! Czesc, jak zyjesz? U mnie spoko (poza tym ze), pracuje nad iterator_range" );
cout << "oryginalny napis: " << napis << endl;
unsigned licznik = 0;
cout << "************ podzial przy uzyciu domyslnego tokenizera: ************\n";
tokenizer <> tok( napis );
for( tokenizer <>::iterator it = tok.begin(); it != tok.end(); ++it )
cout << ++licznik << ")\t" << * it << "\n";
cout << "************ podzial przy uzyciu offsetu (dzielenie slow wg ilosci znakow znaki): ************\n";
int offsets[] = { 1, 4, 5, 10 };
offset_separator f( offsets, offsets + 4 );
tokenizer < offset_separator > tok2( napis, f );
for( tokenizer < offset_separator >::iterator it = tok2.begin(); it != tok2.end(); ++it )
cout << * it << "\n";
cout << "************ podzial przy uzyciu wlasnych separatorow podzialu: ************\n";
boost::char_separator < char > separators( "!,?" );
tokenizer < boost::char_separator < char > > tokens( napis, separators );
for( tokenizer < boost::char_separator < char > >::iterator it = tokens.begin(); it != tokens.end(); ++it )
cout << * it << "\n";
}
I wydruk z programu, w którym zobaczymy jak łatwo można iterować po fragmentach naszego napisu. Zarówno wg domyślnych separatorów (odpowiadających is_punct() || is_space()), jak i własnych separatorów. Możliwe jest też dzielenie wyrazu na podciągi o ustalonej długości/długościach.
oryginalny napis: aaaa! Czesc, jak zyjesz? U mnie spoko (poza tym ze), pracuje nad iterator_range
************ podzial przy uzyciu domyslnego tokenizera: ************
1) aaaa
2) Czesc
3) jak
4) zyjesz
5) U
6) mnie
7) spoko
8) poza
9) tym
10) ze
11) pracuje
12) nad
13) iterator
14) range
************ podzial przy uzyciu offsetu (dzielenie slow wg ilosci znakow znaki): ************
a
aaa!
Czes
c, jak zyj
e
sz?
U mni
e spoko (p
o
za t
ym ze
), pracu
j
e na
d ite
rator_rang
e
************ podzial przy uzyciu wlasnych separatorow podzialu: ************
aaaa
Czesc
jak zyjesz
U mnie spoko (poza tym ze)
pracuje nad iterator_range
Podsumowanie
W tym artykule przedstawiłem pewne operacje na tekście dostepne w C++, korzystanie z tych operacji jest możliwe przy użyciu nie tylko typu
char jako bazowego dla tekstu, ale także innych typów spełniających pewne warunki. Na początku artykułu dokonałem przypomnienia jakie operacje da się dokonać w standardzie C i C++ (w dedykowanych do tego funkcjach i klasach), następnie przedstawiłem jak uzupełnić braki w podstawowej funkcjonalności przy użyciu algorytmów uogólnionych, dostępnych również w standardzie. Potem przyszedł czas na opisanie algorytmów tekstowych biblioteki boost, do opisów starałem się dodać możliwie dużo przykładów wraz z wydrukiem programu, aby było widać dokładnie co która operacja robi, jak działa itp...
Na zakończenie umieściłem kod nagłówkowy klasy String, mojej roboty, która rozszerza niesamowicie standardowe operacje tekstowe dostępne w standardzie języka C++. Klasa ta łączy w sobie wszystkie możliwości std::string (dzięki dziedziczeniu po tym), oraz wiele moich potrzebniejszych oczekiwań względem klasy operującej na tekście.
Przez analogię do innych języków programowania i dostępnych w nich operacji tekstowych, napisałem wiele metod dla mojej klasy. A dzięki użyciu funkcji z boost/algorithm/string wszystkie metody klasy są czytelne (mam nadzieję) i któtkie (to drugie zresztą już widać).
Z premedytacją pominąłem opis pewnej grupy funkcji dostępnych w boost::algorithm -funkcji operujących na wyrażeniach regularnych. Niemniej jednak warto żebym przynajmniej wylistował te funkcje:
Dziękuję wszystkim, którzy dotrwali do końca tego artykułu, mam nadzieję że zdobyta tutaj wiedza się wszystkim przyda!
Przykład -gotowiec
Jeszcze na zakończenie artykułu wrzucę kod rozszerzający możliwości std::string o nowe, brakujące wg mnie możliwości. Dopisałem wiele metod do mojej klasy, których brakowało mi w standardowym, C++-owym std::string, a znałem je z innych języków programowania, a dzięki wcześniej poznanym funkcjom z biblioteki boost metody klasy są bardzo krótkie. Poniższa klasa dziedziczy po std::string, przez co WSZYSTKIE OPERACJE dostępne w std::string SĄ DOSTĘPNE, nawet gdy nie jest to widoczne w kodzie. W wielu językach programowania klasa, do operowania na tekście, ma naprawdę wiele przydatnych metod, np. klasa
java.lang.String w języku java, czy
string w języku python, poniższy kod będzie zawierał analogiczne metody (jeżeli uważam je za przydatne). Licencja do poniższego kodu:
http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License.
#ifndef FULL_STRING_HPP
#define FULL_STRING_HPP
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
namespace
{
using namespace std;
using namespace boost;
using namespace boost::algorithm;
}
class String
: public string
{
public:
String()
: string()
{ }
String( const string & str )
: string( str )
{ }
String( const String & str )
: string( str )
{ }
String( const String & str, size_t pos, size_t len = npos )
: string( str, pos, len )
{ }
String( const char * s )
: string( s )
{ }
String( const char * s, size_t n )
: string( s, n )
{ }
String( size_t n, char c )
: string( n, c )
{ }
String( const string & str, size_t pos, size_t len = npos )
: string( str, pos, len )
{ }
String( char c )
: string( 1, c )
{ }
template < typename NumberCastableToString >
String( NumberCastableToString number ) throw( bad_lexical_cast )
{
* this = lexical_cast < string >( number );
}
template < class InputIterator >
String( InputIterator first, InputIterator last )
: string( first, last )
{ }
template < typename RangeT >
String( iterator_range < typename range_iterator < RangeT >::type > range )
: string( range.begin(), range.end() )
{ }
String( const ostringstream & os )
: string( os.str() )
{ }
explicit String( ifstream & file )
{
char * content_of_file = fileToCharArray( file );
string::assign( content_of_file );
delete[] content_of_file;
}
~String() { }
operator string &()
{
return static_cast < string &>( * this );
}
operator string()
{
return( * this );
}
operator const char *()
{
return c_str();
}
template < typename NumberCastableToString >
operator NumberCastableToString() throw( bad_lexical_cast )
{
return lexical_cast < NumberCastableToString >( * this );
}
operator bool()
{
return not empty();
}
unsigned operator []( const String & to_find )
{
return find( to_find );
}
template < typename ComparableType >
bool operator ==( ComparableType number )
{
try
{
return( lexical_cast < ComparableType >( * this ) == number );
}
catch( bad_lexical_cast & e )
{
return false;
}
}
template < typename ComparableType >
bool operator <( ComparableType & c )
{
return * this < String( c );
}
template < typename TypeWithAvailableStreamOperator >
String & operator <<( TypeWithAvailableStreamOperator value )
{
ostringstream os;
os << value;
string::append( os.str() );
return * this;
}
template < typename TypeWithAvailableStreamOperator >
String & operator >>( TypeWithAvailableStreamOperator & value )
{
istringstream is( * this );
is >> value;
long unsigned characters_to_remove = is.tellg();
erase( 0, characters_to_remove );
return * this;
}
template < typename T >
String & operator ()( T & new_content )
{
assign( String( new_content ) );
return * this;
}
template < typename CastableType >
String operator +( const CastableType & value )
{
String out( * this );
out.append( String( value ) );
return out;
}
template < typename CastableType >
String & operator +=( const CastableType & value )
{
append( String( value ) );
return * this;
}
String operator *( unsigned times )
{
String out, base( * this );
for( unsigned i = 0; i < times; ++i )
out += base;
return out;
}
String operator *=( unsigned times )
{
String base( * this );
for( unsigned i = 0; i < times; ++i )
this->append( base );
return * this;
}
String operator /( unsigned how_much ) throw( invalid_argument )
{
if( how_much == 0 )
throw invalid_argument( "You are trying to dive the String with 0!" );
return String( substr( 0, size() / how_much ) );
}
String & operator /=( unsigned how_much ) throw( invalid_argument )
{
if( how_much == 0 )
throw invalid_argument( "You are trying to dive the String with 0!" );
assign( substr( 0, size() / how_much ) );
return * this;
}
String & operator ()( const char * new_content )
{
string::assign( new_content );
return * this;
}
char & front()
{
throwOut_of_range_ifEmpty();
return at( 0 );
}
const char & front() const
{
throwOut_of_range_ifEmpty();
return at( 0 );
}
char & back()
{
throwOut_of_range_ifEmpty();
return at( size() - 1 );
}
const char & back() const
{
throwOut_of_range_ifEmpty();
return at( size() - 1 );
}
void pop_back()
{
erase_tail( * this, 1 );
}
bool is_empty()
{
return empty();
}
void toFile( String & filename, ios_base::openmode mode = ios_base::out )
{
ofstream file( filename, mode );
file << * this;
}
void toFile( const char * filename, ios_base::openmode mode = ios_base::out )
{
ofstream file( filename, mode );
file << * this;
}
String & getline( istream & is = cin, char delimiter = '\n' )
{
if( is )
{
std::getline( is, * this, delimiter );
return * this;
}
}
void print( ostream & output_stream = cout, bool add_new_line = true )
{
output_stream << * this;
if( add_new_line )
output_stream << '\n';
}
template < typename RangeT >
unsigned count( RangeT & neddle )
{
unsigned out = 0u;
for( find_iterator < string::iterator > it = make_find_iterator( * this, first_finder( neddle ) ), itEnd; it != itEnd; ++it )
++out;
return out;
}
template < typename RangeT >
unsigned count( RangeT & neddle, unsigned from, unsigned to = string::npos )
{
unsigned out = 0u;
for( find_iterator < string::iterator > it = make_find_iterator( * this, first_finder( substr( from, to ) ) ), itEnd; it != itEnd; ++it )
++out;
return out;
}
unsigned indexOf( char c )
{
find( c );
}
unsigned lastIndexOf( char c )
{
rfind( c );
}
String & fill( unsigned width = string::npos, char filler = ' ' )
{
resize( width );
std::fill( begin(), end(), filler );
return * this;
}
String & expandtabs( unsigned tabsize = 4, char char_to_replace = ' ' )
{
boost::algorithm::replace_all( * this, String( '\t' ), String( tabsize, char_to_replace ) );
return * this;
}
String & to_upper()
{
boost::algorithm::to_upper( * this );
return * this;
}
String & to_lower()
{
boost::algorithm::to_lower( * this );
return * this;
}
bool starts_with( String & s, bool ignore_cases = false ) const
{
if( ignore_cases )
return boost::algorithm::istarts_with( * this, s );
else
return boost::algorithm::starts_with( * this, s );
}
bool ends_with( String & s, bool ignore_cases = false ) const
{
if( ignore_cases )
return boost::algorithm::iends_with( * this, s );
else
return boost::algorithm::ends_with( * this, s );
}
bool contains( String & s, bool ignore_cases = false ) const
{
if( ignore_cases )
return boost::algorithm::icontains( * this, s );
else
return boost::algorithm::contains( * this, s );
}
bool lexicographical_compare( String & s, bool ignore_cases = false ) const
{
if( ignore_cases )
return boost::algorithm::ilexicographical_compare( * this, s );
else
return boost::algorithm::lexicographical_compare( * this, s );
}
bool is_alpha() const
{
return all( * this, boost::algorithm::is_alpha() );
}
bool is_alnum() const
{
return all( * this, boost::algorithm::is_alnum() );
}
bool is_digit() const
{
return all( * this, boost::algorithm::is_digit() );
}
bool is_space() const
{
return all( * this, boost::algorithm::is_space() );
}
bool is_punct() const
{
return all( * this, boost::algorithm::is_punct() );
}
bool is_upper() const
{
return all( * this, boost::algorithm::is_upper() );
}
bool is_lower() const
{
return all( * this, boost::algorithm::is_lower() );
}
template < typename RangeT >
bool is_any_of( RangeT & range ) const
{
return all( * this, boost::algorithm::is_any_of( range ) );
}
template < typename CharT >
bool is_from_range( CharT from, CharT to ) const
{
return all( * this, boost::algorithm::is_from_range( from, to ) );
}
String & trim_left()
{
boost::algorithm::trim_left( * this );
return * this;
}
String & trim_right()
{
boost::algorithm::trim_right( * this );
return * this;
}
String & trim()
{
boost::algorithm::trim( * this );
return * this;
}
template < typename RangeT >
String find_first( RangeT & range, bool ignore_cases = false )
{
if( ignore_cases )
return String( boost::algorithm::ifind_first( * this, range ) );
return String( boost::algorithm::find_first( * this, range ) );
}
template < typename RangeT >
String find_last( RangeT & range, bool ignore_cases = false )
{
if( ignore_cases )
return String( boost::algorithm::ifind_last( * this, range ) );
return String( boost::algorithm::find_last( * this, range ) );
}
template < typename RangeT >
String find_nth( RangeT & range, int Nth, bool ignore_cases = false )
{
if( ignore_cases )
return String( boost::algorithm::ifind_nth( * this, range, Nth ) );
return String( boost::algorithm::find_nth( * this, range, Nth ) );
}
String head( int length = 1 )
{
return String( boost::algorithm::find_head( * this, length ) );
}
String tail( int length = 1 )
{
return String( boost::algorithm::find_tail( * this, length ) );
}
template < typename PredicateT >
String find_token( PredicateT property_of_text, token_compress_mode_type eCompress = token_compress_off )
{
return String( boost::algorithm::find_token( * this, property_of_text, eCompress ) );
}
template < typename FinderT >
String find_with_finder( const FinderT & finder )
{
return String( boost::algorithm::find( * this, finder ) );
}
template < typename SequenceSequenceT, typename PredicateT >
SequenceSequenceT & split( SequenceSequenceT & Result, PredicateT Pred, token_compress_mode_type eCompress = token_compress_off ) const
{
return boost::algorithm::split( Result, * this, Pred, eCompress );
}
template < typename SequenceSequenceT, typename Range1T >
String join( const SequenceSequenceT & Input, Range1T separator = ", " ) const
{
return String( boost::algorithm::join( * this, separator ) );
}
template < typename SequenceSequenceT >
SequenceSequenceT & split_lines( SequenceSequenceT & Result, token_compress_mode_type eCompress = token_compress_off ) const
{
return boost::algorithm::split( Result, * this, boost::algorithm::is_any_of( "\n" ), eCompress );
}
template < typename Range1T, typename Range2T >
String & replace_first( const Range1T & neddle, const Range2T & new_string, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ireplace_first( * this, neddle, new_string );
else
boost::algorithm::replace_first( * this, neddle, new_string );
return * this;
}
template < typename Range1T, typename Range2T >
String & replace_last( const Range1T & neddle, const Range2T & new_string, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ireplace_last( * this, neddle, new_string );
else
boost::algorithm::replace_last( * this, neddle, new_string );
return * this;
}
template < typename Range1T, typename Range2T >
String & replace_nth( const Range1T & neddle, int Nth, const Range2T & new_string, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ireplace_nth( * this, neddle, Nth, new_string );
else
boost::algorithm::replace_nth( * this, neddle, Nth, new_string );
return * this;
}
template < typename Range1T, typename Range2T >
String & replace_all( const Range1T & neddle, const Range2T & new_string, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ireplace_all( * this, neddle, new_string );
else
boost::algorithm::replace_all( * this, neddle, new_string );
return * this;
}
template < typename FinderT, typename FormatterT >
String & find_with_finder_and_replace_with_formatter( FinderT Finder, FormatterT Formatter )
{
boost::algorithm::find_format( * this, Finder, Formatter );
return * this;
}
template < typename FinderT, typename FormatterT >
String & find_with_finder_and_replace_with_formatter_all( FinderT Finder, FormatterT Formatter )
{
boost::algorithm::find_format_all( * this, Finder, Formatter );
return * this;
}
template < typename RangeT >
String & erase_first( const RangeT & neddle, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ierase_first( * this, neddle );
else
boost::algorithm::erase_first( * this, neddle );
return * this;
}
template < typename RangeT >
String & erase_last( const RangeT & neddle, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ierase_last( * this, neddle );
else
boost::algorithm::erase_last( * this, neddle );
return * this;
}
template < typename RangeT >
String & erase_nth( const RangeT & neddle, int Nth, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ierase_nth( * this, neddle, Nth );
else
boost::algorithm::erase_nth( * this, neddle, Nth );
return * this;
}
template < typename RangeT >
String & erase_all( const RangeT & neddle, bool ignore_cases = false )
{
if( ignore_cases )
boost::algorithm::ierase_all( * this, neddle );
else
boost::algorithm::erase_all( * this, neddle );
return * this;
}
private:
char * fileToCharArray( ifstream & file )
{
if( file )
{
filebuf * pbuf = file.rdbuf();
long file_size = pbuf->pubseekoff( 0, ios::end, ios::in );
file.seekg( 0 );
char * content_of_file = new char[ file_size + 1 ];
file.read( content_of_file, file_size );
return content_of_file;
}
else
return NULL;
}
void throwOut_of_range_ifEmpty() const throw( out_of_range )
{
if( size() == 0 )
throw out_of_range( "The String has less than one element!" );
}
};
#endif
Bibliografia
http://www.boost.org/doc/libs/1_54_0/doc/html/string_algo.html - Oficjalny tutorial boost::algorithmhttp://www.boost.org/doc/libs/1_54_0/doc/html/string_algo/quickref.html -znajdują się tutaj wylistowane metody i obiekty, dostępne w boost::algorithm, do operowania na tekście, wraz z krótkim opisemforum Stack Overflow na którym znajdywałem mnóstwo inspiracji, przykładów, rozwiązań problemów, napotkanych podczas pisania tego artykułu"Introduction to the Boost C++ Libraries; Volume I - Foundations", autorstwa Roberta Demminga, rozdziały o algorithm::string, lexical_cast, tokenizer
dokumentacja C++, jako 'zawsze konieczna składowa' pisania o C++