1.LINQ初見の印象は「めんどくさい」
運用しているシステムにバグが見つかって、ソースを調査することになった。
そこで初対面したLINQ。
率直な感想は。
「面倒くさい。」
LINQの面倒くさい理由
- メソッド(x=>x.col1)などの記述。
⇒この表現が謎でわかりにくい。 - WhereやSelect、OrderByなどDB操作で見かけるキーワード。
⇒DBじゃないのに、何で使う? - 使わなくても、それなりの処理を書ける。
⇒新しいことは覚えたくない。
担当者が、バグの原因をすぐに突き止めて対応をしたけど、今後対応する事が出てくるかもしれない。
避けてばかりもいられない。
自分なりに理解を深めるために、ロト6の当選データのチカラを借りることにした。
高額当選金額のシャワーを浴びて、いつか私も!!
2.LINQとは
任意のデータの集合に対して、DB操作する感覚で情報を抽出できる仕組みだと思っている。
ここで要素x1(第一当選数字),x2(第二当選数字),x3(一等当選金額)とういう要素を持つデータ集合list_sampleについて考えてみる。
個人的に使い方に戸惑ったメソッドについてあげてみた。
Select
抽出したいデータの要素の列を指定する。
データの集合.Select(x=>x.x1)
複数の列名(x1(第一当選数字),x2(第二当選数字)で)で抽出したいとき
list_sample.Select(x=> new {x.x1,x.x2})
newがポイント
OrderByDescending
データの集合.OrderByDescending(x => 指定列)
で記述する。
データを指定列の降順で並べる。
例)x3(一等当選金額)列を降順で並べる
var o1=list_sample.OrderByDescending(x => x.x3).Select(x=>x.x3);
※最高当選金額を目にして夢を持ちましょう。
ThenBy
データの集合に対して複数項目で並べる時に使う(2番目が昇順)
例)一等当選があった時のx1(第一当選数字),x2(第二当選数字)の昇順で並べる
var o1=list_sample.OrderBy(x => x.x1).ThenBy(x=>x.x2).Where(x=>x.x3>0).Select(x=> new {x.x1,x.x2});
朗報
where 条件に入っている列名を
selectしなくてもエラーはでません。
ThenByDescending
データの集合に対して複数項目で並べる時に使う(2番目が降順)
例)x1(第一当選数字)が昇順,x2(第二当選数字)が降順で並べる
var o2=list_sample.OrderBy(x => x.x1).ThenByDescending(x=>x.x2).Select(x=> new {x.x1,x.x2});
GroupBy
ある要素でグループ化して、集計したい場合に利用する。
例)x1(第一当選数字)の数値で分類分けして、出現回数を抽出する。
var o3 = list_sample.GroupBy(x=>x.x1).Select(x=> new{x.Key,Count0=x.Count()});
※分類分けに指定した列をSelectで指定するとき、x.Keyと記述する。
例)x1,x2の数値で分類分けして、出現回数を抽出する。
var o3 = list_sample.GroupBy(x=>new{x.x1,x.x2}).Select(x=> new{x.Key,Count0=x.Count()});
※上記でx1=1,x2=2の出現回数を知りたいときは
varo33=o3.Where(x=>
x.Key.x1==1 && x.Key.x2==2
).Count0
という指定方法になる。
LINQのメソッドを使って、次の項目ではプログラムを作成する。
3.プログラム概要
(1)参考URL(KYO's LOTO)で、ダウンロードしたLoto6.csv任意の場所(ソースではC:\work_c#)に配置し、これを読み込む。
(2)当選数字系データリスト(loto6_num_cnt)や、
当選金額系データリスト(loto6_amount)を作成する。
ロト6のCSVファイルの構成は下記のようになっている。
loto6_num_cnt:水色と緑色の枠の部分
loto6_amount:ピンク色と緑色の枠の部分
- 最大一等当選金額
- 最大キャリーオーバー金額(これが多いと夢も膨らむ!)
- 過去100回の一等当選金額合計額(大金のシャワーを浴びよう)
- 第一当選数字の最大数字
- 第六当選数字の最小数字
- 出現回数トップ2の当選数字と出現回数(出やすいのはどの数字?)
出力ファイルの内容
4.ソース
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace con_loto6_linq
{
internal class Loto6_num_cnt
{
//数字
public int Num2 { get; set; }
//何番目の数字
public int Type2 { get; set; }
//キャリーオーバーかどうか
public int Flg2 { get; set; }
//開催回
public int Kai2 { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace con_loto6_linq
{
internal class Loto6_amount
{
//開催回
public int No { get; set; }
//開催日
public string Date { get; set; }
//ボーナス数字
public int Num_b { get; set; }
//一等口数
public int Num_of_word { get; set; }
//当選金額
public long Win_amount { get; set; }
//キャリーオーバー
public long Carry_over1 { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace con_loto6_linq
{
internal class Program
{
static void Main(string[] args)
{
List<Loto6_amount> list_amount= new List<Loto6_amount>();
List<Loto6_num_cnt> list_cnt= new List<Loto6_num_cnt>();
// 読み込みたいCSVファイルのパスを指定して開く
string fname = @"C:\work_c#\loto6.csv";
StreamReader sr = new StreamReader(fname);
//ヘッダを読み飛ばす
string line_header = sr.ReadLine();
// 末尾まで繰り返す
while (!sr.EndOfStream)
{
// CSVファイルの一行を読み込む
string line = sr.ReadLine();
// 読み込んだ一行をカンマ毎に分けて配列に格納する
string[] line_csv = line.Split(',');
Loto6_amount lt6_amount = new Loto6_amount();
lt6_amount.No= int.Parse(line_csv[0]);
lt6_amount.Date = line_csv[1];
lt6_amount.Num_b = int.Parse(line_csv[8]);
lt6_amount.Num_of_word = int.Parse(line_csv[9]);
lt6_amount.Win_amount = long.Parse(line_csv[14]);
lt6_amount.Carry_over1 = long.Parse(line_csv[19]);
list_amount.Add(lt6_amount);
for (int j = 1; j < 7; j++)
{
Loto6_num_cnt lt6_cnt = new Loto6_num_cnt();
lt6_cnt.Kai2 = lt6_amount.No;
lt6_cnt.Num2 = int.Parse(line_csv[j+1]);
lt6_cnt.Type2 = j;
lt6_cnt.Flg2 = lt6_amount.Carry_over1 == 0 ? 0 : 1;
list_cnt.Add(lt6_cnt);
}
}
//★出力ファイル
string filepath2 = @"C:\work_c#\analysis_loto6.txt";
StreamWriter writer = new StreamWriter(filepath2, false, Encoding.UTF8);
//★★LINQ
//最大一等賞金
long max_amount = list_amount.Select(x=>x.Win_amount).Max();
//最大キャリーオーバー額
long max_carry1 = list_amount.Select(x => x.Carry_over1).Max();
//最新開催回
long newNo = list_cnt.Select(x => x.Kai2).Max();
//一等当選合計金額(過去100回の)
var order_total_win_amount = list_amount.Where(x => x.No >= newNo-99 && x.No <= newNo)
.Select(x => new { x.Win_amount, x.Num_of_word, multi0 = x.Win_amount * x.Num_of_word });
long total_win_amount = order_total_win_amount.Select(x => x.multi0).Sum();
writer.WriteLine("★★当選金額系データ");
writer.WriteLine("★最高1等当選金額:" + max_amount.ToString() + ",★最高キャリーオーバー:" + max_carry1.ToString());
writer.WriteLine("★過去100回の一等当選金額合計:"+total_win_amount.ToString());
//最大の第1数字
int max_no1 = list_cnt.Where(x => x.Type2 == 1).Select(x => x.Num2).Max();
//最小の第6数字
int min_no6 = list_cnt.Where(x => x.Type2 == 6).Select(x => x.Num2).Min();
//当選数字と出現回数
var max_pair = list_cnt.GroupBy(x => x.Num2).Select(x=>new { x.Key,count0=x.Count()}).OrderByDescending(x => x.count0);
//出現回数トップ2
var order_max_2cnt = max_pair.Select(x=>x.count0).Distinct().Take(2);
writer.WriteLine("★★当選数字系データ");
writer.WriteLine("★最大第一当選数字"+max_no1.ToString()+ ",★最小第六当選数字"+min_no6.ToString());
writer.WriteLine("★出現回数top2リスト");
foreach (var nn in order_max_2cnt)
{
var o2 = max_pair.Where(x => x.count0 == nn).Select(x => new { x.Key, x.count0 });
foreach (var o22 in o2)
writer.WriteLine(o22.count0.ToString() + "回," + o22.Key.ToString());
}
writer.Close();
Console.WriteLine("出力しました");
Console.ReadKey();
}
}
}
5.LINQを使用した感想
- listへの複雑な処理が、 DB操作する感覚で書ける!!
⇒DB処理の経験があるとイメージがわきやすい。 - 憂鬱なラムダ式への抵抗がなくなる。
- 余分な判定、ループに伴うインデントがないので、見やすい上に短い。
- 多くのメソッドを実装して、その魅力を存分に味わいたい。
6.参考url
はじめての LINQ
豊富なメソッドサンプルと説明でLINQの基礎が学べます。
KYO's LOTO
当選データのCSVファイルがダウンロードできる
とても有難いサイト。