【電脳少女プログラミング2088-壊レタ君を再構築-】B：ギャングのアジトをフィードバックを元に改善してみた

こんにちは。

最近、paizaを利用しておりキャンペーンがあるとのことで記事を書いてみることにしました。
問題は【電脳少女プログラミング2088-壊レタ君を再構築-】B：ギャングのアジトです。

https://ppg.paiza.jp/denno2088?from=entertainment

# 問題
N×N列の0と1で表現されるピクセルアートが左右対象かどうかを判定し出力する。

# 考えたこと
* 真ん中の列番号を求め、左右対称を判断する。
* 列の数（N)が奇数と偶数の場合で真ん中の列番号の値が異なるため（奇数は１つに決まるが、偶数は２つ存在する）場合分けが必要。
※サンプルケースが奇数の場合のみしか記載していなかったので偶数の場合を検討漏れが発生しそうな問題だな。。。

# テストデータをクリアした初回ソース
なぜこのような書き方をしたのか見出しにしたのかというと1回目は偶数の場合でミスがあり、クリアできませんでした。。。
```c#:初回ソース
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int N = int.Parse(Console.ReadLine());
        string[,] banmen = new string[N,N];
        for(int i = 0; i < N; i++)
        {
            string[] input = Console.ReadLine().Trim().Split(' ');
            for(int j = 0; j < N; j++)
            {
                banmen[i,j] = input[j];
            }
        }
        bool outputflg = true;
        // 盤面が奇数の時
        if(N % 2 == 1)
        {
            int center = (int)(N / 2);
            for(int m = 0; m < N; m++)
            {
                for(int n = 0; n <= center; n++)
                {
                    if(n == 0)
                    {
                        // nが0の時は影響しないため何もしない
                    }
                    else
                    {
                        if(!banmen[m,center -n].Equals(banmen[m,center + n]))
                        {
                            outputflg = false;
                        }
                    }
                }
            }
        }
        // 盤面が偶数の時
        if(N % 2 == 0)
        {
            int center_r = (int)(N / 2);
            int center_l = center_r - 1;
            for(int k = 0; k < N; k++)
            {
                for(int l = 0; l < center_r; l++)
                {
                    if(!banmen[k,center_l - l].Equals(banmen[k,center_r + l]))
                    {
                        outputflg = false;
                    }
                }
            }
        }
        string output = outputflg ? "Yes" : "No";
        Console.WriteLine(output);
    }
}
```
ざっくり解説すると、、、
* ２次元配列を使用し、ピクセルアートのデータを格納。
* 列の数（N)が奇数の場合と偶数の場合に分ける。
* 列番号の真ん中から外側に向かって値が一致するかを確認し、一致しなかった場合に、出力判定フラグをfalseにする。
* 最終的に出力判定フラグの値が　trueの場合にYes、falseの場合に、Noを出力する。

考え方に沿って記載しているので、正しいがなんか長い。。。
レイミ（少女ロボ）からのフィードバックは以下の通り
> 構造はしっかりしているが、奇数と偶数の場合分けを統一し、ループの回転効率を上げる

→ 確かに、奇数と偶数で似たような記述が多い！統一できないかを検討しました

# 奇数と偶数の場合分けを統一
```c#:奇数と偶数の場合分けを統一
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int N = int.Parse(Console.ReadLine());
        string[,] banmen = new string[N,N];
        for(int i = 0; i < N; i++)
        {
            string[] input = Console.ReadLine().Trim().Split(' ');
            for(int j = 0; j < N; j++)
            {
                banmen[i,j] = input[j];
            }
        }
        int center = (int)(N / 2);
        for(int m = 0; m < N; m++)
        {
            for(int n = 0; n < center; n++)
            {
                // 盤面が奇数の時、中心の右側を１足す
                int center_r = (N % 2 == 1) ? center + 1 : center;
                int center_l = center -1;
                if(!banmen[m,center_l - n].Equals(banmen[m,center_r + n]))
                {
                    Console.WriteLine("No");
                    return ;
                }
            }
        }
        Console.WriteLine("Yes");
    }
}
```
結構、スッキリなりました！for文いつにまとめ、center_lとcenter_rを奇数偶数どちらでも一緒の変数にしたことが大きな要因ですね。

また、小さな修正としてoutputflgを削除し、左と右の値が違う時点でNoの結果を出力しretuenで処理を終了させることとしました。

レイミ（少女ロボ）からのフィードバックは以下の通り
>centerの計算回数を1回にする。
>banmenをstring[][]の形にする。

→この段階ではcenterの計算回数を1回にするは謎でした。
→[][]が二つ！？今はSE２年目なのですが、１年目の研修でなんか出てきたような。。。
調べた結果ジャグ配列とういうものでした。
>通常の配列が各行の要素数が同じであるのに対し、ジャグ配列では各行の要素数が異なることができます。
https://jp-seemore.com/sys/17983/

Qiitaにジャグ配列の記事があったので参考までに
https://qiita.com/No2DGameNoLife/items/d7cd5f55da4c6958b67b

ということでジャグ配列を使用してコードを書いてみました。

# ジャグ配列を使用した改善
```c#:ジャグ配列を使用したソース
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int N = int.Parse(Console.ReadLine());
        
        string[][] banmen = new string[N][];
        for(int i = 0; i < N; i++)
        {
            banmen[i] = Console.ReadLine().Trim().Split(' ');
        }
        int center = (int)(N / 2);
        for(int m = 0; m < N; m++)
        {
            for(int n = 0; n < center; n++)
            {
                // 盤面が奇数の時、中心の右側を１足す
                int center_r = (N % 2 == 1) ? center + 1 : center;
                int center_l = center -1;
                if(!banmen[m][center_l - n].Equals(banmen[m][center_r + n]))
                {
                    Console.WriteLine("No");
                    return ;
                }
            }
        }
        Console.WriteLine("Yes");
    }
}
```
こんな書き方もあるんだという勉強になりました。仕事ではなかなか目にする機会がなかったので。
for文も入れ子しなくていいんでよりスッキリとしてソースとなりました。
恒例となったレイミのフィードバックです。
> 左右対称の判定は端から中心がシンプル
> 不要な変数は省略できる

→ここで気がつきました。実は奇数偶数を気にせず左右対象の判定ができることを。変に中心から端へという考えがあったからでしょう。発想の転換が重要ですね。
１つ前のフィードバックでcenterの計算回数が1回にできるって多分このことを言いたかったのでしょう。

# 左右対称判定を端から中心へ
```c#:左右判定を端から中心へ
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int N = int.Parse(Console.ReadLine());
        
        string[][] banmen = new string[N][];
        for(int i = 0; i < N; i++)
        {
            banmen[i] = Console.ReadLine().Trim().Split(' ');
        }
        int center = (int)(N / 2);
        for(int m = 0; m < N; m++)
        {
            for(int n = 0; n < center; n++)
            {
                if(!banmen[m][n].Equals(banmen[m][(N -1) - n]))
                {
                    Console.WriteLine("No");
                    return ;
                }
            }
        }
        Console.WriteLine("Yes");
    }
}
```
奇数偶数の判定のソースが消えた！こんなにもシンプルになるとは！！
バイト数も1665　→ 674バイトに！！
プログラミングって面白い。他の問題だとレイミのアドバイスがよく分からないこともありましたが、この問題では的確で大きく改善できました。

# 最後に
初めて記事を書いてみましたが楽しかったです。文章が苦手なので読みずらいところもあったかもですが、最後まで読んでいただきありがとうございます。