Как читать числа из файла ASCII (C ++) - PullRequest
18 голосов
/ 24 августа 2009

Мне нужно прочитать в файлах данных, которые выглядят так:

* 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

Какой самый элегантный способ чтения этого файла в массив с плавающей точкой?

Я знаю, как читать каждую строку в строку, и я знаю, как преобразовать строку в число с плавающей точкой, используя atof() Но как мне сделать остальное проще всего?

Я слышал о строковых буферах, это может мне помочь?

Ответы [ 4 ]

18 голосов
/ 25 августа 2009

Библиотека String Toolkit (Strtk) имеет следующее решение вашей проблемы:

#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;
}

Дополнительные примеры можно найти в C ++ String Toolkit (StrTk) Tokenizer .

11 голосов
/ 24 августа 2009

Поскольку он помечен как C ++, наиболее очевидным способом было бы использование потоков. С макушки головы, что-то вроде этого может сделать:

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;
}

Почему float, кстати? Обычно double намного лучше.

Редактировать , так как был задан вопрос, является ли хорошая идея возврата копии vector:

Для первого решения я бы, конечно, сделал очевидное. Функция - это , считывающая файл в vector, и наиболее очевидная вещь, которую должна сделать функция - это вернуть свой результат. Приводит ли это к заметному замедлению, зависит от многих вещей (размер вектора, как часто вызывается функция и откуда, скорость диска, с которого она считывает, может ли компилятор применять RVO). Я бы не хотел испортить очевидное решение оптимизацией, но если профилирование действительно показывает, что это слишком медленно, вектор должен быть передан по неконстантной ссылке.

(Также обратите внимание, что C ++ 1x с поддержкой rvalue, который, как мы надеемся, скоро будет доступен с помощью ближайшего к вам компилятора, сделает этот дискуссионный вопрос спорным, поскольку он предотвратит копирование вектора после возвращения из функции.)

2 голосов
/ 25 августа 2009

Простое решение с использованием алгоритмов STL:

#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;
}

Если вам действительно нужно использовать массив, вы должны сначала выделить его (динамически или статически, если вы знаете размер), а затем вы можете использовать тот же алгоритм копирования

// 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 );

Но я бы рекомендовал использовать std :: vector даже в этом случае, если вам нужно передать данные в функцию, которая принимает массив, вы всегда можете передать его как указатель на первый элемент:

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
}
2 голосов
/ 24 августа 2009

Я бы сделал что-то вроде этого:

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);
    }
}

ПРИМЕЧАНИЕ: header_is_correct(header) является лишь примером, вам нужно будет выполнить любую проверку ошибок для этой первой строки вручную.

...