Разделить строку файла csv на std::vector?

У меня есть функция, которая будет читать CSV-файл построчно. Для каждой строки он разделит линию на вектор. Код для этого

    std::stringstream ss(sText);
    std::string item;

    while(std::getline(ss, item, ','))
    {
        m_vecFields.push_back(item);
    }

Это работает нормально, за исключением случаев, когда он читает строку, где последнее значение пусто. Например,

text1,tex2,

Я бы хотел, чтобы это возвращало вектор размера 3, где третье значение просто пусто. Однако вместо этого он просто возвращает вектор размера 2. Как я могу это исправить?


person Jonnster    schedule 03.07.2012    source источник
comment
возможный дубликат парсера CSV в C++   -  person Tony Delroy    schedule 03.07.2012
comment
нет, это не так. Этот код делает то же самое, если строка заканчивается запятой.   -  person Jonnster    schedule 03.07.2012
comment
Разве проблема не в разделителе? std::getline извлекает до тех пор, пока не будет найден разделитель. Но для последнего элемента нет следующего разделителя ,, поэтому ничего не извлекается, и, таким образом, цикл while завершается.   -  person AquilaRapax    schedule 03.07.2012
comment
@Jonnster: только потому, что в принятом в настоящее время ответе есть недостатки, это не означает, что другой вопрос не касается того же проблемного пространства адекватно - есть другие ответы, которые должны работать и за которые можно проголосовать, и вы можете комментировать проблемы с конкретными ответами.   -  person Tony Delroy    schedule 03.07.2012


Ответы (5)


Вы можете просто использовать boost::split, чтобы сделать все это за вас.
http://www.boost.org/doc/libs/1_50_0/doc/html/string_algo/usage.html#id3207193

Он имеет поведение, которое вам требуется в одной строке.

Пример кода boost::split

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

using namespace std;

int main()
{
    vector<string> strs;

    boost::split(strs, "please split,this,csv,,line,", boost::is_any_of(","));

    for ( vector<string>::iterator it = strs.begin(); it < strs.end(); it++ )
        cout << "\"" << *it << "\"" << endl;

    return 0;
}

Полученные результаты

"please split"
"this"
"csv"
""
"line"
""
person GrahamS    schedule 03.07.2012
comment
Почему? using namespace std это вполне нормально, не так ли? Я не думаю, что его удаление приведет к чему-либо @Seanny123, но не стесняйтесь делать это в своем собственном коде. - person GrahamS; 03.04.2014

Вы можете использовать функцию, подобную этой:

template <class InIt, class OutIt>
void Split(InIt begin, InIt end, OutIt splits)
{
    InIt current = begin;
    while (begin != end)
    {
        if (*begin == ',')
        {
            *splits++ = std::string(current,begin);
            current = ++begin;
        }
        else
            ++begin;
    }
    *splits++ = std::string(current,begin);
}

Он будет перебирать строку и всякий раз, когда встретит разделитель, извлечет строку и сохранит ее в итераторе разделения.
Интересная часть заключается в следующем.

  • когда current == begin он вставит пустую строку (контрольный пример: "text1,,tex2")
  • последняя вставка гарантирует, что всегда будет правильное количество элементов.
    Если есть запятая в конце, она вызовет предыдущую точку маркера и добавит пустую строку, в противном случае она добавит последний элемент в вектор.

Вы можете использовать его следующим образом:

std::stringstream ss(sText);
std::string item;
std::vector<std::string> m_vecFields;
while(std::getline(ss, item))
{
    Split(item.begin(), item.end(), std::back_inserter(m_vecFields));
}

std::for_each(m_vecFields.begin(), m_vecFields.end(), [](std::string& value)
{
    std::cout << value << std::endl;
});
person Julien Lebot    schedule 03.07.2012

Гибкое решение для разбора CSV-файлов: где:

источник – содержимое CSV-файла

разделитель – разделитель CSV, например. ',' ';'

std::vector<std::string> csv_split(std::string source, char delimeter) {
    std::vector<std::string> ret;
    std::string word = "";
    int start = 0;

    bool inQuote = false;
    for(int i=0; i<source.size(); ++i){
        if(inQuote == false && source[i] == '"'){
            inQuote = true;
            continue;
        }
        if(inQuote == true && source[i] == '"'){
            if(source.size() > i && source[i+1] == '"'){
                ++i;
            } else {
                inQuote = false;
                continue;
            }
        }

        if(inQuote == false && source[i] == delimeter){
            ret.push_back(word);
            word = "";
        } else {
            word += source[i];
        }
    }
    ret.push_back(word);

    return ret;
}
person Markoj    schedule 22.03.2015

C++11 позволяет чрезвычайно легко обрабатывать даже экранированные запятые, используя regex_token_iterator:

std::stringstream ss(sText);
std::string item;
const regex re{"((?:[^\\\\,]|\\\\.)*?)(?:,|$)"};

std::getline(ss, item)

m_vecFields.insert(m_vecFields.end(), sregex_token_iterator(item.begin(), item.end(), re, 1), sregex_token_iterator());

Кстати, если вы просто хотите создать vector<string> из CSV string, такого как item, вы можете просто сделать:

const regex re{"((?:[^\\\\,]|\\\\.)*?)(?:,|$)"};
vector<string> m_vecFields{sregex_token_iterator(item.begin(), item.end(), re, 1), sregex_token_iterator()};

[Живой пример]

Некоторое краткое объяснение regex, вероятно, в порядке. (?:[^\\\\,]|\\\\.) соответствует экранированным символам или символам, отличным от ','. (Подробнее см. здесь: https://stackoverflow.com/a/7902016/2642059) *? означает, что это не является жадным соответствием, поэтому оно остановится на первом достигнутом ','. Все это вложено в захват, который выбирается по последнему параметру, от 1 до regex_token_iterator. Наконец, (?:,|$) будет соответствовать либо разделителю ',', либо концу string.

Чтобы эта стандартная программа чтения CSV игнорировала пустые элементы, регулярное выражение можно изменить так, чтобы оно соответствовало только строкам, содержащим более одного символа.

const regex re{"((?:[^\\\\,]|\\\\.)+?)(?:,|$)"};

Обратите внимание, что '+' теперь заменило '*', указывая на то, что требуется 1 или более совпадающих символов. Это предотвратит сопоставление вашей строки item, которая заканчивается на ','. Пример этого можно увидеть здесь: http://ideone.com/W4n44W.

person Jonathan Mee    schedule 05.03.2015

person    schedule
comment
Да, я просто добавил код, как в первом примере, и вернулся, чтобы найти, что кто-то предложил его. Это не самый чистый код, но сойдет. - person Jonnster; 03.07.2012