2009-08-24 6 views
18

Ich brauche in Datendateien zu lesen, die wie folgt aussehen:Wie Zahlen aus einer ASCII-Datei lesen (C++)

* SZA: 10.00 
2.648 2.648 2.648 2.648 2.648 2.648 2.648 2.649 2.650 2.650 
2.652 2.653 2.652 2.653 2.654 2.654 2.654 2.654 2.654 2.654 
2.654 2.654 2.654 2.655 2.656 2.656 2.657 2.657 2.657 2.656 
2.656 2.655 2.655 2.653 2.653 2.653 2.654 2.658 2.669 2.669 
2.667 2.666 2.666 2.664 2.663 2.663 2.663 2.662 2.663 2.663 
2.663 2.663 2.663 2.663 2.662 2.660 2.656 2.657 2.657 2.657 
2.654 2.653 2.652 2.651 2.648 2.647 2.646 2.642 2.641 2.637 
2.636 2.636 2.634 2.635 2.635 2.635 2.635 2.634 2.633 2.633 
2.633 2.634 2.634 2.635 2.637 2.638 2.637 2.639 2.640 2.640 
2.639 2.640 2.640 2.639 2.639 2.638 2.640 2.640 2.638 2.639 
2.638 2.638 2.638 2.638 2.637 2.637 2.637 2.634 2.635 2.636 
2.637 2.639 2.641 2.641 2.643 2.643 2.643 2.642 2.643 2.642 
2.641 2.642 2.642 2.643 2.645 2.645 2.645 2.645 

Was die eleganteste Weg wäre, diese Datei in ein Array von Schwimmern zu lesen?

Ich weiß, wie jede einzelne Zeile in eine Zeichenfolge zu lesen, und ich weiß, wie Sie die Zeichenfolge in float mit atof() konvertieren. Aber wie mache ich den Rest am einfachsten?

Ich habe von String-Puffern gehört, könnte mir das helfen?

Antwort

11

Da dies als C++ markiert ist, wäre der naheliegendste Weg die Verwendung von Streams. Aus der Spitze von meinem Kopf, so etwas wie dies tun könnte:

std::vector<float> readFile(std::istream& is) 
{ 
    char chdummy; 
    is >> std::ws >> chdummy >> std::ws; 
    if(!is || chdummy != '*') error(); 
    std::string strdummy; 
    std::getline(is,strdummy,':'); 
    if(!is || strdummy != "SZA") error(); 

    std::vector<float> result; 
    for(;;) 
    { 
    float number; 
    if(!is>>number) break; 
    result.push_back(number); 
    } 
    if(!is.eof()) error(); 

    return result; 
} 

Warum float, BTW? Normalerweise ist double viel besser.

bearbeiten, da sie, ob eine Kopie der vector Rückkehr wurde in Frage gestellt ist eine gute Idee:

Für eine erste Lösung, würde ich sicherlich das Offensichtliche. Die Funktion ist Lesen einer Datei in eine vector, und die offensichtlichste Sache für eine Funktion zu tun ist, das Ergebnis zurückgeben. Ob dies zu einer merklichen Verlangsamung führt, hängt von vielen Dingen ab (die Größe des Vektors, wie oft die Funktion aufgerufen wird und woher die Geschwindigkeit der Platte liest, ob der Compiler RVO anwenden kann). Ich möchte nicht die offensichtliche Lösung mit einer Optimierung verderben, aber wenn das Profiling tatsächlich zeigt, dass dies zu langsam ist, sollte der Vektor in einer nicht konstanten Referenz übergeben werden.

(Beachten Sie auch, dass C++ 1x mit rvalue-Unterstützung, die hoffentlich bald durch einen Compiler in Ihrer Nähe verfügbar sein wird, diese Diskussion erschweren wird, da dadurch verhindert wird, dass der Vektor nach der Rückkehr von der Funktion kopiert wird.

std::ifstream input("input.txt"); 
std::vector<float> floats; 
std::string header; 
std::getline(input, header); // read in the "* SZA: 10.00" line 
if(header_is_correct(header)) { 
    float value; 
    // while we could successfully read in a float from the file... 
    while(input >> value) { 
     // store it in the vector. 
     floats.push_back(value); 
    } 
} 

HINWEIS: manuell dortheader_is_correct(header) ist nur ein Beispiel, benötigen Sie einen Fehler implementieren für die erste Zeile Überprüfung)

+0

Der generische "load all floats" -Loop wäre 'float number; while (is >> number) result.push_back (number); ' – sth

+0

Obwohl deins natürlich äquivalent ist. – sth

+0

@sth: In der Tat, das ist knapper, obwohl ich nicht mag, dass die Variable "Nummer" aus der Schleife "leckt". – sbi

2

ich so etwas tun würde.

+0

Warum der Downvote? Ich habe das getestet und es liest jedes Float aus der Datei korrekt in einen Vektor. –

18

Die String Toolkit Library (Strtk) hat die folgende Lösung für Ihr Problem:

#include <iostream> 
#include <string> 
#include <deque> 
#include <iterator> 

#include "strtk.hpp" 

int main() 
{ 
    std::deque<float> flist; 
    strtk::for_each_line("file.txt", 
         [&flist](const std::string& line) 
         { strtk::parse(line," ",flist); } 
         ); 
    std::copy(flist.begin(),flist.end(), 
       std::ostream_iterator<float>(std::cout,"\t")); 
    return 0; 
} 

Weitere Beispiele finden Sie in C++ String Toolkit (StrTk) Tokenizer finden.

+0

interessant, obwohl Sie klar sein sollten, dass dies nur für C++ 0x Compiler ist. –

+18

Sehr wahr, aber das Lambda kann genauso gut in ein Struct-Style-Prädikat eingefügt werden. Ich dachte für Stil und zukünftige Referenzen (lassen Sie sich davon in 1-2 Jahren, der obige Code und ähnliche wird die Norm sein), dass es eine gute Idee wäre, eine andere Sicht auf, wie Dinge getan werden können. –

+12

Ich mochte das. Nette Verwendung der neuen Lambdas, auch wenn dies nicht die Antwort sein kann. –

2

Einfache Lösung mit STL-Algorithmen:

#include <vector> 
#include <iostream> 
#include <string> 
#include <iterator> 

struct data 
{ 
    float first; // in case it is required, and assuming it is 
       // different from the rest 
    std::vector<float> values; 
}; 

data read_file(std::istream& in) 
{ 
    std::string tmp; 
    data d; 
    in >> tmp >> tmp >> d.first; 
    if (!in) throw std::runtime_error("Failed to parse line"); 

    std::copy(std::istream_iterator<float>(in), std::istream_iterator<float>(), 
     std::back_inserter<float>(d.values)); 

    return data; 
} 

Wenn Sie wirklich ein Array verwenden müssen, müssen Sie es zuerst zuweisen (entweder dynamisch oder statisch, wenn Sie die Größe kennen), und dann können Sie die gleiche Kopie verwenden

Algorithmus
// parsing the first line would be equivalent 
float data[128]; // assuming 128 elements known at compile time 
std::copy(std::istream_iterator<float>(is), std::istream_iterator<float>(), 
     data); 

Aber ich würde empfehlen std :: vector auch in diesem Fall verwenden, wenn Sie die Daten in eine Funktion zu übergeben müssen, die ein Array bringt Sie immer es als ein Zeiger auf das erste Element passieren kann:

void f(float* data, int size); 
int main() 
{ 
    std::vector<float> v; // and populate 
    f(&v[0], v.size()); // memory is guaranteed to be contiguous 
}