プログラム 技術

重複した行をデータ内から除外する Part2

以前【重複した行をデータ内から除外する】という内容で記事を書きましたが、その際はスクリプト言語で書いたので今回はコンパイル言語であるC#とC++で実装してみようと思います

関連記事
重複した行をデータ内から除外する - ナストンのまとめ
重複した行をデータ内から除外する - ナストンのまとめ

今回は以前紹介した差分取得と似た内容となりますが、重複した内容のものを削除する方法となります 名称バージョンPython ...

関連記事へ

名称バージョン
C#.Net 8.0
C++14

C#の場合

今回の場合も追加のパッケージは使用しません

public static void InvokeDedupeStream(
    string inFile,
    bool inPlace = false,
    bool ignoreCase = false,
    bool trim = false,
    string encoding = "utf-8")
{
    Console.WriteLine(
        $"InvokeDedupeStream called with InFile='{inFile}', InPlace={inPlace}, IgnoreCase={ignoreCase}, Trim={trim}, Encoding='{encoding}'");

    string path = Path.GetFullPath(inFile);
    if (!File.Exists(path))
    {
        throw new FileNotFoundException($"Input file not found: {path}");
    }

    string tempName = Path.GetFileName(path) + "." + Path.GetRandomFileName() + ".tmp";
    string tempPath = Path.Combine(Path.GetDirectoryName(path)!, tempName);

    Encoding enc;
    try
    {
        enc = Encoding.GetEncoding(encoding);
    }
    catch
    {
        throw new ArgumentException($"Unsupported encoding: {encoding}");
    }

    var comparer = ignoreCase
        ? StringComparer.OrdinalIgnoreCase
        : StringComparer.Ordinal;
    var seen = new HashSet<string>(comparer);

    using (var fsIn = File.OpenRead(path))
    using (var sr = new StreamReader(fsIn, enc, true))
    using (var fsOut = File.Open(tempPath, FileMode.Create, FileAccess.Write, FileShare.None))
    using (var sw = new StreamWriter(fsOut, enc))
    {
        string? line;
        while ((line = sr.ReadLine()) != null)
        {
            string k = line;
            if (trim)
            {
                k = System.Text.RegularExpressions.Regex.Replace(k, @"[ \t]+$", "");
            }

            if (!seen.Contains(k))
            {
                seen.Add(k);
                sw.WriteLine(line);
            }
        }
    }

    if (inPlace)
    {
        File.Move(tempPath, path, true);
    }
    else
    {
        Console.WriteLine($"written to {tempPath}");
    }
}

C++の場合

こちらも追加のライブラリはありませんが、一部機能を自作する必要があります
また、C++についてはコメントを記載しているので実装時の参考にしてください

// ExcludeDuplicateRows.h
class ExcludeDuplicateRows
{
public:
    ExcludeDuplicateRows();
    ~ExcludeDuplicateRows();

    static void InvokeDedupeStream(
        const std::string& inFile,
        bool inPlace = false,
        bool ignoreCase = false,
        bool trim = false,
        const std::string& encoding = "utf-8");
};

// ExcludeDuplicateRows.cpp
namespace {
    // 大文字小文字を区別しない
    struct CaseInsensitiveHash {
        std::size_t operator()(const std::string& str) const {
            std::string lower = str;
            std::transform(lower.begin(), lower.end(), lower.begin(),
                [](unsigned char c) { return std::tolower(c); });
            return std::hash<std::string>()(lower);
        }
    };

    // 大文字小文字を区別しない
    struct CaseInsensitiveEqual {
        bool operator()(const std::string& a, const std::string& b) const {
            if (a.length() != b.length()) return false;
            return std::equal(a.begin(), a.end(), b.begin(),
                [](unsigned char c1, unsigned char c2) {
                    return std::tolower(c1) == std::tolower(c2);
                });
        }
    };

    // ランダムなファイル名を生成
    std::string GetRandomFileName() {
        static const char alphanum[] =
            "0123456789"
            "abcdefghijklmnopqrstuvwxyz";
        std::string result;
        result.reserve(12);
        
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(0, sizeof(alphanum) - 2);

        for (int i = 0; i < 12; ++i) {
            result += alphanum[dis(gen)];
        }
        return result;
    }

    // パスからディレクトリを取得
    std::string GetDirectoryName(const std::string& path) {
        size_t pos = path.find_last_of("/\\");
        if (pos != std::string::npos) {
            return path.substr(0, pos);
        }
        return ".";
    }

    // パスからファイル名を取得
    std::string GetFileName(const std::string& path) {
        size_t pos = path.find_last_of("/\\");
        if (pos != std::string::npos) {
            return path.substr(pos + 1);
        }
        return path;
    }

    // ファイルが存在するか確認
    bool FileExists(const std::string& path) {
        std::ifstream f(path.c_str());
        return f.good();
    }

    // 末尾のスペースとタブを削除
    std::string TrimTrailing(const std::string& str) {
        std::regex pattern("[ \\t]+$");
        return std::regex_replace(str, pattern, "");
    }

    // ファイルを移動(存在する場合上書き)
    void MoveFile(const std::string& src, const std::string& dst) {
#ifdef _WIN32
        MoveFileExA(src.c_str(), dst.c_str(), MOVEFILE_REPLACE_EXISTING);
#else
        std::remove(dst.c_str());
        std::rename(src.c_str(), dst.c_str());
#endif
    }
}

void ExcludeDuplicateRows::InvokeDedupeStream(
    const std::string& inFile,
    bool inPlace,
    bool ignoreCase,
    bool trim,
    const std::string& encoding)
{
    std::cout << "InvokeDedupeStream called with InFile='" << inFile
        << "', InPlace=" << (inPlace ? "true" : "false")
        << ", IgnoreCase=" << (ignoreCase ? "true" : "false")
        << ", Trim=" << (trim ? "true" : "false")
        << ", Encoding='" << encoding << "'" << std::endl;

    std::string path = inFile;
    if (!FileExists(path)) {
        throw std::runtime_error("Input file not found: " + path);
    }

    // 文字コードが UTF-8 or ASCII 以外の場合は警告を出す
    if (encoding != "utf-8" && encoding != "UTF-8" && encoding != "ascii" && encoding != "ASCII") {
        std::cerr << "Warning: Only UTF-8 and ASCII encodings are fully supported in this implementation." << std::endl;
    }

    std::string tempName = GetFileName(path) + "." + GetRandomFileName() + ".tmp";
    std::string tempPath = GetDirectoryName(path) + "/" + tempName;

    std::ifstream fsIn(path);
    if (!fsIn.is_open()) {
        throw std::runtime_error("Failed to open input file: " + path);
    }

    std::ofstream fsOut(tempPath);
    if (!fsOut.is_open()) {
        fsIn.close();
        throw std::runtime_error("Failed to create temporary file: " + tempPath);
    }

    std::string line;
    
    if (ignoreCase) {
        std::unordered_set<std::string, CaseInsensitiveHash, CaseInsensitiveEqual> seen;
        while (std::getline(fsIn, line)) {
            std::string k = line;
            if (trim) {
                k = TrimTrailing(k);
            }

            if (seen.find(k) == seen.end()) {
                seen.insert(k);
                fsOut << line << std::endl;
            }
        }
    }
    else {
        std::unordered_set<std::string> seen;
        while (std::getline(fsIn, line)) {
            std::string k = line;
            if (trim) {
                k = TrimTrailing(k);
            }

            if (seen.find(k) == seen.end()) {
                seen.insert(k);
                fsOut << line << std::endl;
            }
        }
    }

    fsIn.close();
    fsOut.close();

    if (inPlace) {
        MoveFile(tempPath, path);
    }
    else {
        std::cout << "written to " << tempPath << std::endl;
    }
}

前回に引き続き今回はコンパイル言語について記載しました。C++は一筋縄にはいかないですね

会社紹介

私が所属しているアドバンスド・ソリューション株式会社(以下、ADS)は一緒に働く仲間を募集しています

会社概要
「技術」×「知恵」=顧客課題の解決・新しい価値の創造

この方程式の実現はADSが大切にしている考えで、技術を磨き続けるgeekさと、顧客を思うloveがあってこそ実現できる世界観だと思っています
この『love & geek』の精神さえあれば、得意不得意はno problem!
技術はピカイチだけど顧客折衝はちょっと苦手。OKです。技術はまだ未熟だけど顧客と知恵を出し合って要件定義するのは大好き。OKです
凸凹な社員の集まり、色んなカラーや柄の個性が集まっているからこそ、常に新しいソリューションが生まれています

ミッション
私たちは、テクノロジーを活用し、業務や事業の生産性向上と企業進化を支援します

ホームページ
アドバンスド・ソリューション株式会社|ADS Co., Ltd.
アドバンスド・ソリューション株式会社|ADS Co., Ltd.

Microsoft 365/SharePoint/Power Platform/Azure による DX コンサル・シス ...

サイトへ移動

PR

-プログラム, 技術
-,