как всегда. Мы часто можем набрать больше скорости, думая о хороших алгоритмах и создавая хороший дизайн.
Первый комментарий. Я проверил ваш подход с исходным файлом размером 100 МБ, и на моем компьютере в режиме Release со всеми оптимизациями потребовалось не менее 30 минут.
И, как вы сами отметили. Он возвращает все пробелы, а не только те, которые находятся в начале файла. Итак, нам нужно найти лучшее решение
Сначала мы подумаем о том, как мы можем определить пробелы в начале строки. Для этого нам понадобится логический флаг, который указывает, что мы находимся в начале строки. Мы назовем его beginOfLine
и первоначально установим на true
, потому что файл всегда начинается со строки.
Затем, далее, мы проверяем, является ли следующий символ пробелом ' '
или символ табуляции '\t'
. В отличие от других решений, мы проверим оба.
Если это так, нам не нужно учитывать это пространство или табуляцию в выводе, в зависимости от того, находимся ли мы в начале строки или не. Итак, результат обратный beginOfLine
.
Если символ не является пробелом или табуляцией, мы проверяем наличие новой строки. Если мы нашли один, то мы устанавливаем флаг beginOfLine
в true, иначе в false. В любом случае мы хотим использовать символ.
Все это можно поместить в простую лямбду с состоянием
auto check = [beginOfLine = true](const char c) mutable -> bool {
if ((c == ' ') || (c == '\t') )
return !beginOfLine;
beginOfLine = (c == '\n');
return true; };
или, более компактно:
auto check = [beginOfLine = true](const char c) mutable -> bool {
if (c == ' ' || c == '\t') return !beginOfLine; beginOfLine = (c == '\n'); return true; };
Затем, следующий. Мы не будем стирать пробелы из исходной строки, потому что это огромная операция по смещению памяти, которая занимает очень много времени. Вместо этого мы копируем данные (символы) в новую строку, но только необходимые единицы.
И для этого мы можем использовать std::copy_if
из стандартной библиотеки.
std::copy_if(data.begin(), data.end(), data2.begin(), check);
Это сделает работу. А для данных объемом 100 МБ требуется 160 мс. По сравнению с 30 минутами это огромная экономия.
Пожалуйста, посмотрите пример кода (который, конечно, нужно адаптировать для ваших нужд):
#include <iostream>
#include <fstream>
#include <filesystem>
#include <iterator>
#include <algorithm>
#include <string>
namespace fs = std::filesystem;
constexpr size_t SizeOfIOStreamBuffer = 1'000'000;
static char ioBuffer[SizeOfIOStreamBuffer];
int main() {
// Path to text file
const fs::path file{ "r:\\test.txt" };
// Open the file and check, if it could be opened
if (std::ifstream fileStream(file); fileStream) {
// Lambda, that identifies, if we have a spece or tab at the begin of a line or not
auto check = [beginOfLine = true](const char c) mutable -> bool {
if (c == ' ' || c == '\t') return !beginOfLine; beginOfLine = (c == '\n'); return true; };
// Huge string with all file data
std::string data{};
// Reserve space to spped up things and to avoid uncessary allocations
data.resize(fs::file_size(file));
// Used buffered IO with a huge iobuffer
fileStream.rdbuf()->pubsetbuf(ioBuffer, SizeOfIOStreamBuffer);
// Read file, Elimiate spaces and tabs at the beginning of the line and store in data
std::copy_if(std::istreambuf_iterator<char>(fileStream), {}, data.begin(), check);
}
return 0;
}
Как видите, все кипит сделано с одним утверждением в коде. И это работает (на моей машине) в 160 мс для файла размером 100 МБ.
Что еще можно оптимизировать? Конечно, мы видим, что в нашем программном обеспечении 2 100 МБ std::string
с. Какая трата. Окончательная оптимизация состояла бы в том, чтобы поместить 2 оператора для чтения файла и удаления пробелов и табуляций в начале строки в один оператор.
std::copy_if(std::istreambuf_iterator<char>(fileStream), {}, data.begin(), check);
Тогда у нас будет только 1 раз данные в памяти и устраните бессмысленность того, что мы читаем данные из файла, который нам не нужен. И прелесть этого в том, что при использовании современных элементов языка C ++ необходимы лишь незначительные модификации. Просто замените исходные итераторы:
Да, я знаю, что размер строки в конце слишком велик, но его можно легко установить на фактическое значение. Например, используя data.reserve (...) и back::inserter