Чтение Getline в CSV очень странно - PullRequest
0 голосов
/ 18 ноября 2009

Я пытаюсь прочитать csv, используя строку get, чтобы извлечь три переменные, разделенные запятыми. Имя, курс и класс.

Я хорошо читаю в первой строке, но она добавляет странные новые разрывы строк и отправляет формат в кластер.

Вот мой код:

#include "header.h"

string student::GetCourse() {
    return course;
}

string student::GetName() {
    return name;
}

string student::GetGrade() {
    return grade;
}

void student::setname(string n) {
    name = n;
}

void student::setCourse(string c) {
    course = c;
}

void student::setGrade(string g) {
    grade = g;
}
void sort (vector <student> &List) {

    student temp;
    int first = 1;
    int vectorLength = List.size() - 1;

    for (int i = vectorLength; i > 0; i--) {
        first = i;
        for (int j = 0; j < i; j++) {
            if (List[j].GetName() > List[first].GetName())
            first = j;
        }
        temp = List[first];
        List[first] = List[i];
        List[i] = temp;
    }

}

void main () {
    ifstream file;
    vector <student> StudentList;

    file.open("short.txt");

    while (!file.eof()) {

        file.ignore(8196,'\n');

        string tempname, tempgrade, tempcourse = "";

        if (file != "\n") {
            getline(file, tempname, ',');
            getline(file, tempcourse, ',');
            getline(file, tempgrade, ',');
        }

        student s;
        s.setCourse(tempcourse);
        s.setname (tempname);
        s.setGrade (tempgrade);

            StudentList.push_back(s);

    }
    //sort (StudentList);

    for (int i = 0; i < StudentList.size(); i++) {
        cout << StudentList[i].GetName() << " " << StudentList[i].GetCourse() << " " << StudentList[i].GetGrade() << endl;
    }
}

Любые идеи, мне очень тяжело читать этот файл.

Ответы [ 5 ]

7 голосов
/ 18 ноября 2009

Ну, вот и

  • if (file != "\n") сравнение бессмысленно. Он не делает то, что вы думаете.
  • Ваш разделитель после оценки не ',', это '\n'.
  • while (!file.eof()) неверно. Это проверяет EOF только после того, как это уже произошло. Вы должны проверить возвращаемое значение вашего getline() вместо

Также

  • обычно в C ++ вы делаете std::ifstream file("short.txt");. Вам не нужно звонить open() отдельно.
  • Вам не нужно инициализировать std::string в "". Это происходит автоматически. Даже если бы вам пришлось это сделать, вы должны были написать

    std::string a = "", b = "", c = "";.

    Если вы делаете std::string a, b, c = "something", тогда только c инициализируется чем-то.

4 голосов
/ 19 ноября 2009

Некоторые комментарии:

Не пишите свой собственный вид.

STL имеет собственные встроенные алгоритмы сортировки.
Все, что вам нужно сделать, это указать отношения между объектами:

bool operator<(student const& lhs,student const& rhs)
{
    return lhs.GetName() < rhs.GetName();
}
// Now a sort is:

   std::sort(list.begin(),list.end());

Не использовать: while (! File.eof ())

Это стандартный шаблон для чтения файла.
Проблема в том, что еще слишком рано или два поздно для теста. Если вы ничего не читали, это два рано, так как ничего не произошло. Если вы что-то прочитали, значит, уже слишком поздно, поскольку вы выполнили обработку прочитанного элемента (но не смогли).

Лучший способ - поместить чтение в цикл while. Это потому, что результат чтения возвращает ссылку на поток. Это может быть автоматически преобразовано в объект, который может быть использован в логическом контексте (тест преобразования, чтобы увидеть, если что-то не так с потоком). Таким образом, ошибка чтения оставит поток в состоянии, которое приведет к его преобразованию в эквивалент false в логическом контексте.

std::string line;
while(std::getline(file,line))
{
   // loop only entered if getline() worked.
   // Thus we know that we have a good value in line.
   // use line
}

Не используйте магические числа:

Вы действительно игнорируете 8000 символов или просто пытаетесь удалить строку?

file.ignore(8196,'\n');

У вас есть две альтернативы:

std::string ignoreLine;
std::getline(file,ignoreLine);

// Dont use a magic number but use a number that is guranteed not to fail.
file.ignore(std::numeric_limits<std::streamsize>::max(), '\n')

Не ленитесь:

Главное в программировании - написание поддерживаемого кода.
Использование такого рода инициализации (относительно универсально) осуждается как просто ленивый. Разместите каждое объявление в отдельной строке. Это облегчает чтение кода.

string tempname, tempgrade, tempcourse = "";

// Like this:
std::string tempname;
std::string tempgrade;
std::string tempcourse;

Используйте поток строк, чтобы разбить строку на части

Я не уверен, что вы пытаетесь здесь?

if (file != "\n")
{   getline(file, tempname, ',');
    getline(file, tempcourse, ',');
    getline(file, tempgrade, ',');
}

Я думаю, что было бы легче читать, если бы мы объединили его с циклом выше:

std::string line;
while(std::getline(file,line))
{
    std::stringstream  linestr(line);

    if (getline(linestr, tempname, ',') &&
        getline(linestr, tempcourse, ',') &&
        getline(linestr, tempgrade, ',')
       )
    {
        // Here we have read a line.
        // And successfully retrieved three comma separated values from the line
    }
}

При появлении возможности замените цикл стандартным алгоритмом

Этот цикл печати можно заменить на std :: copy ()

for (int i = 0; i < StudentList.size(); i++)
{        cout << StudentList[i].GetName() << " " 
              << StudentList[i].GetCourse() << " " 
              << StudentList[i].GetGrade() << endl;
}

Все, что вам нужно сделать, это определить оператор вывода для вашего класса.

std::ostream& operator<<(std::ostream& str,student const& data)
{
    str << data.getName() << " "
        << data.getCourse() << " "
        << data.getGrade() << " "; // No newline here.
    return str;
}

Теперь мы можем скопировать вектор в std :: cout

std::copy(StudentList.begin(),StudentList.end(),
          std::ostream_iterator<student>(std::cout,"\n")
         );

Основная ошибка.

Основная ошибка, которую я вижу, это строка:

if (file != "\n")

Здесь вы сравниваете файл с «C-строкой». Как компилятору удается это скомпилировать, я не уверен.
На ум приходят несколько вариантов, но тот факт, что он неочевиден, делает его вероятным источником ошибок. Также обратите внимание, что это не то, как вы сравниваете две строки (если только одна не является std :: string).

Я думаю, что компилятор преобразует файл в указатель и сравнивает его с "C-String" (поскольку это также просто указатель). Вы можете подумать, что это немного странно, но есть оператор, который преобразует файл в пустоту *. Указатель не указывает на что-либо значимое, но имеет значение NULL или не NULL и может сравниваться с указателем char *, что приводит к значению true (поскольку оно никогда не равно строке "\ n").

2 голосов
/ 18 ноября 2009

Во-первых: вы не проверяете, успешен ли ввод где-либо. Черт, вы даже не проверяете, можно ли открыть файл:

int main () {                          // it's int main()!
  ifstream file("short.txt");
  if(!file.good()) {
    std::cerr << "couldn't open \"short.txt\"\n";
    return 1;
  }

  vector <student> StudentList;
  for(;;) {
    // ...
  }
  if( !file.eof() ) {
    std::cerr << "error reading before eof!\n";
    return 2;
  }
  // ...
}

Затем: как правило, сначала легче прочитать строки в этом цикле:

for(;;) {
  std::string line;
  std::getline(file, line);
  if(!file) break;
  // ...
}

и затем читать из этих строк через поток строк. Я бы поместил чтение кода в строки в его собственную функцию:

std::istream& read_line(std::istream& is, StudentList& list)
{
  std::string value1, value2, value3;
  std::getline(is, value1, ',');
  std::getline(is, value2, ',');
  std::getline(is, value3, ',');
  if(is)
    StudentList.push_back(...);
}

// ...
for(;;) {
  std::string line;
  std::getline(file, line);
  if(!file) break;

  std::istringstream iss(line);
  read_line(iss, StudentList);
  if(!iss) break;
}
// ...

НТН.

1 голос
/ 18 ноября 2009

Вы получили несколько ответов. Хотя их предложения определенно улучшают то, что вы делаете сейчас, я бы отнесся к этому немного иначе, чем они предлагали.

Прямо сейчас ваш student класс в основном делает все возможное, чтобы имитировать «тупые данные» (т.е. просто простую структуру), но с более уродливым синтаксисом - вы используете пару get / set для каждого члена, но они не добавляешь ничего Сам класс student так же «туп», как если бы он был простой структурой. Вся логика для student все еще находится вне класса student.

Чтобы сделать его полезным, класс student должен содержать достаточное количество задействованной логики, например, как читать student из потока или отображать student в другом потоке:

class student { 
    std::string name, course, grade;
public:

    bool operator<(student const &other) const {
        return name < other.name;
    }

    friend std::ostream &operator<<(std::ostream &os, student const &st) { 
        return os << st.name << " " << st.course << " " << st.grade;
    }

    friend std::istream &operator>>(std::istream &is, student &st) { 
         std::string temp;
         is >> temp;
         std::istringstream t(temp);
         std::getline(t, st.name, ',');
         std::getline(t, st.course, ',');
         std::getline(t, st.grade);
         return is;
    }
};

Это значительно упрощает основное:

int main() { 
    std::ifstream in("short.txt");
    std::vector<student> students;

    std::copy(std::istream_iterator<student>(in),
              std::istream_itertor<student>(),
              std::back_inserter(students));
    std::sort(students.begin(), students.end());
    std::copy(students.begin(), students.end(), 
        std::ostream_iterator<student>(std::cout, "\n"));
    return 0;
}               

Обратите внимание, в частности, что main когда-либо имеет дело только с «целым» student как с логической сущностью - он никогда однажды не заглянет «внутрь» объекта student в его составные части.

0 голосов
/ 18 ноября 2009

Установив разделитель на ',' в вызове

getline(file, tempname, ',');

вы не читаете всю строку за раз. '\ N' является разделителем по умолчанию, и, используя значение по умолчанию, вы получите всю строку, а не только ее часть.

Я бы предложил использовать разделитель по умолчанию для чтения всей строки, а затем разбить строку на токены, используя «,» в качестве разделителя, и использовать if(!file.eof) чтобы определить, когда вы закончите чтение файла.

...