【C#】Loto6当選データで学ぶ、プチLINQ入門

# 1.LINQ初見の印象は「めんどくさい」

運用しているシステムにバグが見つかって、ソースを調査することになった。
そこで初対面したLINQ。
率直な感想は。
## 「面倒くさい。」

:::note warn
__LINQの面倒くさい理由__
+ メソッド(x=>x.col1)などの記述。
⇒この表現が謎でわかりにくい。
+ WhereやSelect、OrderByなどDB操作で見かけるキーワード。
⇒DBじゃないのに、何で使う？
+ 使わなくても、それなりの処理を書ける。
__~~⇒新しいことは覚えたくない。~~__
:::

担当者が、バグの原因をすぐに突き止めて対応をしたけど、今後対応する事が出てくるかもしれない。
避けてばかりもいられない。

自分なりに理解を深めるために、ロト6の当選データのチカラを借りることにした。
高額当選金額のシャワーを浴びて、いつか私も！！

# 2.LINQとは
任意のデータの集合に対して、DB操作する感覚で情報を抽出できる仕組みだと思っている。

ここで要素x1(第一当選数字),x2(第二当選数字),x3(一等当選金額)とういう要素を持つデータ集合list_sampleについて考えてみる。
個人的に使い方に戸惑ったメソッドについてあげてみた。

### Select
抽出したいデータの要素の列を指定する。
データの集合.Select(x=>x.x1)

:::note info
`複数の列名(x1(第一当選数字),x2(第二当選数字)で)で抽出したいとき`
list_sample.Select(x=> __new__ {x.x1,x.x2})
newがポイント
:::

### OrderByDescending
`データの集合.OrderByDescending(x => 指定列)`で記述する。
データを指定列の降順で並べる。

:::note info
例）x3(一等当選金額)列を降順で並べる
var o1=list_sample.OrderByDescending(x => x.x3).Select(x=>x.x3);
※最高当選金額を目にして夢を持ちましょう。
:::

### ThenBy
`データの集合に対して複数項目で並べる時に使う(2番目が昇順)`

:::note info
例）一等当選があった時の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});
:::

:::note info 
朗報
where 条件に入っている列名を
selectしなくてもエラーはでません。
:::

### ThenByDescending
`データの集合に対して複数項目で並べる時に使う(2番目が降順)`

:::note info
例）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と記述する。

:::note info
例）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.プログラム概要
（１）参考URL(KYO's LOTO)で、ダウンロードしたLoto6.csv任意の場所（ソースではC:\work_c#）に配置し、これを読み込む。

（２）当選数字系データリスト(loto6_num_cnt)や、
当選金額系データリスト(loto6_amount)を作成する。

ロト６のCSVファイルの構成は下記のようになっている。

loto6_num_cnt:水色と緑色の枠の部分
loto6_amount:ピンク色と緑色の枠の部分

![loto6_linq1_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689780/db546b29-b08b-fc88-47ac-8751621aabd5.jpeg)

![loto6_linq2_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689780/e0e0e7b8-dd3a-dcfa-2eb8-9dca0348cd84.jpeg)
（３）下記の６項目についてファイルに出力する。
+ 最大一等当選金額
+ 最大キャリーオーバー金額(これが多いと夢も膨らむ！)
+ 過去100回の一等当選金額合計額（大金のシャワーを浴びよう）
+ 第一当選数字の最大数字
+ 第六当選数字の最小数字
+ 出現回数トップ2の当選数字と出現回数（出やすいのはどの数字？）

__出力ファイルの内容__

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689780/d71e9ae2-a32b-42b8-85b7-e3b974ebb388.png)


# 4.ソース

```c#:loto6_num_cnt.cs
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; }
 }
}
```
```c#:loto6_amount.cs
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; }
 }
}
```
```c#:Program.cs
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](https://qiita.com/nskydiving/items/c9c47c1e48ea365f8995)
豊富なメソッドサンプルと説明でLINQの基礎が学べます。

[KYO's LOTO](https://loto6.thekyo.jp/download/index)
当選データのCSVファイルがダウンロードできる
とても有難いサイト。