2016-08-12 4 views
1

Ich muss mehrere C-Stil-Strings (um 500k) mit 4 Gleitkommazahlen durch ein einzelnes Leerzeichen getrennt analysieren. Es folgt ein Beispiel einer einzelnen Saite:Parse String von Zahlen

„90292 5879 89042,2576 5879“

Ich brauche diese Zahlen in zwei Strukturen zu speichern, die zwei Punkte. Wenn man bedenkt, dass die Zeichenkette während der Analyse geändert werden kann und dass 99,99% der Zahlen nur vorzeichenlose Ganzzahlen sind, was ist der schnellste Weg dazu?

Nach meiner aktuellen Implementierung ist:

#include <iostream> 
#include <cassert> 
#include <chrono> 
#include <algorithm> 
#include <vector> 
#include <string> 
using namespace std; 
using namespace chrono; 



struct PointF 
{ 
    float x; 
    float y; 
}; 


void parse_points(char* points, PointF& p1, PointF& p2) 
{ 
    auto start = points; 
    const auto end = start + strlen(points); 

    // p1.x 
    start = std::find(start, end, ' '); 
    assert(start < end); 
    *start = '\0'; 
    p1.x = static_cast<float>(atof(points)); 
    points = start + 1; 

    // p1.y 
    start = std::find(start, end, ' '); 
    assert(start < end); 
    *start = '\0'; 
    p1.y = static_cast<float>(atof(points)); 
    points = start + 1; 

    // p2.x 
    start = std::find(start, end, ' '); 
    assert(start < end); 
    *start = '\0'; 
    p2.x = static_cast<float>(atof(points)); 
    points = start + 1; 

    // p2.y 
    start = std::find(start, end, ' '); 
    assert(start == end); 
    p2.y = static_cast<float>(atof(points)); 
} 



int main() 
{ 
    const auto n = 500000; 
    char points_str[] = "90292 5879 89042.2576 5879"; 
    PointF p1, p2; 

    vector<string> data(n); 

    for (auto& s : data) 
     s.assign(points_str); 

    const auto t0 = system_clock::now(); 

    for (auto i = 0; i < n; i++) 
     parse_points(const_cast<char*>(data[i].c_str()), p1, p2); 

    const auto t1 = system_clock::now(); 
    const auto elapsed = duration_cast<milliseconds>(t1 - t0).count(); 

    cout << "Elapsed: " << elapsed << " ms" << endl; 

    cin.get(); 
    return 0; 
} 
+0

Ich denke 'boost :: lexical_cast' ist schneller als' atof'. –

+2

@Sorosh_Sabz ist tatsächlich mehr als 8 Mal langsamer .... – Nick

+0

Es gibt zu viele Parsing-Fragen, das Mindeste, was Sie tun können, ist, zuerst zu suchen. Probieren Sie Folgendes aus: ["stackoverflow C++ - Lesen von getrenntem Dateispeicher"] (https://www.google.com/search?q=stackoverflow+c%2B%2B+read+file+space+separated+float&ie=utf-8&oe = utf-8) –

Antwort

0

einen String mit Gleitkommazahlen Gegeben, Leerzeichen getrennt:

const std::string example_input = "90292 5879 89042.2576 5879"; 

Sie sollten sehen Profil, was schneller ist, als Floating-Point-Lesen :

std::istringstream text_stream(example_input); 
std::vector<double> container; 
double value; 
while (text_stream >> value) 
{ 
    container.push_back(value); 
} 

Oder Lesen als ganze Zahlen, eine Performance-Einbußen zu nehmen, wenn Hinweise auf eine Floating-Point ist:

std::istringstream text_stream(example_input); 
std::vector<double> container; 
double value; 
signed int int_value; 
std::streampos position_before_read = text_stream.tellg(); 
while (text_stream >> int_value) 
{ 
    // check the next character for possible floating point differences. 
    char c; 
    text_stream >> c; 
    switch (c) 
    { 
    case '.': 
    case 'E': case 'e': 
     // Rewind to before the number and read as floating point 
     text_stream.seekg(position_before_read); 
     text_stream >> value; 
     break; 
    default: 
     value = 1.0 * int_value; 
     break; 
    } 
    container.push_back(value); 
    position_before_read = text_stream.tellg(); 
} 

Meine Vermutung ist, dass die Standard-Bibliotheken Floating-Point zu lesen sind optimiert, viel besser als das obige Beispiel und Konto für alle Varianzen des Gleitkommaformats.

Hinweis: alternativ könnten Sie die Dezimalzahlen und Exponenten als Ganzzahlen lesen, falls vorhanden, und dann einen Gleitkommawert mit allen drei Teilen erstellen.

+0

Haben Sie Ihre Lösung gegen meine gemessen? – Nick

+0

Nein, haben Sie Ihre Lösung gegen das einfache Lesen aller Floats gemessen? –

-1

Sie können atof implementieren, die die Pos von space zurückgeben. Auf diese Weise müssen Sie jede Zeichenfolge nur einmal durchlaufen.

z.

char *atof(char *point, float &num) { 
    num = 0; 
    bool neg = false, dot = false; 
    float decimal = 0, mul = 0.1; 
    if (*point == '-') { 
    neg = true; 
    point++; 
    } else if (*point == '+') { 
    point++; 
    } 
    while (*point != ' ' && *point) { 
    if (*point == '.') { 
     dot = true; 
    } else { 
     if (dot) { 
     decimal += (*point - '0') * mul; 
     mul *= 0.1; 
     } else { 
     num = num * 10 + *point - '0'; 
     } 
    } 
    point++; 
    } 
    if (dot) { 
    num += decimal; 
    } 
    if (neg) { 
    num = -num; 
    } 
    return point; 
} 
+0

Verwenden Sie strtod; Erfinde das Rad nicht neu. – rici

0

ich mehrere Probleme mit dem Code zu sehen (und es ist wirklich gut, dass Sie gefragt haben):

  • Es gibt keinen Fehler für den Fall bearbeitet, wenn es Zahlen (NB: wie pro Diskussion, erwarten Sie in diesem Fall 0)
  • Sie erstellen die PointF zweimal Objekte, um sie
    • Sie sie als Referenz zu übergeben passieren, so ist es nicht trivial für einen Menschen die Berufung Code zu lesen, dass diese aus sind params.
  • Der Parser Sie erstellt haben, ist in C verfügbar (wenn auch Sie könnten messen, wenn es schneller ist oder langsamer)

ich dies würde vorschlagen: (beachten Sie, dass std::experimental::optional<> entspricht hier boost::optional<>)

#include <iostream> 
#include <cstring> 
#include <utility> 
#include <experimental/optional> 

struct PointF 
{ 
    float x; 
    float y; 
}; 

std::experimental::optional<std::pair<PointF, PointF>> parse_points(char* pch) 
{ 
    pch = strtok (pch, " "); 
    if (pch != NULL) 
    { 
     float x0 = atof(pch); 
     pch = strtok (NULL, " "); 
     if (pch != NULL) 
     { 
      float y0 = atof(pch); 
      pch = strtok (NULL, " "); 
      if (pch != NULL) 
      { 
       float x1 = atof(pch); 
       pch = strtok (NULL, " "); 
       if (pch != NULL) 
       { 
        float y1 = atof(pch); 
        PointF p0{x0, y0}, p1{x1, y1}; 
        return std::make_pair(p0, p1); 
       } 
      } 
     } 
    } 
    return std::experimental::nullopt; 
} 

int main() { 
    const char str[] ="90292 5879 89042.2576 5879"; 
    char* pch0 = new char[sizeof(str)], *pch = pch0; 
    memcpy(pch0, str, sizeof(str)); 

    std::experimental::optional<std::pair<PointF, PointF>> pOpt(parse_points(pch0)); 
    if(pOpt) 
     std::cout << pOpt->first.x << " " << pOpt->first.y << " " 
        << pOpt->second.x << " " << pOpt->second.y << " " << std::endl; 
    delete pch; 
} 
+0

Leider ist 'std :: optional' in C++ 11 nicht verfügbar (ich kann es nicht einmal testen). Hast du deine Lösung gegen meine getestet? Welche Art von Verbesserung hast du bekommen? – Nick

+0

'atof' im Fehlerfall gibt 0 zurück, und das ist gut, weil es der Standardwert sein sollte. Klarheit wird immer akzeptiert, es sei denn, dies beeinträchtigt die Leistung in diesem Fall nicht. – Nick

+0

@Nick: yep, der 'äquivalent zu' Teil fehlt: es ist 'boost :: optional', du hast es auch für C++ 03. Das zu beheben. Wie für 'atof' und Standard 0, ist es nicht klar, dass' 0x12 AB AF xx 'zu ((0,0), (0,0)) oder (was ich denke) zu einem Fehler führen sollte. Auch - Was passiert, wenn sich die Standardwerte ändern (z. B. (-1, -1), (-1, -1))? – lorro

0

Hier ist meine Version ohne strlen aber mit Verwendung von strtok_s. Auf meiner Maschine dauert es 1.1sec anstelle 1.5sec.

void parse_points(char* points, PointF& p1, PointF& p2) 
{ 
    char *next_token1 = nullptr; 

    // p1.x 
    points = strtok_s(points, " ", &next_token1); 
    p1.x = points ? static_cast<float>(atof(points)) : 0.0f; 

    // p1.y 
    points = strtok_s(nullptr, " ", &next_token1); 
    p1.y = points ? static_cast<float>(atof(points)) : 0.0f; 

    // p2.x 
    points = strtok_s(nullptr, " ", &next_token1); 
    p2.x = points ? static_cast<float>(atof(points)) : 0.0f; 

    // p2.y 
    points = strtok_s(nullptr, " ", &next_token1); 
    p2.y = points ? static_cast<float>(atof(points)) : 0.0f; 
} 



int main() 
{ 
    const auto n = 500000; 
    char points_str[] = "90292 5879 89042.2576 5879"; 
    PointF p1, p2; 

    vector<string> data(n); 

    for (auto& s : data) 
     s.assign(points_str); 

    const auto t0 = system_clock::now(); 

    for (auto i = 0; i < n; i++) 
     parse_points(const_cast<char*>(data[i].c_str()), p1, p2); 

    const auto t1 = system_clock::now(); 
    const auto elapsed = duration_cast<milliseconds>(t1 - t0).count(); 

    cout << "Elapsed: " << elapsed << " ms" << endl; 

    //cin.get(); 
    return 0; 
}