昨日の記事を書き上げた後、forの二重ループをどうするか考えていたところ、以下の記事を目にしました。
StringとStringBuilderと、in C# - 亀岡的プログラマ日記
その中でループはLINQにするという記載があり、LINQのコードをメモ的に残していなかったこともあり、forの二重ループをLINQで書きなおしてみました(合わせて、string.Format()部分も修正)。
■元々のソース
var contents = new List<string>(); for (int row = 1; row < 10000; row++) { var startNo = GetCellValue(row, START_COLUMN); var lastNo = GetCellValue(row, LAST_COLUMN); if (startNo == 0 && lastNo == 0) break; var amount = lastNo - startNo + 1; for (int i = 0; i < amount; i++) { contents.Add("x" + string.Format("{0:D4}", (startNo + i)) + "x"); } }
■string.Format部分の修正
一部にしかstring.Formatを使えていなかったので、まとめるようにしました。
(以前のバージョンと区別するように、prefix・suffixを変えています。以下同じ。)
var contents = new List<string>(); for (int row = 1; row < 10000; row++) { var startNo = GetCellValue(row, START_COLUMN); var lastNo = GetCellValue(row, LAST_COLUMN); if (startNo == 0 && lastNo == 0) break; var amount = lastNo - startNo + 1; for (int i = 0; i < amount; i++) { // string.Formatをまとめる contents.Add(string.Format("{0}{1:D4}{0}", "w", (startNo + i))); } }
■内側のforループをLINQに置き換える
AggregateでListを返すようにして、内側のforループを置き換えました。
参考:c# - LINQやRxのAggregateの初期値 - Qiita
var contents = new List<string>(); for (int row = 1; row < 10000; row++) { var startNo = GetCellValue(row, START_COLUMN); var lastNo = GetCellValue(row, LAST_COLUMN); if (startNo == 0 && lastNo == 0) break; // 以降をforからLINQへと置き換え contents.AddRange(Enumerable.Range(0, lastNo - startNo + 1).Aggregate(new List<string>(), (list, i) => { list.Add(string.Format("{0}{1:D4}{0}", "y", (startNo + i))); return list; })); }
■外側のforループもLINQに置き換える
空白行を挟んでまたデータがある場合の挙動を考え、WhereではなくTakeWhileを使っています。
また、GetCellValue()メソッドをSelectとTakeWhileの両方で呼ぶのは処理が遅くなると考え、今回は最初にSelectを持ってきています。
var contents = new List<string>(); var items = Enumerable.Range(1, 10000) .Select((i) => new { startNo = GetCellValue(i, START_COLUMN), lastNo = GetCellValue(i, LAST_COLUMN) }) .TakeWhile(j => j.startNo != 0 || j.lastNo != 0); foreach (var item in items) { var amount = item.lastNo - item.startNo + 1; contents.AddRange(Enumerable.Range(0, amount).Aggregate(new List<string>(), (list, k) => { list.Add(string.Format("{0}{1:D4}{0}", "z", (item.startNo + k))); return list; })); }
■ソース全体
Gistにも上げてありますが、上記の全4パターンを記載しているため、出力されるデータは4倍になっています。
ExcelDNASkype.csの、for二重ループをLINQで置き換えたサンプル (出力されるテキストファイルは4倍になっていることに注意)
using System; using System.Collections.Generic; using System.Linq; using System.Text; // Add using System.Windows.Forms; using ExcelDna.Integration; using SKYPE4COMLib; namespace ExcelDNASkype { public class ExcelDNASkype : IExcelAddIn { const int START_COLUMN = 0; const int LAST_COLUMN = 1; const string MENU_NORMAL = "一般"; const string MENU_SPECIAL = "特殊"; const string SKYPE_USER = ""; /// <summary> /// アドインメニュー:一般 /// </summary> [ExcelCommand(MenuName = "AddIn", MenuText = "Normal")] public static void DisplayNormalMenu() { Run(MENU_NORMAL); } /// <summary> /// アドインメニュー:特殊 /// </summary> [ExcelCommand(MenuName = "AddIn", MenuText = "Special")] public static void DisplaySpecialMenu() { Run(MENU_SPECIAL); } /// <summary> /// メイン処理 /// </summary> /// <param name="menuName">メニュー名</param> private static void Run(string menuName) { // テキストファイル出力 var contents = CreateContents(); // .NET4 から登場した、Path.Combineのオーバーロードを利用 var txtpath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), menuName + "_content.txt"); MessageBox.Show(txtpath); if (ExportTextFile(contents, txtpath)) MessageBox.Show(contents.Count.ToString() + " 件、出力しました。"); // Excelファイル保存 if (SaveAsExcelFile(txtpath)) MessageBox.Show("Excelファイルを保存しました。"); // Skype通知 if (SendSkypeMessage(menuName)) MessageBox.Show("Skype通知をしました。"); } /// <summary> /// 出力するテキストファイルデータの作成 /// </summary> /// <returns>テキストファイルデータ</returns> private static List<string> CreateContents() { var contents = new List<string>(); // 最初 for (int row = 1; row < 10000; row++) { var startNo = GetCellValue(row, START_COLUMN); var lastNo = GetCellValue(row, LAST_COLUMN); if (startNo == 0 && lastNo == 0) break; var amount = lastNo - startNo + 1; for (int i = 0; i < amount; i++) { contents.Add("x" + string.Format("{0:D4}", (startNo + i)) + "x"); } } // string.Formatを使う for (int row = 1; row < 10000; row++) { var startNo = GetCellValue(row, START_COLUMN); var lastNo = GetCellValue(row, LAST_COLUMN); if (startNo == 0 && lastNo == 0) break; var amount = lastNo - startNo + 1; for (int i = 0; i < amount; i++) { contents.Add(string.Format("{0}{1:D4}{0}", "w", (startNo + i))); } } // 内側のforループをLINQに変更 for (int row = 1; row < 10000; row++) { var startNo = GetCellValue(row, START_COLUMN); var lastNo = GetCellValue(row, LAST_COLUMN); if (startNo == 0 && lastNo == 0) break; contents.AddRange(Enumerable.Range(0, lastNo - startNo + 1).Aggregate(new List<string>(), (list, i) => { list.Add(string.Format("{0}{1:D4}{0}", "y", (startNo + i))); return list; })); } // 外側・内側ともforループを削除 var items = Enumerable.Range(1, 10000) .Select((i) => new { startNo = GetCellValue(i, START_COLUMN), lastNo = GetCellValue(i, LAST_COLUMN) }) .TakeWhile(j => j.startNo != 0 || j.lastNo != 0); foreach (var item in items) { var amount = item.lastNo - item.startNo + 1; contents.AddRange(Enumerable.Range(0, amount).Aggregate(new List<string>(), (list, k) => { list.Add(string.Format("{0}{1:D4}{0}", "z", (item.startNo + k))); return list; })); } return contents; } /// <summary> /// セルの値を取得する /// </summary> /// <param name="row">セルの行(0始まり)</param> /// <param name="column">セルの列(0始まり)</param> /// <returns>セルの値、取得できない場合は、セルの値は0を返す</returns> private static int GetCellValue(int row, int column) { var cell = new ExcelReference(row, column); var value = 0; if (int.TryParse(cell.GetValue().ToString(), out value)) return value; else return 0; } /// <summary> /// テキストファイルの出力 /// </summary> /// <param name="contents">テキストファイルの内容</param> /// <param name="fullpath">出力先のパス</param> /// <returns></returns> private static bool ExportTextFile(List<string> contents, string fullpath) { System.Text.Encoding encode = System.Text.Encoding.GetEncoding("SHIFT_JIS"); using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fullpath, true, encode)) { foreach (var content in contents) { sw.WriteLine(content); } } return true; } /// <summary> /// Excelファイルとして保存 /// </summary> /// <param name="txtpath">テキストファイルの出力先</param> /// <returns></returns> private static bool SaveAsExcelFile(string txtpath) { var regex = new System.Text.RegularExpressions.Regex("txt$"); var xlspath = regex.Replace(txtpath, "xls"); // Excel-DNAの機能を使用 XlCall.Excel(XlCall.xlcSaveAs, xlspath); return true; } /// <summary> /// Skypeメッセージの送信 /// </summary> /// <returns></returns> private static bool SendSkypeMessage(string menu) { // Skype4COMを使用 SKYPE4COMLib.Skype skype = new Skype(); skype.SendMessage(SKYPE_USER, menu); return true; } // 以下、IExcelAddIn用で、今回は使用しない public void AutoOpen() { } public void AutoClose() { } } }