@Mayumi_Sasakura

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

データが全て16バイトになってしまう

解決したいこと

ランレングス圧縮を実現するプログラムを作成しているのですが、すべて16バイトに書き換えられてしまいます。
下記のサイトを参考にコーディングしました。エラーなどは出ていません。
https://algoful.com/Archive/Algorithm/RLE

Visual Studio C++ 17です。

該当するソースコード

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


class RLE
{
public:
	void Encode(std::string inputFile);
	std::vector<char> EncodeRunLength(char buffer[], int size);
	std::vector<char> GetRunLength(char c, int length);
};

void RLE::Encode(std::string inputFile)
{
	//バイナリファイルで読み込む
	std::ifstream in
	{
		inputFile,
		std::ios::binary
	};

	if (!in) {
		std::cout << "ファイルが読み込まれていません" << std::endl;
	}

	//ファイルサイズを調べる
	in.seekg(0, std::ios::end);
	size_t size = static_cast<size_t>(in.tellg());
	in.seekg(0, std::ios::beg);

	//読み込んだデータをchar配列に格納する
	char* buffer = new char[size];
	in.read(buffer, size);

	in.close();

	//圧縮
	auto enc = EncodeRunLength(buffer, size);

	//ファイルに書き込み
	std::ofstream out
	{
		inputFile,
		std::ios::binary
	};

	//ベクターを配列に変換
	char* encArray = new char[enc.size()];
	std::copy(enc.begin(), enc.end(), encArray);

	out.write(encArray, sizeof(enc));

	delete buffer;
	delete encArray;
	out.close();
}

//ファイルを圧縮する
std::vector<char> RLE::EncodeRunLength(char buffer[], int size)
{
	std::vector<char> result;

	int length = 0;
	char b = 0;

	for (int i = 0; i < size; ++i)
	{
		if (i == 0)
		{
			// 1文字目の場合保持
			length = 1;
			b = buffer[0];
		}
		else if (buffer[i] == b)
		{
			// 直前の文字と一致していればカウントアップ
			length++;
		}

		// 不一致のタイミングで出力
		if (buffer[i] != b)
		{
			std::vector<char> rl = GetRunLength(b, length);
			result.insert(result.end(), rl.begin(), rl.end());

			// 文字データ更新
			length = 1;
			b = buffer[i];
		}
	}

	// 最後の圧縮結果を出力
	std::vector<char> rl = GetRunLength(b, length);
	result.insert(result.end(), rl.begin(), rl.end());

	return result;
}

//圧縮文字列を返す
std::vector<char> RLE::GetRunLength(char c, int length)
{
	std::vector<char> result;
	const int MaxLength = 255;

	for (int i = 1; i <= length; ++i)
	{
		if (i % MaxLength == 0)
		{
			result.push_back(c);
			result.push_back(MaxLength);
		}
		else if (i == length)
		{
			// 最終文字数時点で出力
			result.push_back(c);
			result.push_back(static_cast<char>(length % MaxLength));
		}
	}

	return result;
}


### 自分で試したこと
0バイトのテキストデータで試しても、16バイトになってしまうため、
おそらくout.write(encArray,sizeof(enc))encArrayが原因ではないかと思っています。
0 likes

2Answer

何を入力しても出力が16バイトになる,というなら,「出力サイズの指定」を真っ先に疑うべきでしょう.すなわち,

out.write(encArray, sizeof(enc));

ここの sizeof(enc) が間違いでしょう.( これでは「vector<char> 型インスタンスのサイズ」になっている)

直前でわざわざ char の配列をサイズ enc.size() で作っているのですから,全く同じ記述にすれば良いのでは.

0Like

Comments

  1. わざわざ char の配列を~

    というのは,コピーせずとも直接 std::vector::data で中身を参照するとかで良いのでは? という意味.

  2. 回答ありがとうございます。
    char配列を作るのではなく、out.write(enc.data(),enc.size())にしたら上手くいきました。

  3. 本件の問題とは直接関係ありませんが,動的な領域確保に std::vector を使う箇所と,自前で new/delete で頑張る箇所とがあって(且つ,そこに特に使い分けがあるようにも見えないので)何か「変な感じ」を受けます.

    (あとは,class である必要も無いとか,入力ファイルが上書きされるのでは使いにくいのでは? とか.)

  4. これは元ネタが悪いのでしょうけど,

    RLE::GetRunLength

    の実装はさすがにどうなのか?
    「割り算? 引けなくなるまで引き続ければいいよね!」みたいな.

  5. ご指摘ありがとうございます。newではなくvectorに統一しました。
    理解が乏しくて申し訳ないのですが、「引けなくなるまで引き続ける」とはどういうことでしょうか?

  6. ああ,失礼.ちょっと間違っていました.
    今のコードは「引けなくなるまで引き続ける」よりもさらに悪い実装ですね.

    例えばあるbyte値(ここでは x としよう)が 513(=255x2 + 3)個だけ連続していた場合,

    • x が 255個
    • x が 255個
    • x が 3個

    という結果を作るのが RLE::GetRunLength のやることですよね.

    ここで「x が 255個」という結果を何個作ればいいのかをどうやって求めるのか? と言われたら, 513/255=2 という割り算で求められますよね.
    つまり割り算で実装すればループは不要です.

    で,ここを「割り算」じゃなくて「引けなくなるまで引き続ける」という話で実装するとしたら,例えば

    //残りが255を下回るまで255を引き続ける
    int Rest = length;
    while( Rest >= 255 )
    {
      「xが255個」という結果を1個生成する;
      Rest -= 255;
    }
    

    みたいな形の実装になりますね.
    ループが回る回数は2回です.

    今のコードはこれよりもさらにひどくて,length回(この例だと513回)ループが回るんですね.
    無意味だと思いませんか?


    「引けなくなるまで引き続ける」に関しては,「255byte毎に1つ結果を作らなきゃならない」という話を(だったら割り算で求まるよねとか言わずに)愚直に実装しているのだとも捉えられる気がしますが……

  7. std::vector<char> RLE::GetRunLength(char c, int length)
    {
    	std::vector<char> result;
    	const int MaxLength = 255;
    	int rest = length;
    
    	while (rest >= MaxLength)
    	{
    		rest -= MaxLength;
    
    		//cが255個という結果を生成
    		result.push_back(c);
    		result.push_back(static_cast<char>(MaxLength));
    	}
    
    	//残りのcの個数をpushする
    	result.push_back(c);
    	result.push_back(static_cast<char>(rest));
    	
    	return result;
    }
    

    コードを修正したのですが、上記のような形にすればループ回数が少なくて済む、という事でよろしいでしょうか。

  8. 前記した通り,当然ループ回数は減りますね.

    //残りのcの個数をpushする

    ここ,「restが0ではないときにのみやる」ようにしないとまずそうです.

  9. ありがとうございます。迅速な回答助かりました。

参考にされたサイトに、C#のコードが書かれていたので、それを機械的にC++に変換しました。
コンパイルエラーが無いことは確認しましたが、動作環境はできていません。
一旦、先にコードを提示しておきます。

#include <iostream>
#include <fstream>
#include <vector>

std::vector<char> GetRunLength(char b, int length)
{
    std::vector<char> result;
    const int MaxLength = 255;
    for (int i = 1; i <= length; i++)
    {
        if (i % MaxLength == 0)
        {
            result.push_back(b);
            result.push_back((char)MaxLength);
        }
        else if (i == length)
        {
            result.push_back(b);
            result.push_back(length % MaxLength);
        }
    }
    return result;
}

std::vector<char> EncodingRunLength(std::vector<char> bytes)
{
    std::vector<char> result;
    int length = 0;
    char b = 0;
    for (int i = 0; i < bytes.size(); i++)
    {
        if (i == 0)
        {
            length = 1;
            b = bytes[0];
        }
        else if (bytes[i] == b)
        {
            length++;
        }

        if (bytes[i] != b)
        {
            std::vector<char> runLength = GetRunLength(b, length);
            result.insert(result.end(), runLength.begin(), runLength.end());
            length = 1;
            b = bytes[i];
        }
    }

    std::vector<char> runLength = GetRunLength(b, length);
    result.insert(result.end(), runLength.begin(), runLength.end());
    return result;
}

std::vector<char> DecodingRunLength(std::vector<char> bytes)
{
    std::vector<char> result;
    char b = 0;
    for (int i = 0; i < bytes.size(); i++)
    {
        if (i % 2 == 0)
        {
            b = bytes[i];
        }
        else
        {
            result.insert(result.end(), bytes[i], b);
        }
    }
    return result;
}

void Encode(const std::string& inputFile, const std::string& outputFile) {
    std::ifstream input(inputFile, std::ios::binary);
    std::vector<char> bytes((std::istreambuf_iterator<char>(input)), (std::istreambuf_iterator<char>()));
    input.close();

    std::vector<char> enc = EncodingRunLength(bytes);

    std::ofstream output(outputFile, std::ios::binary);
    output.write(enc.data(), enc.size());
    output.close();
}

void Decode(const std::string& inputFile, const std::string& outputFile) {
    std::ifstream input(inputFile, std::ios::binary);
    std::vector<char> bytes((std::istreambuf_iterator<char>(input)), (std::istreambuf_iterator<char>()));
    input.close();

    std::vector<char> dec = DecodingRunLength(bytes);

    std::ofstream output(outputFile, std::ios::binary);
    output.write(dec.data(), dec.size());
    output.close();
}

void RLE__(std::string inputFile, std::string outputFile)
{
    std::ifstream file(inputFile, std::ios::binary);
    std::vector<char> bytes((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();

    std::vector<char> enc = EncodingRunLength(bytes);

    std::ofstream outFile(outputFile, std::ios::binary);
    outFile.write(enc.data(), enc.size());
    outFile.close();

    std::vector<char> dec = DecodingRunLength(enc);

    std::ofstream decFile(inputFile + "_dec.txt", std::ios::binary);
    decFile.write(dec.data(), dec.size());
    decFile.close();
}

int main()
{
    std::string inputFile = "input.txt";
    std::string outputFile = "output.txt";
    RLE__(inputFile, outputFile);

    return 0;
}
0Like

Your answer might help someone💌