Сравнение списков
Сравнение списков

Сравнение списков

28 сентября 2024       93 просмотра

Итак, плюсики, сравнение строк двух разных файлов.

Вы все прекрасно знаете как я обожаю креветки и пиво практические, а не выдуманные задачи.
Потому можете себе представить как я был воодушевлен, что мне, наконец-то можно будет размять пальцы за настоящей, а не выдуманной задачей.
Собственно, задача легчайшая, но я подумал, что сначала решу её тупо, в лоб, а потом посижу - покумекаю как это можно сделать красиво.

Собственно к телу делу. Листая с утра пикабу, наткнулся на пост пользователя, которому, с его слов, нужно было автоматизировать процесс сравнения строк в двух файлах так, чтобы на выходе были только уникальные, неповторяющиеся снежинки строки, а мерзкие повторюшки — отправлялись в биореактор. Утренняя прогулка не успела закончиться, а я уже прикидывал как это сделать: вычитать список из одного файла и затем построчно сравнить с другим, а потом наоборот? Вычистить массив придётся, да и переоткрывать файл... Смысл заморачиваться, примерный алгоритм уже маячил в моём ещё не до конца проснувшемся мозгу. Сколько там могут эти текстовики весить? Учитывая современные объемы оперативной памяти — не думаю, что стоит заморачиваться, по крайней мере пока. Так что делаем так:

1. Самое главное! Ничего стирать не будем, просто на выходе будут 4 файла, а не два.
2. Вычитываем строки из обоих файлов (пока).
3. Сравниваем строки.
4. Если строки равны (==), нихера не делаем.
5. В противном случае — записываем их в черный список соответствующие файлы.

Собственно, код:

#include <fstream>
#include <string>
#include <set>

int main() {
    std::ifstream file1("file1.txt");
    std::ifstream file2("file2.txt");
    std::ofstream file3("file3.txt", std::ios_base::app);
    std::ofstream file4("file4.txt", std::ios_base::app);

    std::set<std::string> set1, set2;
    std::string line;

    // Считываем строки из первого файла и добавляем их в set1
    while (std::getline(file1, line)) {
        set1.insert(line);
    }

    // Считываем строки из второго файла и добавляем их в set2
    while (std::getline(file2, line)) {
        set2.insert(line);
    }

    // Записываем уникальные строки из первого файла в file3
    for (const auto& str : set1) {
        if (set2.find(str) == set2.end()) {  // Проверяем, отсутствует ли строка во втором файле
            file3 << str << std::endl;        // Записываем уникальную строку
        }
    }

    // Записываем уникальные строки из второго файла в file4
    for (const auto& str : set2) {
        if (set1.find(str) == set1.end()) {  // Проверяем, отсутствует ли строка в первом файле
            file4 << str << std::endl;        // Записываем уникальную строку
        }
    }

    // Закрываем файлы
    file1.close();
    file2.close();
    file3.close();
    file4.close();

    return 0;
}

Ссыкло на порнхаб ссылко на гитхаб.

Пока так, чуть позже мы с ним ещё поизвращаемся, поэкономим память, попробуем асинхронщину, она тут применима.

upd (ver 1.1) подумаем об экономии памяти

Представим что у нас реально большущие наборы данных, окей, тогда давайте выгружать для проверки только 1 список, тогда нам нужно будет:
1. Выгрузить список для проверки
2. Сверить его со вторым
3. Попутно записывая уникальных в выходной файл
4. По окончании проверки — файл закрыть, список очистить
5. Составить список из второго файла
6. Повторить действия 2-4, но для файла за номером 

Окей, говно вопрос, кода, конечно, больше, делать медленнее, но экономим память, за счет считывания всего 1 списка (сета):

#include <iostream>
#include <fstream>
#include <string>
#include <set>

int main() {
    std::ifstream file1("file1.txt");
    std::ifstream file2("file2.txt");
    std::ofstream outputFile1("unique_from_file1.txt", std::ios::app);
    if (!file1.is_open() || !file2.is_open()) {
        std::cerr << "opening error" << std::endl;
        return 1;
    }

    // Сохраним строки второго файла в set
    std::set<std::string> linesInFile;
    std::string line;

    while (std::getline(file2, line)) {
        linesInFile.insert(line);
    }

    // Сравниваем строки из первого файла с множеством строк из второго файла
    while (std::getline(file1, line)) {
        if (linesInFile.find(line) == linesInFile.end()) {
            outputFile1 << line << std::endl; // Записываем уникальную строку
        }
    }
    
    //закрываем файл 3, открываем файл 4, попутно чистим временный список
    outputFile1.close();
    linesInFile.clear();
    std::ofstream outputFile2("unique_from_file2.txt", std::ios::app);
    file2.clear();  // Очищаем флаг конца файла
    file2.seekg(0); // Возвращаемся в начало второго файла
    file1.clear();  // Очищаем флаг конца файла
    file1.seekg(0); // Возвращаемся в начало первого файла
	
    while (std::getline(file1, line)) {
        linesInFile.insert(line);
    }

    // Сравниваем строки из первого файла с множеством строк из второго файла
    while (std::getline(file2, line)) {
        if (linesInFile.find(line) == linesInFile.end()) {
            outputFile2 << line << std::endl; // Записываем уникальную строку
        }
    }
	
	while (std::getline(file2, line)) {
		std::cout << line << std::endl;
	}
	
    // Закрываем файлы
    file1.close();
    file2.close();
    outputFile2.close();

    std::cout << "all done" << std::endl;
    return 0;
}

Ссылка на пронхаб

upd2 Представим, что мы бомжи. Или что нас за лишнюю купленную плашку оперативки ебут медведи.

Итак, ради максимальной экономии памяти, забьем процессорное время и остальное, будем открывать и закрывать файлы при каждом чихе, лишь бы память не перерасходовать, важно экономить память, ещё можно заиспользовать unsortered_set, и move, чтобы избежать лишних копий строк.

Итак, понгали:

#include <iostream>
#include <fstream>
#include <string>
#include <unordered_set>

int main() {
    std::ifstream file1("file1.txt");
    std::ifstream file2("file2.txt");
    std::ofstream outputFile1("unique_from_file1.txt", std::ios::app);

    if (!file1.is_open() || !file2.is_open() || !outputFile1.is_open() || !outputFile2.is_open()) {
        std::cerr << "3rr0r. you dolbaeb" << std::endl;
        return 1;
    }

    // Сохраним строки второго файла в unordered_set для быстрого поиска
    std::unordered_set<std::string> linesInFile;
    std::string line;

    while (std::getline(file2, line)) {
        linesInFile.insert(std::move(line)); // Применяем move, чтобы избежать лишнего копирования
    }
    file2.close(); // Закрываем второй файл для освобождения ресурсов

    // Сравниваем строки из первого файла
    while (std::getline(file1, line)) {
        if (linesInFile.find(line) == linesInFile.end()) {
            outputFile1 << line << std::endl; // Записываем уникальную строку
        }
    }
    outputFile1.close(); // Закрываем файл 3

    // Открываем файл 4 и очищаем временный список
    std::ofstream outputFile2("unique_from_file2.txt", std::ios::app);
    linesInFile.clear();
    file1.clear(); // Очищаем флаг конца файла
    file1.seekg(0); // Возвращаемся к началу первого файла

    while (std::getline(file1, line)) {
        linesInFile.insert(std::move(line)); // Опять используем move
    }
    file1.close(); // Закрываем первый файл

    // Открываем файл 2 снова для чтения
    file2.open("file2.txt");
    while (std::getline(file2, line)) {
        if (linesInFile.find(line) == linesInFile.end()) {
            outputFile2 << line << std::endl; // Записываем уникальную строку
        }
    }

    // Закрываем файлы
    outputFile2.close();
    file2.close();

    std::cout << "all done" << std::endl;
    return 0;
}

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

Ссыкло на гитхам.

upd3 Представим, что ресурсами мы не ограничены и выполнение должно быть максимально быстрым. Лепим горбатого асинхронщину!!!

В принципе, основное на чем мы можем сэкономить — распараллелить поиск уникальных строк и запись в файлы:

#include <fstream>
#include <string>
#include <unordered_set>
#include <iostream>
#include <future>
#include <vector>

std::unordered_set<std::string> readLines(const std::string& filename) {
    std::unordered_set<std::string> lines;
    std::ifstream file(filename);
    std::string line;

    if (!file.is_open()) {
        throw std::runtime_error("error opening: " + filename);
    }

    while (std::getline(file, line)) {
        lines.insert(line);
    }
    return lines;
}

void writeUniqueLines(const std::unordered_set<std::string>& fromSet, 
                      const std::unordered_set<std::string>& toSet, 
                      const std::string& outputFile) {
    std::ofstream out(outputFile);
    if (!out.is_open()) {
        throw std::runtime_error("error opening: " + outputFile);
    }

    for (const auto& str : fromSet) {
        if (toSet.find(str) == toSet.end()) {
            out << str << std::endl;
        }
    }
}

int main() {
    try {
        // Считываем данные асинхронно
        auto future1 = std::async(std::launch::async, readLines, "file1.txt");
        auto future2 = std::async(std::launch::async, readLines, "file2.txt");

        // Получаем результаты асинхронных задач
        auto set1 = future1.get();
        auto set2 = future2.get();

        // Записываем уникальные строки в файлы асинхронно
        auto writeFuture1 = std::async(std::launch::async, writeUniqueLines, set1, set2, "file3.txt");
        auto writeFuture2 = std::async(std::launch::async, writeUniqueLines, set2, set1, "file4.txt");

        // Ожидаем завершения записи
        writeFuture1.get();
        writeFuture2.get();

    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Вот так, казалось бы, такая простая задачка, и столько подходов в её реализации!