2014-07-01 15 views
21

Sono interessato a discutere i metodi per l'utilizzo di stringstream per analizzare una riga con più tipi. Vorrei iniziare a guardare la seguente riga:Come verificare se l'operatore di stringstream >> ha analizzato un tipo errato e saltarlo

"2.832 1.3067 nana 1.678" 

Ora lascia supporre ho una lunga serie che ha più strings e doubles. Il modo ovvio per risolvere questo è tokenize la stringa e quindi verificare la conversione di ciascuno. Sono interessato a saltare questo secondo passaggio e usare stringstream direttamente per trovare solo i numeri.

Ho immaginato che un buon modo per avvicinarsi a questo sarebbe quello di leggere la stringa e controllare se è stato impostato il failbit, che sarà se provo a analizzare una stringa in un doppio.

Dire che ho il seguente codice:

string a("2.832 1.3067 nana 1.678"); 

stringstream parser; 
parser.str(a); 

for (int i = 0; i < 4; ++i) 
{ 
    double b; 
    parser >> b; 
    if (parser.fail()) 
    { 
     std::cout << "Failed!" << std::endl; 
     parser.clear(); 
    } 
    std::cout << b << std::endl; 
} 

Sarà stampare le seguenti:

2.832 
1.3067 
Failed! 
0 
Failed! 
0 

Non mi sorprende che non riesce ad analizzare una stringa, ma ciò che sta accadendo internamente tale che non riesce a cancellare il suo failbit e analizzare il prossimo numero?

+0

Controllare la mia risposta qui per favore: [C++ in movimento all'elemento successivo in un file.txt] (http://stackoverflow.com/a/24501035/1413395). Penso che sia rilevante. –

+0

@ πάνταῥεῖ Ahhh, ok così si blocca al primo errore. –

+1

@ πάνταῥεῖ Ho cancellato la richiesta. Dopo il tuo post suggerito ero abbastanza sicuro che fosse un duplicato. Ma se vuoi aggiungere una risposta che sarebbe grandiosa. –

risposta

14

Il seguente codice funziona bene per saltare la parolaccia e raccogliere i double valori validi

istringstream iss("2.832 1.3067 nana 1.678"); 
double num = 0; 
while(iss >> num || !iss.eof()) { 
    if(iss.fail()) { 
     iss.clear(); 
     string dummy; 
     iss >> dummy; 
     continue; 
    } 
    cout << num << endl; 
} 

Ecco un fully working sample.


tuo campione quasi capito bene, era solo mancava di consumare il campo di input non valida dal flusso dopo il rilevamento è formato sbagliato

if (parser.fail()) { 
    std::cout << "Failed!" << std::endl; 
    parser.clear(); 
    string dummy; 
    parser >> dummy; 
} 

Nel tuo caso l'estrazione tenterà di rileggere da "nana" per l'ultima iterazione, quindi le ultime due righe nell'output.

Nota anche l'inganno su iostream::fail() e su come testare effettivamente per iostream::eof() nel mio primo campione. There's a well known Q&A, perché il semplice test per EOF come condizione di loop è considerato errato. E risponde bene, come interrompere il ciclo di input quando si incontrano valori inaspettati/non validi. Ma come saltare/ignorare i campi di input non validi non è spiegato lì (e non è stato chiesto).

+0

Sì, questo è quello che ho fatto subito dopo aver collegato quella risposta per quell'altra domanda. Lavoro eccellente e veloce! +1 –

2

ho costruito una versione più messo a punto per questo, che è in grado di saltare carattere di input valido saggio (senza necessità di separare double numeri con caratteri di spazio bianco):

#include <iostream> 
#include <sstream> 
#include <string> 
using namespace std; 

int main() { 

    istringstream iss("2.832 1.3067 nana1.678 xxx.05 meh.ugh"); 
    double num = 0; 
    while(iss >> num || !iss.eof()) { 
     if(iss.fail()) { 
      iss.clear(); 
      while(iss) { 
       char dummy = iss.peek(); 
       if(std::isdigit(dummy) || dummy == '.') { 
        // Stop consuming invalid double characters 
        break; 
       } 
       else { 
        iss >> dummy; // Consume invalid double characters 
       } 
      } 
      continue; 
     } 
     cout << num << endl; 
    } 
    return 0; 
} 

uscita

2.832 
1.3067 
1.678 
0.05 

Live Demo

+0

Questo è buono, ma potresti voler rivedere il ciclo while (1). Se alla fine dell'input è presente un doppio carattere non valido, questo verrà interrotto per sempre. es. http://ideone.com/3HSyxH –

+0

@ Ben 'while (iss)' può essere? –

1

È possibile utilizzare 012.a validate ingresso simili:

#include <string> 
#include <sstream> 
#include <iostream> 

// remove white-space from each end of a std::string 
inline std::string& trim(std::string& s, const char* t = " \t") 
{ 
    s.erase(s.find_last_not_of(t) + 1); 
    s.erase(0, s.find_first_not_of(t)); 
    return s; 
} 

// serial input 
std::istringstream in1(R"~(
2.34 3 3.f 3.d .75 0 wibble 
)~"); 

// line input 
std::istringstream in2(R"~(
2.34 
3 

3.f 
3.d 
.75 
0 
wibble 
)~"); 

int main() 
{ 
    std::string input; 

    // NOTE: This technique will not work if input is empty 
    // or contains only white-space characters. Therefore 
    // it is safe to use after a conditional extraction 
    // operation >> but it is not reliable after std::getline() 
    // without further checks. 

    while(in1 >> input) 
    { 
     // input will not be empty and will not contain white-space. 
     double d; 
     if((std::istringstream(input) >> d >> std::ws).eof()) 
     { 
      // d is a valid double 
      std::cout << "d1: " << d << '\n'; 
     } 
    } 

    std::cout << '\n'; 

    while(std::getline(in2, input)) 
    { 
     // eliminate blank lines and lines 
     // containing only white-space (trim()) 
     if(trim(input).empty()) 
      continue; 

     // NOW this is safe to use 

     double d; 
     if((std::istringstream(input) >> d >> std::ws).eof()) 
     { 
      // d is a valid double 
      std::cout << "d2: " << d << '\n'; 
     } 
    } 
} 

Ciò funziona poiché il controllo eof() assicura che solo doppio stata inserita e non spazzatura come 12d4.

+0

Se 'cin >> input' fallisce con eof, l'istruzione' if' valuterà 'true' - facilmente risolto ala' if (cin >> input && (...)) ... '. –

+0

@TonyD Hai perfettamente ragione, è importante ricordare che questa tecnica si aspetta che 'input' contenga * qualcosa *. – Galik

3

Poche differenze minori rispetto alla risposta di πάντα ῥεῖ - lo fanno anche gestire ad es. rappresentazioni di numeri negativi ecc., oltre ad essere - IMHO - un po 'più semplice da leggere.

#include <iostream> 
#include <sstream> 
#include <string> 

int main() 
{ 
    std::istringstream iss("2.832 1.3067 nana1.678 x-1E2 xxx.05 meh.ugh"); 
    double num = 0; 
    for (; iss;) 
     if (iss >> num) 
      std::cout << num << '\n'; 
     else if (!iss.eof()) 
     { 
      iss.clear(); 
      iss.ignore(1); 
     } 
} 

uscita:

2.832 
1.3067 
1.678 
-100 
0.05 

(vedere in esecuzione here)

2

Se ti piace concisione - ecco un'altra opzione che (ab?) Utilizza && per ottenere cout fatto solo quando è stato un numero analizzato correttamente e quando un numero non viene analizzato utilizza l'operatore virgola per essere in grado di clear() stato di errore del flusso all'interno del condizionale prima di leggere un carattere da ignorare ...

#include <iostream> 
#include <sstream> 
#include <string> 

int main() 
{ 
    std::istringstream iss("2.832 1.3067 nana1.678 x-1E2 xxx.05 meh.ugh"); 
    double num = 0; 
    char ignored; 
    while (iss >> num && std::cout << num << '\n' || 
      (iss.clear(), iss) >> ignored) 
     ; 
} 

http://ideone.com/WvtvfU