CSV結合ツールを自作しよう!(第4回・別のアルゴリズム編)

C#入門
この記事は約7分で読めます。

今回位は、「結合実行ボタンクリック処理」を、いくつかのアルゴリズムで置き換えてみたいと思います。

あくまでも参考情報というこのなのでプロジェクトのZIPファイルは用意していません。

「こういう方法もあるんだ」という事で読み進めて頂ければOKですが、動作確認がしたい方は、前回のソースコードの該当部分(UxExecJoin_Clickイベントハンドラ)を、今回掲載するソースコードで置き換えて実行してみて下さい。

フラグを使った方法(前回の復習)

結合対象のファイルが1個目か2個目以降かによって、

  • 1個目はヘッダを出力するが、2個目はヘッダを出力しない
  • 1個目はファイルを上書きするが、2個目は追加する

という処理を分ける必要があります。

そこで、 first_file_flg という bool型の変数を用意し、この値が true か false かによって、読み込んだファイルが1個目か2個以降かを判断していました。

フラグの代わりにカウント変数を使って判定する

1個目と2個目を判定したいだけですから、フラグではなくファイル数をカウントして、カウント数が0なら1個目、0以外なら2個目という風に判定できますよね。

ということで、数を数える変数 cnt を用意し、ループの最後に cnt に1を加算することで、1個目か2個目以上かを判定するアルゴリズムが次のものになります。

普通に使う分には、この方法でも問題はありません。

整数数値型 – C# リファレンス | Microsoft Docs

int型の変数が扱える値は -2,147,483,648 ~ 2,147,483,647(マイナス21億~+21億)です。

cnt を加算し続けると 21億で符号が反転し、やがて 0 に戻ってしまいますが、そもそもそんな数をファイルを1つのフォルダに置けないので、実用上問題はありません。

ちなみに今は if(cnt == 0) としていますが、 if(cnt > 0) と記述してもいいと思います。

以上の方法でイベントハンドラを書き換えたソースコードは以下の様になります。

Forループの添え字を使って判定する

「1づつ加算する cnt 変数で1個目のファイルか2個目のファイルかを判断するんだったら、いっそForループにしてしまって、添え字で判断したらよくね?」

という意見が聞こえてきそうですが、まさにその通りです。

今は foreach を使っていますが、 for に変更すれば、わざわざ判定用の変数を用意する必要がなくなります。

for ループの中で files.Length という記述がありますが、これは配列変数の要素数を取得するプロパティです。

配列変数はLinqのCount() メソッドが使えますので、 files.Count() と記述することが出来ます。

Linq についてはこちらに簡単に触れていますので、目次から「Linqとは」を参照して頂ければと思います。

ちなみに、List や Dictionaryのクラスの要素数を求める場合は、Countプロパティを使いますので、この点はご注意ください。

ListやDictionaryにもLinqが使えるので、Count() メソッドも使えますが、通常はCountプロパティの方を使います。

では、for ループに書き換えたソースコードは以下の様になります。

追加書き込みだけで済ませる

今までのアルゴリズムでは、1回目はFile.WriteAllLines、2回目以降はFile.AppendAllLines を使いました。

今回はFile.AppendAllLines だけで済ませようというものです。

考え方は簡単で、最初に出力ファイルが存在していれば、削除するということです。

ファイルの存在確認と削除は以下の様に記述できます。

一目瞭然ですが、Existsメソッドがファイルの存在確認、Deleteメソッドがファイルの削除です。

これで if 文によるFile.WriteAllLinesとFile.AppendAllLinesの切り替えは無くなりましたが、ヘッダに関しては1個目か否かの判断が必要なので、それは残ってしまいます。

では、実際にこの方法で書き換えたソースコードのご覧ください。

Forループから if 文を排除してみる

今度は、ファイルが1個目か否かの判断をForループから排除して、その部分をシンプルにしたいと思います。

考え方としては

  • 結合対象フォルダのファイル数が0より大きい場合、そのファイルを読んで出力ファイル名で上書き保存する(WriteAllLinesを使う)。
  • 残りのファイルについては、ループ処理にてヘッダを削除しながら追加書き込みする。

ということになります。

残りのファイルをループ処理する際、既に1個目は処理済みになっているので、マイナス1した回数ループする必要があります。

そこで、For ループの開始を1、ループ回数を Length -1 にしています。

以上の内容で書き換えたソースコードは次の様になります。

File.WriteAllLines が復活してしまいましたが、ループ処理は見やすくなったのではないかと思います。

ヘッダを除外できるファイル読み込みメソッドを自作してみる

それでは、最後にヘッダを除外できるファイル読み込みメソッドを自作して、それを使ってみましょう。

こうすることによって、結合実行ボタンクリック時のイベントハンドラがスッキリします。

ここでのポイントはファイルから1行ずつファイルを読み込むという行為です。

ファイルを読み込む場合 StreamReaderクラスを、ファイルを書き込む場合、StreamWriter クラスを使います。

今回はファイルを読み込む部分を別メソッド化するため、StreamReaderクラスだけを使用しました。

ファイル読み込みの要の部分を抜粋したのが以下のソースコードです。

StreamReaderクラスのプロパティとして、 EndOfStream というのがあり、これを参照することでファイルをすべて読み終わったか否かが判断できます。

また、ファイルから1行取り出すメソッドとして ReadLine メソッドが用意されています。

StreamReaderのインスタンスを生成する際にファイル名と文字コードを引数として渡すことで、そのファイル名がその文字コードで参照できるようになります。

インスタンスを生成した時点でファイルがオープンされますので、処理が終わったら必ずCloseメソッドを読んで、ファイルのクローズをしなければなりません。

これをしないと、そのファイルがオープンされたままになったり、メモリにゴミが残ってしまいますので、必ずCloseメソッドを呼び出すようにして下さい。

では、このメソッドとそれを使った修正後のソースコードを見ていきましょう。

イベントハンドラの中身はずいぶんスッキリしたのではないかと思います。

forループの cnt 変数が0なら無視するヘッダ行数は0、cnt 変数が0以外なら無視するヘッダ行数に head_cntの値を使用することで、ヘッダの出力有無を切り替えています。

まとめ

いかがでしたでしょうか。

今回はいくつかのアルゴリズムについて実際の例と修正後のソースコードを紹介しました。

このように、同じことをするにしても様々な考え方(アルゴリズム)や記述ができます。

アルゴリズムはメモリ効率や処理速度、後々のメンテナンスのしやすさ等、色々な要素を考慮して決めていくものなので、状況によってはどの方法も正解になります。

趣味で行うDIYプログラミングでは、そこまで難しく考える必要はありませんので、自分が一番理解しやすい方法を選んでもらえればOKです。

タイトルとURLをコピーしました