Быстро .SLK файл (формат sylk) парсер


Для моего проекта мне нужен (частичная) .СЛК (формат sylk) парсер, который нужно загрузить 10+ достаточно большие таблицы (30.000-50.000 строк) быстро. Подмножество очень ограничены только следующий формат:

ИДЕНТИФИКАТОР;PWXL;Н;Е

Всегда первый и это только важно, если "идентификатор" есть

Б;На Базе Чипсета X79;Y838;Н0

Размер стола, всегда идет Перед "с" строк. X-это ширина, а y-высота.

С;Х25;К0.25
С;X26;K"всегда"
С;X26;Y32;К"никогда"

Х столбца и дополнительный г устанавливает ряд (если отсутствует последняя строка или 0 используется). "К" части может быть в кавычках или без. В любом случае формат хранения-это std::string (для теперь).

Теперь для фактического кода

std::vector<std::vector<std::string>> table_data;
std::unordered_map<std::string, int> header_to_column;
std::unordered_map<std::string, int> header_to_row;

std::ifstream stream(path);

std::string line;

size_t position = 0;
size_t length = 0;

size_t column = 0;
size_t row = 0;

const auto parse_int_part = [&]() {
    position++;
    length = line.find_first_of(';', position) - position;
    position += length;
    return std::stoi(line.substr(position - length, length));
};

if (std::getline(file, line)) {
    if (line.substr(0, 2) != "ID") {
        std::cout << "Invalid SLK file, does not contain \"ID\" as first record" << std::endl;
        return;
    }
} else {
    return;
}

while (std::getline(stream, line)) {
    position = 2;

    switch (line.front()) {
        case 'B':
            while (true) {
                switch (line[position]) {
                    case 'X':
                        columns = parse_int_part();
                        break;
                    case 'Y':
                        rows = parse_int_part();
                        break;
                    default:
                        table_data.resize(rows, std::vector<std::string>(columns));
                        goto nextline;
                }
                if (position < line.size() - 1) {
                    position++;
                }
            }
        case 'C':
            while (true) {
                switch (line[position]) {
                    case 'X':
                        column = parse_int_part() - 1;
                        break;
                    case 'Y':
                        row = parse_int_part() - 1;
                        break;
                    case 'K': {
                        position++;
                        if (line[position] == '\"') {
                            position++;
                        }

                        length = line.size() - position - ((line.back() == '\r') ? 1 : 0);
                        const std::string part = line.substr(position, length - ((line[position + length - 1] == '\"') ? 1 : 0) );

                        if (row == 0) {
                            header_to_column.emplace(part, column);
                        }

                        if (column == 0) {
                            header_to_row.emplace(part, row);
                        }
                        table_data[row][column] = part;
                        goto nextline;
                    }
                }
                if (position < line.size() - 1) {
                    position++;
                }
            }
        case 'E':
            goto exitloop;
    }
    nextline:
    position = 2;
}
exitloop:;

Я использую некоторые Гото, который, вероятно, ОК, чтобы использовать здесь, так как других решений гораздо более многословен, но, по-видимому, плохая практика? А поскольку файл может иметь "\р\н" окончаний строк те должны быть обработаны вручную, но кажется, что там должен быть лучший способ.

Как бы парсинг быть улучшено при сохранении той же производительности?

Редактировать
Я изменил пример, поэтому его можно выполнить легко и просто [тест .СЛК].3.



157
0
задан 9 марта 2018 в 09:03 Источник Поделиться
Комментарии
1 ответ

Я дал ему попробовать компиляции на моей машине, но есть слишком много вещей еще не хватает, так что это будет в основном основан на визуальном осмотре кода.

Функции

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

bool check_SLK_header(std::fstream file)
{
bool header_is_good = false;
std::string line;
if (std::getline(file, line)) {
if (line.substr(0, 2) == "ID") {
header_is_good = true;
}
}
return header_is_good;
}

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

Далее я хотел вырваться на различные случаи switch заявление в свои функции, слишком. Я бы назвал их как-то так parse_dimensions_record() для B случае, и parse_cell_record() для C случае. Я буду говорить о E случае ниже.

Избегайте Бесконечных Циклов

Этот образец написания бесконечный цикл, который имеет некоторые специальные "целевых" критериев, скрытой в середине крайне трудно читать и следовать. Это одна из проблем с использованием goto, break (вне switch заявление), continueи т. д. Он делает поток код. Почти всегда можно переписать бесконечный цикл лучше с реальным критериям выхода. Вот как я напишу parse_dimensions_record() функция для B случае:

void parse_dimensions_record(const std::string& line, std::vector<std::vector<std::string>>& table_data)
{
int columns = -1;
int rows = -1;
size_t position = 2;
while (((columns < 0) && (rows < 0)) && (position < line.size() - 1))
{
switch (line [ position ])
{
case 'X':
columns = parse_int_part();
break;
case 'Y':
rows = parse_int_part();
break;
}
position++;
}

if ((columns >= 0) && (rows >= 0))
{
table_data.resize(rows, std::vector<std::string>(columns));
}
}

Теперь становится понятным, что существуют некоторые граничные случаи, которые могут потребовать обработки. Во-первых, есть ли смысл иметь 0 columns или 0 rows? Если нет, вы должны вернуть ошибку и остановить парсинг, наверное, когда вы нажмете что случае. Если это так, то вы можете также, вероятно, рано, так как нет смысла в чтении оставшейся части файла, так как там нет столбцов или строк, чтобы провести какие-либо данные.

Вы также не обрабатывать искаженные данные, такие как недопустимый описатель в B запись, например. Безопасно ли игнорировать эти? Это указывает на поврежденный файл? Вы, вероятно, нужно вернуть код ошибки и обработать это дело в некотором роде.

Я уже упоминал выше, что я говорил о E случае ниже. Ну вот и мы. Основной цикл для парсинга файла не бесконечный цикл, и это хорошо, но вы все равно не нужна goto у вас есть там. Вы можете покончить с этим, просто держать вокруг переменную, которая говорит, Может ли вы попали в конец записи. Что-то вроде этого:

bool hit_end = false;
while ((std::get line(stream, line)) && (!hit_end)) {
position = 2;

switch (line.front()) {
case 'B':
parse_dimensions_record(line, table_data);
break;
case 'C':
parse_cell_record(line, table_data);
break;
case 'E':
hit_end = true;
break;
default:
// Got some sort of invalid record here. Should probably stop parsing
break;
}
}

Это гораздо легче для чтения и понимания и во избежание различных goto звонки, которые сделали другие версии трудно понять.

1
ответ дан 10 марта 2018 в 09:03 Источник Поделиться