Linqで1行ずつ与えられた、データを縦に分割しよう！（ToLookup使用）

#目的

1行ずつ与えられるデータ（ファイルとか、標準入力とか）が重なって2次元配列みたいなデータを列で分割して、楽に変換したい。

## サンプル

例えば、こんなデータがあったとします。

```csharp:Sample.cs
var str = new List<string>()
              {
                  "  1,  2,  3,  4,  5,  6,  7,  8,  9, 10",
                  "  2,  4,  6,  8, 10, 12, 14, 16, 18, 20",
                  "  3,  6,  9, 12, 15, 18, 21, 24, 27, 30",
                  "  4,  8, 12, 16, 20, 24, 28, 32, 36, 40",
                  "  5, 10, 15, 20, 25, 30, 35, 40, 45, 50",
                  "  6, 12, 18, 24, 30, 36, 42, 48, 54, 60",
                  "  7, 14, 21, 28, 35, 42, 49, 56, 63, 70",
                  "  8, 16, 24, 32, 40, 48, 56, 64, 72, 80",
                  "  9, 18, 27, 36, 45, 54, 63, 72, 81, 90",
                  " 10, 20, 30, 40, 50, 60, 70, 80, 90,100",
              };
```

これを列（1列目だと、1,2,3, ... , 10 のように）ごとに取得したい。

## for文で
forなら簡単に取得ができる

```csharp:ForSample.cs
            var sample = new List<string>();
            for (int i = 0; i < str.Count; i++)
            {
                sample.Add(str[i].Split(',')[0]);
            }

            Console.WriteLine("forサンプル");
            Console.WriteLine(string.Join(",", sample));
```

こんな感じでこれを実行すると、こんな感じになる
![image.png](https://qiita-image-store.s3.amazonaws.com/0/110736/0c468036-5022-009d-dd0b-322746e66613.png)

1列目だけでいいのなら、このサンプルコードでもいいのだけど、複数行対応したいときにはネストをしないといけなくなる。
例えばこんな感じ

```csharp:AllFor.cs
//// 1つ目のデータで配列の数を設定
            var length = str[0].Split(',').Length;

            // 各列のリストを初期化
            var sample = new List<List<string>>();
            for (int i = 0; i < length; i++)
            {
                sample.Add(new List<string>());
            }

            // 1行をカンマで分割し、それぞれのリストに追加していく
            for (int i = 0; i < str.Count; i++)
            {
                var oneLine = str[i].Split(',');
                for (int j = 0; j < length; j++)
                {
                    sample[j].Add(oneLine[j]);
                }
            }

            // 表示
            for (int i = 0; i < length; i++)
            {
                Console.WriteLine($"i : {i}");
                Console.WriteLine(string.Join(",", sample[i]));
            }
```

冗長な部分もあると思いますが、素直に書いたら大体こんな感じになると思います。意外と長くなってしまいます。

## Linqで
次はLinqで書いてみたいと思います。こちらの方がやはり少ない行数で書けるということで、いいですね。慣れるまでは大変ですが。

```csharp:LinqSample.cs
            // 1行をカンマで分割して、0からインデックスを追加
            var strWithIndex = str.Select(
                x => x.Split(',').Where(word => !string.IsNullOrWhiteSpace(word)).Select((word, i) => new { word, i }));
            // 全ての行を平坦化して、先ほど追加したインデックスでグループ化する.
            var oneColumnData = strWithIndex.SelectMany(word => word).ToLookup(x => x.i);

```

一応この2行で、列で取得することが可能です。
実際に取得するとこんな感じです。

```csharp:LinqOutput.cs
            foreach (var oneColumn in oneColumnData)
            {
                Console.WriteLine("キー：" + oneColumn.Key);
                Console.WriteLine(string.Join(",", oneColumn.Select(x => x.word)));
            }
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/110736/1f9f55a6-f62a-4a55-7b01-4b1f7c5fb340.png)

これで、2次元配列とかも簡単に列で取得することができますね！

##簡単な解説
このままで終わるのもあれなので、一応解説をしておきます。

まず、LinqSample.csの1行目はコメントにも書いてある通り、1行をまずカンマで分割し、そのあと分割後の要素が｛空、Null、string.Empty｝ではない要素のみ取得し、その要素にインデックスを付与しています。
1つだけ実行するとこんな感じ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/110736/aac577a6-8d4f-40f1-4199-e99d6cce24ac.png)
ここでの、iの値がそのまま列のインデックスとなります。

2つ目は、全部を列挙できるような状態に直してからインデックスでグループを作っていきます。

全部列挙（SelectMany(x => x)）後の状態
![image.png](https://qiita-image-store.s3.amazonaws.com/0/110736/52940c07-d9de-2a6a-c578-4b57cf8bdd81.png)

ToLookupでグループ化した状態は先ほど見せた画像と同じです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/110736/1f9f55a6-f62a-4a55-7b01-4b1f7c5fb340.png)

##終わりに
いかがだったでしょうか？
次があれば、自分が便利だと思った、C#の標準メソッドなどを紹介したいです。
それではよいC#ライフを！