Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Inne artykuły

Praca z tekstem przy użyciu dostępnych narzędzi w C++

[artykuł] Operowanie na napisach przy użyciu dostępnych narzędzi tekstowych w C++. Omawiane narzędzia: std::string, boost/algorithm/string, lexical_cast, tokenizer.

Zawartość artykułu

  • Intro do artykułu
  • Powtórzenie standardowych możliwości operowania na tekście w C++ (klasa std::string)
  • Powtórzenie standardowych możliwości operowania na tekście w C (nagłówki cstring, cctype, cstdlib, cstdio), które dodają pewne dodatkowe możliwości ponad możliwości klasy std::string z języka C++
  • Strumienie zapisu z/do obiektu std::string w C++
  • Algorytmy uogólnione języka C++ i ich zastosowanie względem tekstu
  • boost/algorithm/string.hpp -podstawowe metody:
  •     - zmiana wielkości liter
  •     - czy wyraz zawiera się w innym wyrazie,
  •     - czy wyraz zaczyna/kończy się innym wyrazem,
  •     - czy napis ma określoną własność (np. składa się wyłącznie z liter/cyfr)
  •     - przycinanie teksty z lewej/prawej, lub z obu stron
  • Funkcje biblioteki boost do operowania na tekście, przy użyciu iteratorów
  •     - wyszukiwanie,
  •     - zastępowywanie podciągów innymi podciągami,
  •     - dzielenie wg ograniczników,
  •     - łączenie kolekcji/kontenera do postaci napisu,
  •     - wymazywanie podciągów
  •     - iterowanie po wynikach wyszukiwania
  • lexical_cast -proste rzutowanie między typami liczbowymi i tekstowymi
  • finders & formatters (obiekty wyszukujące i zamieniające na własny sposób)
  • Operowanie na własnościach tekstowych -przykłady
  • tokenizer -łatwy sposób wyświetlania poszczególnych wyrazów w ciągu znaków
  • Dodatek -klasa String zawierająca przedstawione tutaj możliwości

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ę
C/C++
explicit string(); // konstruktor domyślny/bezargumentowy.
string( const string & str ); // konstruktor kopiujący
string( const string & str, size_t kopiuj_od, size_t dlugosc_do_skopiowania = npos ) throw( out_of_range ); // kopiowanie danych z innego stringa od określonej pozycji i w określonej ilości (lub do końca kopiowanego stringa), gdy pozycja jest większa niż długość stringa jest wyrzucany wyjątek out_of_range
string( const char * s ); // tworzenie instancji stringa z c-stringa
string( const char * s, size_t N ); // tworzenie instancji stringa z c-stringa kopiując N znaków
string( size_t N, char c ); // tworzenie stringa o długości N zawierającego znaki c
template < class InputIterator >
string( InputIterator first, InputIterator last ); // tworzenie stringa z iteratorów (po sekwencji znaków)

/** Dodatkowo w nowym standardzie C++11 dostępne są jeszcze 2 konstruktory: **/
string( initializer_list < char > il ); // - z listy inicjacyjnej
string( string && str ) noexcept; // - konstruktor przenoszący

Teraz opisowy przegąd funkcjonalności klasy std::string
Mają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.
C/C++
int stoi( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 ); // funkcja konwertuje napis na liczbę w podanym systemie liczbowym, gdy indeks_za_liczba nie wynosi nullptr wtedy jest mu przypisywana pierwsza pozycja w stringu za liczbą.
long stol( const string & napis, size_t * indeks_za_liczba = 0, int system_liczbowy = 10 ); // analogicznie do powyższego, zwraca long
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::wstringa

Bardzo przydatne są jeszcze kolejne funkcje z C++11:
C/C++
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:
C/C++
size_t strspn( const char * str1, const char * str2 ); // zwraca ilość pierwszych liter str1 zawierających jedynie litery z str2; np. strspn("2313dfsdfr454", "1234567890"); zwróci 4.
char * strtok( char * napis, const char * ciag_ogranicznikow ); // funkcja umożliwiająca przy sekwencyjnym wywoływaniu podzielenie wejściowego napisu na fragmenty oddzielone dowolnym z ciagu_ograniczników. Dzięki niej można by zaimplementować metodę znaną w innych językach o nazwie split()

/** przykładowa implementacja: **/
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:
C/C++
int atoi( const char * napis ); // zamienia napis na int
double atof( const char * napis ); // zamienia napis na double
long int atol( const char * str ); // zamienia napis na long
long long int atoll( const char * str ); // zamienia napis na long (dostępne w C++11)
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:
C/C++
double strtod( const char * str, char ** endptr ); // funkcja ta pomijając początkowe białe znaki konwertuje napis na liczbę i ustawia wskaźnik endptr na pierwszą pozycję za liczbą
unsigned long int strtoul( const char * str, char ** endptr, int base ); // jak wyżej, ale jako trzeci argument przyjmuje system liczbowy (np. 2, 4, 8, 10, 16, nawet 0-wtedy zgaduje system liczbowy).
unsigned long int strtoul( const char * str, char ** endptr, int base ); // jw. ale zwraca unsigned long

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:
C/C++
std::ostringstream os;
os << napis1 << '\t' << liczba << '\t' << obiekt << endl;
cout << os.str(); // zostanie zwrócony obiekt klasy std::string zawierający powyższe elementy oddzielone '\t'

W drugą stronę mamy std::istringstream, który przy użyciu operatora>> zapisuje do zmiennych zawartość z obiektu
std::string, przykład:
C/C++
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:
  • Używamy rzutowania -funkcje rzutujemy do tego samego typu jaki ma:
    static_cast < int( * )( int ) >( f );
    .
  • Możemy też użyć funkcji pakującej funkcję w obiekt-funktor: std::ptr_fun(), dla naszej funkji f() wyglądać to będzie tak:
    ptr_fun < int, int >( f );
  • Wskazać kompilatorowi, że chodzi o funkcje globalną poprzez operator zakresu ::.

Przykładowe problemy z przykładowymi rozwiązaniami

1. Sprawdzenie czy napis (z założenia długi) zawiera jakieś znaki interpunkcyjne.
podpowiedź:
... find_if(...);
int ispunct(int c);

Przykładowe rozwiązanie:
C/C++
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.
podpowiedź:
... count(...);
rozwiązanie:
C/C++
string s( "Ciekawe ile tutaj jest perwszych liter alfabetu?" );
cout << count( s.begin(), s.end(), 'a' ) << endl; // wydruk: 4

3. Zliczenie cyfr w napisie:
podpowiedź:
... count_if(...);
int isdigit(int c);
Przykładowe rozwiązanie:
C/C++
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:
podpowiedź (dla rozwiązania w C++11):
... find_if_not(...);
int isprint(int c);
wyrażenia lambda
Przykładowe rozwiązanie:
C/C++
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.
podpowiedź:
... transform(...);
int tolower(int c);
int toupper(int c);
Przykładowe rozwiązanie:
C/C++
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:
podpowiedź:
void replace(...)
proponowane rozwiazanie:
C/C++
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.
podpowiedź:
void replace_if(...)
int isspace(int c);
proponowane rozwiazanie:
C/C++
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:
podpowiedź:
... count_if(...)
szablony funkcji
proponowane rozwiązanie (przy używaniu szablonów nie musimy używać ani rzutowań ani ptr_fun<>):
C/C++
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):
C/C++
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:
C/C++
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):
C/C++
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:
podpowiedź:
pair<...> mismatch(...)
proponowane rozwiązanie (gdy wiemy że dwa napisy są różne i że mają taką samą długość):
C/C++
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-stdstring
napisanym 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):
C/C++
// trim from start
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;
}

// trim from end
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;
}

// trim from both ends
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")
podpowiedź:
... count_if(...)
template<typename T> struct equal_to;
template <class Operation, class T>   binder2nd<Operation> bind2nd (const Operation& op, const T& x);
przykładowe rozwiązanie:
C/C++
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:
podpowiedź: każdy ma swój własny sposób, ja użyłem iteratorów i copy()
C/C++
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:
  • z suffixem "_copy" - zwracają zmieniony string, a oryginał pozostaje niezmieniony
  • bez suffixu "_copy" (jak w konwencji nazewniczej std) - zmieniają argumenty, a zwracają void
  • z przedrostkiem "i" - wtedy są to operacje niezależne od wielkości liter, ponadto mogą mieć lub nie mieć suffixa "_copy"
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:
C/C++
#include<boost/algorithm/string/case_conv.hpp>
//...
string napis( "ZacZyNaMy ZAbAWE" );
to_upper( napis ); // zmodyfikuje oryginalny napis do postaci: "ZACZYNAMY ZABAWE"
to_lower( napis ); // zmodyfikuje oryginalny napis do postaci: "zaczynamy zabawe"
napis = "ZacZyNaMy ZAbAWE";
to_upper_copy( napis ); // funkcja zwroci "ZACZYNAMY ZABAWE", oryginalny napis nie zostanie zmieniony
to_lower_copy( napis ); // funkcja zwroci "zaczynamy zabawe", oryginalny napis nie zostanie zmieniony

Czy string zaczyna/kończy się innym stringiem

C/C++
#include<boost/algorithm/string/predicate.hpp>
...
string napis( "ZacZyNaMy ZAbAWE" );
starts_with( napis, "Zac" ); // tak tez sie zaczyna, zostanie zwrocone true
ends_with( napis, "AWE" ); // tak tez sie kończy, zostanie zwrocone true
istarts_with( napis, "zAc" ); // pomijając wielkość liter tak się zaczyna, zostanie zwrocone true
iends_with( napis, "awE" ); // pomijając wielkość liter tak się kończy, zostanie zwrocone true

Czy jeden string zawiera/równa się drugiemu

C/C++
#include<boost/algorithm/string/predicate.hpp>
...
contains( string( "Ala ma kangury" ), string( "Ala" ) ); // wyraz "Ala" jest zawarty w zdaniu "Ala ma kangury", więc zostanie zwrócone true
icontains( string( "Ala ma kangury" ), string( "alA" ) ); // wyraz "alA" jest zawarty w zdaniu "Ala ma kangury", chociaż aby go wykryć musimy zignorować różnice w wielkości liter, ta funkcja sprawdza niezależnie od wielkości liter, więc zostanie zwrócone true

equals( string( "Ala" ), string( "Ala" ) ); // na pierwszy rzut oka jest to zwyczajny operator porównania, zwróci to co: string("Ala")==string("Ala")
iequals( string( "Ala" ), string( "alA" ) ); // porównywanie ale ignorując wielkość liter, więc w tym przypadku w przeciwieństwie do zwykłego equals zwróci true

// porównywanie leksykograficzne - idziemy znak po znaku porównując kody odpowiadające poszczególnym literom, zupełnie jak (string("aLe_baran")<string("Ale_gazda")), zostanie zwrócone false, gdyż 'a' (nr 97)> 'A' (nr 65)
lexicographical_compare( string( "aLe_baran" ), string( "Ale_gazda" ) ); // jak w powyższym przypadku zostanie zwrócone false
ilexicographical_compare( string( "aLe_baran" ), string( "Ale_gazda" ) ) // w tym przypadku wielkie i małe litery mają taki sam nr, zupełnie jakby napisać lexicographical_compare(to_lower_copy(string("aLe_baran")), to_lower_copy(string("Ale_gazda")));

all( string( "napis" ), is_alpha() ); // sprawdzi czy napis sklada sie wylacznie z liter. Jest tu użyty tzn. predykat (własność tekstu), o nich za chwilę

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:
C/C++
template < typename RangeT > unspecified is_any_of( const RangeT & Set );
template < typename CharT > unspecified is_from_range( CharT From, CharT To );
/**Ich bezpośrednie użycie nie byłoby zbyt wygodne:**/
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)

C/C++
#include<boost/algorithm/string/trim.hpp>
...
template < typename SequenceT >
void trim_left( SequenceT & Input ); // przycina białe znaki oryginalnego napisu z lewej strony
template < typename SequenceT >
void trim_right( SequenceT & Input ); // przycina białe znaki oryginalnego napisu z prawej strony

template < typename SequenceT >
SequenceT trim_left_copy( const SequenceT & Input ); // zwraca napis po usunięciu z lewej strony białych znaków, oryginalny napis pozostaje niezmieniony
template < typename SequenceT >
SequenceT trim_right_copy( const SequenceT & Input ); // zwraca napis po usunięciu z prawej strony białych znaków, oryginalny napis pozostaje niezmieniony

template < typename SequenceT, typename PredicateT >
void trim_left_if( SequenceT & Input, PredicateT IsSpace ); // przycina znaki oryginalnego napisu spełniające pewne własności z lewej strony
template < typename SequenceT, typename PredicateT >
void trim_right_if( SequenceT & Input, PredicateT IsSpace ); // jw. ale z prawej strony

template < typename SequenceT, typename PredicateT >
SequenceT trim_left_copy_if( const SequenceT & Input, PredicateT IsSpace ); // zwraca napis po ucięciu znaków z lewej strony spełniających pewne własności
template < typename SequenceT, typename PredicateT >
SequenceT trim_right_copy_if( const SequenceT & Input, PredicateT IsSpace ); // jw. ale z prawej strony

// do powyższych, jednostronnych operacji są jeszcze wersje dwustronne, robiące to co powyższe funkcje ale z obydwu stron:
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

C/C++
#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,
  • zastępowywanie,
  • dzielenie wg ograniczników,
  • iterowanie po wynikach wyszukiwania

Wyszukiwanie

Pominę typ zwracany przez te funkcje (który jest iteratorem).
C/C++
#include<boost/algorithm/string/find.hpp>
//...

template < typename Range1T, typename Range2T >
...find_first( Range1T & napis, const Range2T & igla ); // wyszukuje pierwszego wystapienia igly w napisie
template < typename Range1T, typename Range2T >
...ifind_first( Range1T & napis, const Range2T & igla ); // wyszukuje pierwszego wystapienia igly w napisie z pominięciem wielkości liter
template < typename Range1T, typename Range2T >
...find_last( Range1T & napis, const Range2T & igla ); // wyszukuje ostatniego wystapienia igly w napisie
template < typename Range1T, typename Range2T >
...ifind_last( Range1T & napis, const Range2T & igla ); // wyszukuje ostatniego wystapienia igly w napisie z pominięciem wielkości liter
template < typename Range1T, typename Range2T >
...find_nth( Range1T & napis, const Range2T & igla, int Nty ); // wyszukuje N-te wystąpienie igły w tekście
template < typename Range1T, typename Range2T >
...ifind_nth( Range1T & napis, const Range2T & igla, int Nty ); // wyszukuje N-te wystąpienie igły w tekście z pominięciem wielkości liter

template < typename RangeT >
...find_head( RangeT & napis, int N_znakow ); // "wyszukuje" a dla scislosci robi substr(0, N_znakow). W razie gdyby N_znakow>napis.size() wtedy zwraca caly napis. Gdy N_znakow<0 wtedy robi: napis.substr(0, napis.size() - |N_znakow|)
template < typename RangeT >
...find_tail( RangeT & napis, int N_znakow ); // zwraca ostatnie N_znakow z konca napisu. Gdy N_znakow<0 wtedy zwróci znaki z końca w ilości napis.size() - |N_znakow|

template < typename RangeT, typename PredicateT >
...find_token( RangeT & Input, PredicateT wlasnosci, token_compress_mode_type eCompress = token_compress_off ); // znajduje token/tokeny podane jako własności np: is_digit(), ostatni parametr decyduje czy zostanie zwrócony jeden znak (domyślnie), czy wszystkie pasujące, przyległe do pierwszego znalezionego. Przykład za chwilę.

template < typename RangeT, typename FinderT >
...find( RangeT & napis, const FinderT & wyszukiwacz ); // wyszukuje wystąpienia zgodnego z obiektem służącym do wyszukiwania, o tym później


Ż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:
C/C++
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:
C/C++
cout << ifind_first( string( "dzisiaj swieci slonce, ale pada deszcz" ), "Ale" ) ) << endl; // zostanie wypisane: "ale"

Przykładowy program ilustrujący użycie funkcji wyszukujących

C/C++
#include <iostream>
#include <string>
#include<boost/algorithm/string.hpp> // (0)

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 ) ) // (1)
         cout << "1) znaleziono wyraz: " << igla << " szukajac ostatniego jego wystapienia: " << szukany << endl; // (2)
   
    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; // (3)
   
    string znaleziony( szukany.begin(), szukany.end() ); // (4)
    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; // (5)
    cout << "8) ostatnie -4 znaki napisu: " << find_tail( napis, - 4 ) << endl; // (6)
   
    cout << "9) wyszukiwanie tokenu: " << find_token( napis, not is_space() && not is_alpha() ) << endl; // (7)
}
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:
  • C/C++
    #include<boost/algorithm/string.hpp> // (0)
     - we wcześniejszych operacjach tekstowych podawałem konkretne nagłówki do konkretnych operacji, tak naprawdę nie trzeba o wszystkich pamiętać i wystarczy jedynie włączyć ten nagłówek, a on już włącza resztę operacji tekstowych.
  • C/C++
    if( szukany = find_last( napis, igla ) ) // (1)
     - konwersja do boola (gdy zjadzie true, w przeciwnym razie false).
  • C/C++
    " szukajac ostatniego jego wystapienia: " << szukany << endl; // (2)
     - iterator zwracany przez operacje wyszukiwania posiada operator<<.
  • C/C++
    ( szukany = ifind_first( napis, "dzisiaj" ) ) << endl; // (3)
     - wyszukało wyraz "Dzisiaj", którego nie wyszukało wyszukiwanie wrażliwe na wielkość znaków.
  • C/C++
    string znaleziony( szukany.begin(), szukany.end() ); // (4)
     - konstruowanie obiektu klasy std::string z iteratorów. Boostowy iterator_range ma metody begin() i end(), ale niestety nie ma operatora konwersji do std::string, więc musimy sobie z tym jakoś poradzić.
  • C/C++
    cout << "7) pierwsze -4 znaki napisu: " << find_head( napis, - 4 ) << endl; // (5)
     - zwraca pierwsze [0, napis.size() - |N| ] znaki napisu
  • C/C++
    cout << "8) ostatnie -4 znaki napisu: " << find_tail( napis, - 4 ) << endl; // (6)
     - zwraca ostatnie [0, napis.size() - |N| ] znaki napisu
  • C/C++
    cout << "9) wyszukiwanie tokenu: " << find_token( napis, not is_space() && not is_alpha() ) << endl; // (7)
     -wyszukuje czegoś co nie jest białym znakiem i nie jest literą

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:
C/C++
#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

C/C++
#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:
    split( owoce_warzywa, napis, is_punct() || is_space() ); // (1)
    cout << "1) Operacja split przy wylaczonej kompresji 'token_compress_off' daje: ";
    copy( owoce_warzywa.begin(), owoce_warzywa.end(), ostream_iterator < string >( cout, "/" ) ); // (2)
    cout << endl;
   
    split( owoce_warzywa, napis, is_punct() || is_space(), token_compress_on ); // (3)
    cout << "2) Operacja split przy wlacznonej kompresji 'token_compress_off' daje: ";
    copy( owoce_warzywa.begin(), owoce_warzywa.end(), ostream_iterator < string >( cout, "/" ) );
    cout << endl;
   
    //////////////////////////////// join:
    vector < string > pseudonimy_zwyciezcow_konkursu;
    pseudonimy_zwyciezcow_konkursu << "Zimny1" << "Krzywy90" << "PostrachOceanow" << "ProPlayer13" << "Wymiatasz";
    cout << "3) Zwyciescy gracze w konkursie: " << join( pseudonimy_zwyciezcow_konkursu, "/" ) << endl; // (4)
    cout << "4) Gracze o fajniejszych pseudonimach: " << join_if( pseudonimy_zwyciezcow_konkursu, "/", is_string_alphaOrSpace ) << endl; // (5)
}
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:
  • C/C++
    split( owoce_warzywa, napis, is_punct() || is_space() ); // (1)
     to wywołanie wyszuka w napisie punkty(m.in. '.') i białe znaki-separatory i litery między każdymi separatorami wrzuci jako kolejną pozycję vectora, jak widzimy z domyślną opcją gdy separatory są obok siebie to do vectora wrzucane są puste pozycje
  • C/C++
    copy( owoce_warzywa.begin(), owoce_warzywa.end(), ostream_iterator < string >( cout, "/" ) ); // (2)
     - w ten sposób drukuję na strumieć std::cout cały vector, oddzielając elementy znakiem '/'
  • C/C++
    split( owoce_warzywa, napis, is_punct() || is_space(), token_compress_on ); // (3)
     -wywołanie jak w (1), ale tym razem mamy włączoną kompresję, przez co pare separatorów obok sieie nie jest traktowana jako parę, tylko jako jeden
  • C/C++
    cout << "3) Zwyciescy gracze w konkursie: " << join( pseudonimy_zwyciezcow_konkursu, "/" ) << endl; // (4)
     - jak widać już nie musimy używać copy aby wyświetlić kolekcję na ekran, wystarczy użyć metody join() i otrzymujemy gotowy string do wypisania
  • C/C++
    cout << "4) Gracze o fajniejszych pseudonimach: " << join_if( pseudonimy_zwyciezcow_konkursu, "/", is_string_alphaOrSpace ) << endl; // (5)
    O tym wywołaniu można by troszeczkę powiedzieć -chcemy aby połączone do wynikowego stringa były tylko te elementy, które składają się z liter lub białych znaków, najwygodniej byłoby napisać coś takiego:
    join_if( pseudonimy_zwyciezcow_konkursu, "/", is_space() || is_alpha() );
    , niestety is_space przyjmuje 1 znak, a nie cały wyraz. Można by więc to zrobić przy użyciu wyrażeń lambda z C++11:
    join_if( pseudonimy_zwyciezcow_konkursu, ", ",[]( const string & s ) { return all( s, is_space() || is_alpha() ); } )
    , lub poprzez podanie własnej funkcji tak jak ja to zrobiłem, zabawy w wyrażenia lambda bez wyrażeń lambda są dość nieprzyjemne:
    C/C++
    typedef boost::algorithm::detail::is_classifiedF ClassifiedF;
    join_if( pseudonimy_zwyciezcow_konkursu, "/", bind( all < string, ClassifiedF >, _1, is_alpha() ) );

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:
C/C++
#include<boost/algorithm/string/replace.hpp>
template < typename SequenceT, typename Range1T, typename Range2T >
void replace_first( SequenceT & napis, const Range1T & szukany, const Range2T & nowy ); // zastępuje w napisie pierwsze wystąpienie szukanego ciągu przez nowy ciąg
template < typename SequenceT, typename Range1T, typename Range2T >
SequenceT replace_first_copy( const SequenceT & napis, const Range1T & szukany, const Range2T & nowy ); // zwraca nowy napis z zamienioną szukaną frazą na nową frazę
template < typename SequenceT, typename Range1T, typename Range2T >
void ireplace_first( SequenceT & napis, const Range1T & szukany, const Range2T & nowy ); // analogicznie do replace_first, ale wyszukuje bez wrażliwości na wielkość znaków
template < typename SequenceT, typename Range2T, typename Range1T >
SequenceT ireplace_first_copy( const SequenceT & napis, const Range1T & szukany, const Range2T & nowy ); // analogicznie do replace_first_copy, ale wyszukiwanie odbywa się bez wrażliwości na wielkość znaków

#include<boost/algorithm/string/erase.hpp>
template < typename SequenceT, typename RangeT >
void erase_first( SequenceT & napis, const RangeT & szukany ); // wymazywanie pierwszego wystąpienia szukanego podciągu w oryginalnym napisie
template < typename SequenceT, typename RangeT >
SequenceT erase_first_copy( const SequenceT & napis, const RangeT & szukany ); // funkcja zwraca kopię napisu z wymazanym pierwszym wystąpieniem szukanego podciągu
template < typename SequenceT, typename RangeT >
void ierase_first( SequenceT & napis, const RangeT & szukany ); // jak erase_first ale bez wrażliwości na wielkość znaków
template < typename SequenceT, typename RangeT >
SequenceT ierase_first_copy( const SequenceT & napis, const RangeT & szukany ); // jak erase_first_copy ale bez wrażliwości na wielkość znaków
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ę.
C/C++
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.
C/C++
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ę.
C/C++
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:
C/C++
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

C/C++
#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ą:
C/C++
#include<boost/lexical_cast.hpp>

template < typename Target, typename Source >
Target lexical_cast( const Source & arg );
I od razu przykład:
C/C++
#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"; // (1)
    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"; // (2)
   
    napis = "aaabBbccc";
    string napis2 = lexical_cast < string >( ifind_first( napis, "bbb" ) ); // (3)
    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 ) ); // (4)
    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:
  • typeid( napis ).name() << "'\n";
     -dynamiczne wykrywanie typów, oraz wypisywanie symbolicznej nazwy typu (dlatego widzimy 'Ss')
  • C/C++
    lexical_cast < string >( numeric_limits < long long unsigned >::max() ) << "'\n"; // (2)
    -numeric_limits<T> zawieta własności typów numerycznych takie jak maksymalna/minimalna wartość i inne
  • C/C++
    string napis2 = lexical_cast < string >( ifind_first( napis, "bbb" ) ); // (3)
    -jak widzimy możliwa jest również konwersja iterator_range (obiekt trzymający iteratory do wyszukanego fragmentu takstu) do obiektu klasy std::string
  • C/C++
    liczba_double = lexical_cast < double >( find_token( napis, is_digit() || is_punct(), token_compress_on ) ); // (4)
    -a tutaj wyszukujemy w napisie cyfr i kropek, oraz rzutujemy bezpośrednio na liczbę
W tym temacie dodam jeszcze jeden przykład, ilustrujący sytuację nieudanego rzutowania:
C/C++
#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:
C/C++
#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:
C/C++
#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:
C/C++
#include<boost/algorithm/string/finder.hpp>
template < typename RangeT >
unspecified first_finder( const RangeT & Search ); // obiekt wyszukiwania wyszukuje pierwszego wystąpienia w danym napisie, jest zwracany iterator_range
template < typename RangeT, typename PredicateT >
unspecified first_finder( const RangeT & Search, PredicateT Comp ); // jw. ale przyjmuje funktor którym porównuje np.: is_equal()
template < typename RangeT >
unspecified last_finder( const RangeT & Search ); // jw. ale ostatnie wystąpienie
template < typename RangeT, typename PredicateT >
unspecified last_finder( const RangeT & Search, PredicateT Comp ); // jw. ale przyjmuje funktor którym porównuje otrzymany napis z przeszukiwanym tekstem
template < typename RangeT >
unspecified nth_finder( const RangeT & Search, int Nth ); // wyszukuje N-te wystąpienie
template < typename RangeT, typename PredicateT >
unspecified nth_finder( const RangeT & Search, int Nth, PredicateT Comp ); // jw. ale przyjmuje funktor którym porównuje otrzymany napis z przeszukiwanym tekstem
unspecified head_finder( int N ); // zwraca pierwsze N znaków z napisu
unspecified tail_finder( int N ); // zwraca ostatnie N znaków z napisu
template < typename PredicateT >
unspecified token_finder( PredicateT Pred, token_compress_mode_type eCompress = token_compress_off ); // wyszukuje token wg podanych własności. Zwracany jest iterator_range<>, ale zależnie od parametru eCompress może być zwrócony jeden 'token', lub wiele (które ze sobą sąsiadują). To wyszukiwanie przypomina std::find_if
template < typename ForwardIteratorT >
unspecified range_finder( ForwardIteratorT Begin, ForwardIteratorT End ); // ten wyszukiwacz jest "najbardziej skomplikowany" i "godny zaufania" -zwraca dokładnie to co przyjął :D
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:
C/C++
#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:
C/C++
#include<boost/algorithm/string/formatter.hpp>
template < typename RangeT >
unspecified const_formatter( const RangeT & Format ); // zwraca to co otrzymal
template < typename RangeT >
unspecified identity_formatter(); // zwraca parametr
template < typename RangeT >
unspecified empty_formatter( const RangeT & ); // zwraca pustą sekwencję, podany argument jest ignorowany, ale warto go podać -automatyczna dedukcja typu szablonu funkcji

    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):
C/C++
#include <boost/algorithm/string/find.hpp>

template < typename RangeT, typename FinderT >
iterator_range < typename range_iterator < RangeT >::type >
find( RangeT & Input, const FinderT & Finder ); // funkcja przyjmuje napis i obiekt wyszukujący i zwraca znaleziony zakres
Poniższe funkcje z rodziny find_format* wyszukują przy użyciu findera i zastępują formatterem.
C/C++
#include<boost/algorithm/string/find_format.hpp>
template < typename SequenceT, typename FinderT, typename FormatterT >
void find_format( SequenceT & Input, FinderT Finder, FormatterT Formatter ); // w oryginalnym napisie wyszukuje Finderem pierwsze wystąpienie i zastępuje Formatterem
template < typename SequenceT, typename FinderT, typename FormatterT >
SequenceT find_format_copy( const SequenceT & Input, FinderT Finder, FormatterT Formatter ); // zwraca napis z pierwszym podciągiem wyszukanym Finderem i zastąpionym Formatterem
template < typename SequenceT, typename FinderT, typename FormatterT >
void find_format_all( SequenceT & Input, FinderT Finder, FormatterT Formatter ); // w oryginalnym napisie wyszukuje Finderem wszystkie wystąpienia i zastępuje Formatterem
template < typename SequenceT, typename FinderT, typename FormatterT >
SequenceT find_format_all_copy( const SequenceT & Input, FinderT Finder, FormatterT Formatter ); // zwraca napis ze wszystkimi podciągami wyszukanymi Finderem i zastąpionymi Formatterem

Poza tym kolejne funkcje -iterujące po wynikach wyszukiwania:
C/C++
#include <boost/algorithm/string/iter_find.hpp>

template < typename SequenceSequenceT, typename RangeT, typename FinderT >
SequenceSequenceT & iter_find( SequenceSequenceT & Result, RangeT & Input, FinderT Finder ); // w podanym napisie wyszukuje wszystkie wystapienia szukanego tekstu przy uzyciu obiektu wyszukujacego

template < typename SequenceSequenceT, typename RangeT, typename FinderT >
SequenceSequenceT & iter_split( SequenceSequenceT & Result, RangeT & Input, FinderT Finder ); // w podanym napisie wyszukuje i zwraca wszystkie separatory w podanym tekscie

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:
C/C++
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:
C/C++
#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 ) // (1)
         napis.replace( it->begin(), it->end(), "od" ); // (2)
   
    cout << "Napis po zmianach: " << napis << endl;
   
    for( find_iterator < string::iterator > it = make_find_iterator( napis, token_finder( is_punct() ) ), itEnd; it != itEnd; ++it ) // (3)
         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 ) // (4)
         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:
  • for( find_iterator < string::iterator > it = make_find_iterator( napis, first_finder( "dla" ) ), itEnd; it != itEnd; ++it )
     -tworzę tutaj find_iterator i iteruję nim po wynikach wyszukiwania first_findera (czli po wszystkich wystąpieniach "dla" z uzwględnieniem wrażliwości na wielkość liter).
  • napis.replace( it->begin(), it->end(), "od" );
     -trzeba uważać z taką operację. Gdyby oryginalny napis się zmienił po pobraniu iteratorów to mogło by być nieciekawie.
  • C/C++
    for( find_iterator < string::iterator > it = make_find_iterator( napis, token_finder( is_punct() ) ), itEnd; it != itEnd; ++it ) // (3)
    -tutaj tworzę kolejny iterator, a ten wyszukuje wszystkie przecinki
  • C/C++
    for( split_iterator < string::iterator > it = make_split_iterator( napis, first_finder( ". " ) ), itEnd; it != itEnd; ++it ) // (4)
    -tutaj tworzę split_iterator, który wskazuje na przestrenie między poszczególnymi separatorami, u nas kropkami
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):
C/C++
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:
C/C++
#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;
   
    /** find: **/
    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; // (1)
   
    cout << "Wyszukanie pierwszego wystąpienia slowa 'deszcz', z pominieciem wielkosci liter: " << find( napis, first_finder( "deszcz", is_iequal() ) ) << endl; // (2)
    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: **/
    find_format_all( napis, first_finder( "deszcz", is_iequal() ), const_formatter( "grad" ) ); // (3)
    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; // (4)
    cout << "identity_formatter<string>(napis): " << identity_formatter < string >()( napis ) << endl;
    // cout << "empty_formatter(dzwoni)(napis): " << empty_formatter("dzwoni")(napis) << endl; // (5)
   
    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:

  • C/C++
    cout << "Napis po modufikacjach: " << napis << endl; // (1)
    -tutaj tylko wypisujemy nasz napis, ale obszar, wskazywany przez iterator wcześniej znaleziony, został zmieniony przez operację to_upper()
  • C/C++
    cout << "Wyszukanie pierwszego wystąpienia slowa 'deszcz', z pominieciem wielkosci liter: " << find( napis, first_finder( "deszcz", is_iequal() ) ) << endl;
    -w gruncie rzeczy nie jest tu potrzebne wyjaśnienie poza tym co widać w kodzie. Natomiast zwróciłen na to uwagę, gdyż widzimy tu analogię do wcześniej przedstawionych funkcji wyszukujących. Dodam jeszcze że ta operacja find() "tylko wyszukuje", natomiast operacje z rodziny find_format() już zamieniają wyszukany tekst.
  • C/C++
    find_format_all( napis, first_finder( "deszcz", is_iequal() ), const_formatter( "grad" ) ); // (3)
    -w tej operacji wyszukujemy każdy "deszcz" i zamieniamy go na "grad" (niezależnie od wielkości liter). Ponadto działamy na oryginalnym napisie zmieniając go. W następnych linijkach używamy innych formatterów, które zmieniają operacje zastępowania na tekście przez funkcje find_format* (const_formatter -zamienia na podany argument, empty_formatter-po prostu czyści znaleziony fragment, a identity_formatter jak widać nic nie wpływa na tekst).
  • C/C++
    cout << "const_formatter('dzwoni')(napis): " << const_formatter( "dzwoni" )( napis ) << endl; // (4)
    -chciałem tutaj zwrócić uwagę na samo zachowanie poszczególnych formatterów, każdy z nich zwraca inny rezultat, przez co wyniki (przekazane do operatora<<) są zupełnie inne.
  • C/C++
    // cout << "empty_formatter(dzwoni)(napis): " << empty_formatter("dzwoni")(napis) << endl; // (5)
    jak widać empty_formatter zwraca boost::algorithm::detail::empty_container<char>, którego niestety nie można przekazać bezpośrednio do operatora<<, dlatego muszę sobie poradzić inaczej chcąc wyświetlenia jakże "obszernej zawartości".

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"):
C/C++
#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ą:
C/C++
#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:
C/C++
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ć:
C/C++
#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.
C/C++
#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:
    /** ************* Constructors: ************* **/
    /** constructors as in std::string: **/
    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 )
    { }
   
    /** extra constructors: **/
    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() { }
   
    /** ************* Conversion operators: ************* **/
    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();
    }
   
    /** Operators: **/
    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;
    }
   
    /** methods which has std::string in C++11: **/
    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 );
    }
   
    /** additional methods: */
    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;
    }
   
    /** Methods thanks to boost::algorithm **/
    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 );
       
    }
   
    /** Properties of entire text: **/
    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 ) );
    }
   
    /** Trimming: **/
    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;
    }
   
    /** Finding: **/
    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 ) );
    }
   
    /** Split & join: **/
    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 );
    }
   
    /** Replacing: **/
    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;
    }
   
    /** Erasing: **/
    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  // FULL_STRING_HPP

Bibliografia

http://www.boost.org/doc/libs​/1_54_0/doc/html​/string_algo.html - Oficjalny tutorial boost::algorithm
http://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 opisem
forum 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++