三流プログラマの戯言

プログラミング初心者が気になったことを書き綴るだけ。主にc#

Brainf*ckでオセロを書いた話

Brainf*ckでオセロ(Reversi)プログラム書いたので、メモ程度に投稿。

詳しい解説は気が向けばするかも…

一応,オーバーフロー、アンダーフローは反対側に出る。 標準入力はEnterも受け取ることにしています。

境界値判定は行っていないので、入力値がおかしいとバグります。

細かい説明とかは今度するかも…?

>>>++++++++[->>>++++++++++++>>>++++++<<< <<<]>>>- //ptr ==2 {0}{0}{95}{32}
// **** Make '_' pattern ***
[
  -
  >>> >>> >>> >>> >>> >>> >>> >>> >>>
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //1
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //2
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //3
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //4
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //5
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //6
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //7
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>> >>>   //8

  
  <<< <<<
  [[<<<]<<< <<< <<<]
  <<< <<< <<< <<< <<< <<<
]
// **** End of Make '_' pattern ***
>>>
// **** Make '1'to'9' pattern ***
>>> >>> >>> >>> >>> >>> >>> >>>+//1
[>>>]>>>++//2
[>>>]>>>+++//3
[>>>]>>>++++//4
[>>>]>>>+++++//5zz
[>>>]>>>++++++//6
[>>>]>>>+++++++//7
[>>>]>>>++++++++//8
<<< <<<
[[<<<]<<<]
<<< <<< <<< <<< <<< <<<
[-
  >>> >>> >>> >>> >>> >>> >>> >>>
  [+[>>>]>>>]
  <<< <<<
  [[<<<]<<<]
  <<< <<< <<< <<< <<< <<<
]
// **** End of Make '1'to'9' pattern ***
<<< <<< <<<
// **** Make 'A'to'H' pattern***
>>>++++++++[-<<<++++++++>>>]
>>>+        //A
>>>++       //B
>>>+++      //C
>>>++++     //D
>>>+++++    //E
>>>++++++   //F
>>>+++++++  //G
>>>++++++++ //H
[<<<]<<<
[-
  >>> >>>[+>>>]
  <<<[<<<]<<<
]
// **** End of Make 'A'to'H' pattern***
// **** Make first ' '  ***
++++++++[->>>++++<<<]
// **** End of Make  first ' ' ***
>>>
// **** Make '\n' pattern (Ignore last2 line '\n'***
  >>>[<<<++++++++++[>>>]>>>]
  
// **** End of Make '\n' pattern ***
// ****Make Last Line ' ' pattern***
++++++++[-<<<++++>>>]<<<
[
  -
  >>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+>>>+
  <<< <<< <<< <<< <<< <<< <<< <<< <<<
]
++++++++++
[>>>]++++++++++
>> >>> <<< << // Memory keep
// ****Print Stage***
[<<<]>>>[>[-]>[-]++++++++[-<++++>]<.[-]<.>>>]
// ****End of Print Stage***

// **** set First Stones ****
<<<[<<<] // Back to fiest Pointer
// **set 'o' to 4D
>++++++++++[-<++++>]<++++ //Set 4D
[ - > >>> [+>>>]+ [<<<]< ] > >>> [>>>]<
[-]//Zero Zet
++++[->+++++<]>++[-<+++++>]<+ // Set 'o'
> <<< [[-]<<<]< //Reset

// **set 'o' to 5E
>++++++++++[-<+++++>]<+++++ //Set 5E
[ - > >>> [+>>>]+ [<<<]< ] > >>> [>>>]<
[-]//Zero Zet
++++[->+++++<]>++[-<+++++>]<+ // Set 'o'
> <<< [[-]<<<]< //Reset

// **set 'x' to 5D
>++++++++++[-<+++++>]<++++ //Set 5D
[ - > >>> [+>>>]+ [<<<]< ] > >>> [>>>]<
[-]//Zero Zet
++++[->++++++<]>[-<+++++>]< // Set 'x'
> <<< [[-]<<<]< //Reset

// **set 'o' to 5E
>++++++++++[-<++++>]<+++++ //Set 4E
[ - > >>> [+>>>]+ [<<<]< ] > >>> [>>>]<
[-]//Zero Zet
++++[->++++++<]>[-<+++++>]< // Set 'x'
> <<< [[-]<<<]< //Reset

// **** End of Set number to Any Pointer ****

>>> [ >>> ] >>> //位置ポインタに移動
+               //ダミーの設置
>>>             //Move to TrunPlayerStone Pointer 

// **** Set StornColor to TrunStonePointer ****
[-]++++[->++++++<]>[-<+++++>]<  // Set 'x'
>>> 
[-]++++[->+++++<]>++[-<+++++>]<+ // Set 'o'
>>
>>>>> >>>                          //方向メモリの確保
// ************Set sering "It's '_' turn_\n\0"    (length:16) ※_の部分は0が代入されている
>>>>>>>>>>>>>>>+++++++++++++++++++
[<<<<<<<<<<<<<<<+++>++++++>++>++++++>+>++>>++>+>++++++>++++++>++++++>+++++>++>>-]
<++++++++++<++++++++<+++++++++++++++<<+++<++<+++++++++++++<+<<+<+++++++++++++<+<+<++<++++++++++++++++[>]>[>]
>
// **************Set sering "Plese input place_\n\0"(length:20)
>>>>>>>>>>>>>>>>>>>++++++++++++++++
[<<<<<<<<<<<<<<<<<<<+++++>++++++>++++++>+++++++>++++++>++>++++++>++++++>+++++++>+++++++>+++++++>++>+++++++>++++++>++++++>++++++>++++++>++>>-]
<++++++++++<++++++++++++++<+++++<+++<+<++++++++++++<<<++++<+++++<<++++++++++++++<+++++++++<<+++++<+++<+++++<++++++++++++<[>]

>
// *********************Set sering "Can't put here_\n\0"   (length:17)
>>>>>>>>>>>>>>>>++++++++++++++++
[<<<<<<<<<<<<<<<<++++>++++++>++++++>++>+++++++>++>+++++++>+++++++>+++++++>++>++++++>++++++>+++++++>++++++>++>>-]
<++++++++++<++++++++++++++<+++++<++<+++++<++++++++<<++++<+++++<<<++++<+++++++<++++++++++++++<+<+++[>]

<[<]<[<]<[<]<[<]
<< <<<<< << 
// **** End of Set StornColor to TrunStone Pointer ****
<<< <<< <<< <<< [<<<] //back to First Pointer


// ************************End of setUp

>>>[>[-]>[-]++++++++[-<++++>]<.[-]<.>>>]//Print Stage
>>> >>> >>> >>>
>>>>>>> 
[.>]//Print(It's ')
<[<]<<<<<< <<< <<< . >>> >>> >>>>>>>[>]//Print(手番石)
>[.>]//Print(' turn_)
>[.>]//Print(Prease Input place_)
<[<]<[<]<[<]
<<<<<< 
<<< <<< <<< <<<
<<<[<<<]//back to First Pointer

// *********************************************
>>> [ >>> ] >>>              //Move to PlacePointer 
[-]+
[//位置の入力により石を置く(メインループ)
  [-]>[-]>[-]<<                // メモリクリア
  ++++++[->++++++++<]>         // 一つ目の入力のオフセットを設定
  >,                           // 一つ目の入力を得る
  <[->-<]>                     // オフセットを引く
  <<++++[->++++<]>[-<++++++>]< // 二つ目の入力のオフセットを設定
  >,                           // 一つ目の入力を得る
  <[->-<]>                     // オフセットを引く
  >[-<<++++++++++>>]<[-<+>]<   // 番地を計算する(Add{ buf2*10_buf1 } )

  >,[-]<                       // 改行コードを読み取る
  // ********************************************
  // *****'_'かどうかの確認******
  >[-]>[-]<<[->>+<<]>>[-<+<+>>]<<  //位置メモリのbuf0をbuf1にコピー
  >[-< <<< <<< [<<<] >+< >>>[>>>]>>> > ]< //位置メモリのbuf1を始点メモリのbuf1に移動
  <<< <<< [<<<]                          //始点メモリにポインタを移動
  >[-<+>]<                               // buf0に値を移動
  
  [ - > >>> [+>>>]+ [<<<]< ] > >>> [>>>]< //ターゲットの位置に移動
  >++++++++[->++++++++++++<]>-<<          // make '_' to  buf2 (use buf1)
  [->+>-<<]>[-<+>]<                       // Cal buf2 minus val (use buf1)
  >[-]>
  [[-]+<]<[>]<                            // cast to Bool (1 or 0)
  >[-]>-
  [[+]+<]<[>]<                            // Get Not 
  >>[-< <<< [<<<] >+ < >>> [>>>] < >>]<<  //始点ポインタに結果の移動
  > <<< [<<<]<                            //始点ポインタに移動  
  >>                                      //置けるかどうかの判定結果が格納されているbuf2に移動
  [                                       //もし置けるのであれば
    [-]
    // *** 方向の値を代入
    <<                                    //位置調整
    >>> [>[[-]>>>]>>]
    >>> >>> >>> >>>                       //方向ポインタに移動
    [-]+          >                       //   1をセット
    [-]+++++++++  >                       //   9をセット
    [-]++++++++++ >                       //  10をセット
    [-]+++++++++++>                       //  11をセット
    // ***方向の値の代入終了
    <
    [                                       //すべての方向に関してのサーチ 
      /**************
      * 正方向の判定 *
      **************/
      [<]<< <<< <<<                    //位置メモリに移動
      >[-]>[-]<<[->>+<<]>>[-<+<+>>]<<  //位置メモリのbuf0をbuf1にコピー
      >[-< <<< <<< [<<<] >+< >>>[>>>]>>> > ]< //位置メモリのbuf1を始点メモリのbuf1に移動
      <<< <<< [<<<]                          //始点メモリにポインタを移動
      >[-<+>]<                               // buf0に値を移動
      [ - > >>> [+>>>]+ [<<<]< ]             //ターゲットの位置をセット (初期ポインタに移動)
      >>+                                   //初期ポインタのフラグ管理(buf2)に移動
      [                                     //相手の色の石が出なくなるまで繰り返す
        [-]                                 //フラグ値をゼロにする
        << >>> >>>[>>>] >>> >>> >>> >>>[>]< //方向ポインタの末尾に移動
        [->+>+<<]>>[-<<+>>]>+               //対象の方向値をコピーする
        [<<-<[<]>[<<<]<<<[<<<] >+< >>>[>>>] >>> >>> >>> >>>[>]>] //対象の方向値を開始ポインタに移動
        <<[<]>[<<<]<<<[<<<]                 //初期ポインタに移動
        >[-<+>]<                            // buf0に値を移動
        
        [ - > >>> [+>>>]+ [<<<]< ] > >>> [>>>]< //ターゲットの位置に移動
        >[-]>[-]<<[->+>+<<]>[-<+>]           //ターゲットの値をbuf2にコピー
        <<< [<<<]<                           //始点ポインタに移動 
        >>> [>>>] >>> >>> >>>                //相手色ポインタに移動
        >[-]>[-]<<[->>+<<]>>[-<+<+>>]        //相手色の値をbuf1にコピー
        <[-<[<<<] <<< [<<<] >+< >>> [>>>] >>> >>> >>> >] //相手色の値を始点ポインタのbuf1に移動
        <[<<<] <<< [<<<]                     //始点ポインタに移動
        >[-<+>]<                             // buf0に値を移動
        [-> >>> [>>>] > - < <<< [<<<] < ]    //相手色とターゲットマスの色の差をターゲットポインタのbuf2に格納
        > >>> [>>>] >                        //ターゲットポインタのbuf2に移動
        [[-]<]<[>>]<[>>+<]                   //Notをとる(buf0がTrueでbuf1がfalseであることを利用)(buf1に移動する)
        >[ < <<< [<<<] >+< >>> [>>>] ]<[>]   //演算結果を始点ポインタのbuf2にコピー(buf1に移動)
        <<< [<<<]                            //始点ポインタbuf1に移動
        >                                    //始点ポインタbuf2に移動
      ]
  
  
      // ***ストップ位置が自分の色かの判定***
      <<                                     //始点ポインタbuf0に移動 
      > >>> [>>>]<                           //ターゲットの位置(buf0)に移動
      >[-]>[-]<<[->+>+<<]>[-<+>]             //ターゲットの値をbuf2にコピー(buf1に移動)
      <<< [<<<]<                             //始点ポインタに移動 
      >>> [>>>] >>> >>>                      //自分色ポインタに移動
      >[-]>[-]<<[->>+<<]>>[-<+<+>>]          //自分色の値をbuf1にコピー
      <[-<[<<<] <<< [<<<] >+< >>> [>>>] >>> >>> >] //相手色の値を始点ポインタのbuf1に移動
      <[<<<] <<< [<<<]                       //始点ポインタに移動
      >[-<+>]<                               // buf0に値を移動
      [-> >>> [>>>] > - < <<< [<<<] < ]      //相手色とターゲットマスの色の差をターゲットポインタのbuf2に格納
      > >>> [>>>] >                          //ターゲットポインタのbuf2に移動
      [[-]<]<[>>]<[>>+<]                     //Notをとる(buf0がTrueでbuf1がfalseであることを利用)(buf1に移動する)
      >[[-] < <<< [<<<] >+< >>> [>>>] ]<[>]  //演算結果を始点ポインタのbuf2にコピー(buf1に移動)
      <<< [<<<]                              //始点ポインタbuf1に移動
      
      // ***End of ストップ位置が自分の色かの判定***
  
      >                                      //始点ポインタのbuf2に移動
      [                                     //ひっくり返せるなら
        [-]                                 //フラグ値をゼロにする
        >[>>[+>>>]>] <<< [<<<] >>       //ひっくり返すフラグをインクリメント
      ]
  
      << >>> >>>[>>>] >>> >>> >>> >>>[>]< //方向ポインタの末尾に移動
      [->+>+<<]>>[-<<+>>]>+               //対象の方向値をコピーする
      [<<-<[<]>[<<<]<<<[<<<] >+< >>>[>>>] >>> >>> >>> >>>[>]>] //対象の方向値を開始ポインタに移動
      <<[<]>[<<<]<<<[<<<]                 //初期ポインタに移動
      >[-<+>]<                            // buf0に値を移動
     
      [ - > >>> [->>>] <<< <<< [<<<]< ] > >>> [>>>]< //ターゲットの位置に移動 
      >
      +[[-]<<<]    //buf1のデータを初期化
      
      >>[>>[->>>]>] <<< [<<<]> //ひっくり返すフラグをデクリメントし正規のひっくり返る位置のみフラグを立てる
      >>[>>[+>>>]>]       //ひっくり返すフラグをインクリメントし、終点ポインタに移動
  
      >>> >>> >>> >>> [>]<             //方向ポインタの最終ポインタに移動
  
  
      /**************
      * 負方向の判定 *
      **************/
      [<]<< <<< <<<                    //位置メモリに移動
      >[-]>[-]<<[->>+<<]>>[-<+<+>>]<<  //位置メモリのbuf0をbuf1にコピー
      >[-< <<< <<< [<<<] >+< >>>[>>>]>>> > ]< //位置メモリのbuf1を始点メモリのbuf1に移動
      <<< <<< [<<<]                          //始点メモリにポインタを移動
      >[-<+>]<                               // buf0に値を移動
      [ - > >>> [+>>>]+ [<<<]< ]             //ターゲットの位置をセット (初期ポインタに移動)
  
      >>+                                    //初期ポインタのフラグ管理(buf2)に移動
      [                                     //相手の色の石が出なくなるまで繰り返す
        [-]                                 //フラグ値をゼロにする
        << >>> >>>[>>>] >>> >>> >>> >>>[>]< //方向ポインタの末尾に移動
        [->+>+<<]>>[-<<+>>]>+               //対象の方向値をコピーする
        [<<-<[<]>[<<<]<<<[<<<] >+< >>>[>>>] >>> >>> >>> >>>[>]>] //対象の方向値を開始ポインタに移動
        <<[<]>[<<<]<<<[<<<]                 //初期ポインタに移動
        >[-<+>]<                            // buf0に値を移動
        
        [ - > >>> [->>>]<<< <<< [<<<]< ] > >>> [>>>]< //ターゲットの位置に移動
        >[-]>[-]<<[->+>+<<]>[-<+>]           //ターゲットの値をbuf2にコピー
        <<< [<<<]<                           //始点ポインタに移動 
        >>> [>>>] >>> >>> >>>                //相手色ポインタに移動
        >[-]>[-]<<[->>+<<]>>[-<+<+>>]        //相手色の値をbuf1にコピー
        <[-<[<<<] <<< [<<<] >+< >>> [>>>] >>> >>> >>> >] //相手色の値を始点ポインタのbuf1に移動
        <[<<<] <<< [<<<]                     //始点ポインタに移動
        >[-<+>]<                             // buf0に値を移動
        [-> >>> [>>>] > - < <<< [<<<] < ]    //相手色とターゲットマスの色の差をターゲットポインタのbuf2に格納
        > >>> [>>>] >                        //ターゲットポインタのbuf2に移動
        [[-]<]<[>>]<[>>+<]                   //Notをとる(buf0がTrueでbuf1がfalseであることを利用)(buf1に移動する)
        >[ < <<< [<<<] >+< >>> [>>>] ]<[>]   //演算結果を始点ポインタのbuf2にコピー(buf1に移動)
        <<< [<<<]                            //始点ポインタbuf1に移動
        >                                    //始点ポインタbuf2に移動
      ]
  
  
      // ***ストップ位置が自分の色かの判定***
      <<                                     //始点ポインタbuf0に移動 
      > >>> [>>>]<                           //ターゲットの位置(buf0)に移動
      >[-]>[-]<<[->+>+<<]>[-<+>]             //ターゲットの値をbuf2にコピー(buf1に移動)
      <<< [<<<]<                             //始点ポインタに移動 
      >>> [>>>] >>> >>>                      //自分色ポインタに移動
      >[-]>[-]<<[->>+<<]>>[-<+<+>>]          //自分色の値をbuf1にコピー
      <[-<[<<<] <<< [<<<] >+< >>> [>>>] >>> >>> >] //相手色の値を始点ポインタのbuf1に移動
      <[<<<] <<< [<<<]                       //始点ポインタに移動
      >[-<+>]<                               // buf0に値を移動
      [-> >>> [>>>] > - < <<< [<<<] < ]      //相手色とターゲットマスの色の差をターゲットポインタのbuf2に格納
      > >>> [>>>] >                          //ターゲットポインタのbuf2に移動
      [[-]<]<[>>]<[>>+<]                     //Notをとる(buf0がTrueでbuf1がfalseであることを利用)(buf1に移動する)
      >[[-] < <<< [<<<] >+< >>> [>>>] ]<[>]  //演算結果を始点ポインタのbuf2にコピー(buf1に移動)
      <<< [<<<]                              //始点ポインタbuf1に移動
      
      // ***End of ストップ位置が自分の色かの判定***
  
      >                                      //始点ポインタのbuf2に移動
      [                                     //ひっくり返せるなら
        [-]                                 //フラグ値をゼロにする
        >[>>[+>>>]>] <<< [<<<] >>       //ひっくり返すフラグをインクリメント
      ]
  
      << > >>> [>>>]//ターゲットの位置に移動 

      +[[-]<<<]    //buf1のデータを初期化
      >>[>>[->>>]>] <<< [<<<] > //ひっくり返すフラグをデクリメントし正規のひっくり返る位置のみフラグを立てる
      >>[>>[+>>>]>]       //ひっくり返すフラグをインクリメントし、終点ポインタに移動
  
      >>> >>> >>> >>> [>]<             //方向ポインタの最終ポインタに移動
      
      >>>[-]<<<                        //フラグに使ってたメモリをクリア
      [-]<
  
    ]//End of すべての方向へのサーチ
    > <<< [<<<]//終点ポインタに移動
    <<<
    [<                                 //ひっくり返すフラグが立っている場所を探す
      [                                //フラグが立っていれば         
        <<[<<<]                          //初期ポインタに移動
        >+                               //buf1に結果を格納
        >                                //調整のためbuf2に移動
      ]
    <<]                 //終点は初期ポインタ
    >
    [                                 //ひっくり返せるとき(buf1)
      [-]      
      < >>> [>>>]                      //終点ポインタに移動
      >>>                              //配置位置ポインタに移動  
      >[-]>[-]<<[->>+<<]>>[-<+<+>>]<<  //位置メモリのbuf0をbuf1にコピー
      >[-< <<< <<< [<<<] >+< >>>[>>>]>>> > ]< //位置メモリのbuf1を始点メモリのbuf1に移動
      <<< <<< [<<<]                          //始点メモリにポインタを移動
      >[-<+>]<                               // buf0に値を移動
      [ - > >>> [+>>>]+ [<<<]< ]             //ターゲットの位置をセット (初期ポインタに移動)
      > >>> [>>>]                            //ターゲット位置に移動
      > [-]+                                //ターゲット位置のひっくり返すフラグを立てる
      <+[[-]<<<] <                          //ターゲット位置情報をクリアしつつ初期位置に戻る
      >>> [>>[<<[-]+>]>[>]< ]               //ひっくり返すフラグがある位置の色情報を1にする
      >                                     //終点ポインタに移動
  
      >>> >>>                               //自分の色ポインタに移動
      [->>+<<] >> [-<+<+>>]                 //buf1にコピー
      <-                                    // 値コピー用の数字を1引いておく(既に1は加算されているため)
      [-                                    //自分の色をひっくり返る位置にコピー
        < <<< <<< <<< [<<<]                   //始点ポインタに移動
        >>> [>>[<<+>]>[>]< ]               //ひっくり返すフラグがある位置の色情報をインクリメント
        >                                     //終点ポインタに移動
      >>> >>> >                                //自分の色ポインタのbuf1に移動
        
      ]
      < <<< <<< <<< [<<<] > //始点ポインタBuf1に移動
      [-]
      >[-]+<//buf2にひっくり返せたことを格納する
    ]
    >[[-]<+>]<//buf2(ひっくり返したか)をbuf1に移動
    >[-]//条件分岐を合わせるためにbuf2に移動後クリア
  ]//End of 
  [-]+//buf2にTrueを代入
  <//buf1に移動
  [//ひっくり返えしたとき
    // ****手番プレーヤーを入れ替える
   < >>> [>>>] >>> >>> >>> //相手プレイヤーポインタに移動
    [-<<+>>]//手番プレイヤーポインタのbuf1にデータを移動
    <<< //手番プレイヤーポインタに移動
    [->>>+<<<] //相手プレイヤーポインタにデータを移動
    >[-<+>]    //相手プレイヤーポインタのbuf1をbuf0に移動 
    <          //相手プレイヤーポインタに移動
  
    <<< <<< <<< <<< [<<<] //始点ポインタに移動
    
    //盤面の再描画
  
    >>>[>[-]>[-]++++++++[-<++++>]<.[-]<.>>>]//Print Stage
    //終点ポインタに移動している
    
    >>> [>>>] >>>>>>> //文字列メモリの先頭に移動
    <//文字メモリの先頭のひとつ前に戻る
    <
  ]//End of 返えしたとき 
  >
  [//ひっくり返せなかったとき
   << >>> [>>>] //終点ポインタに移動
    >>> [>>>] >>>>>>> //文字列メモリの先頭に移動
    [>]> [>]> [>]> //3(4)文目に移動
    [.>]//print(can't put here)
    <[<] <[<] <[<] <[<] //文字メモリの先頭のひとつ前に戻る
  ]
  // ****ポインタ=文字メモリの先頭のひとつ前
  共通メッセージの描画
  >[.>]//Print(It's ')
  <[<]<<<<<< <<< <<< . >>> >>> >>>>>>>[>]//Print(手番石)
  >[.>]//Print(' turn_)
  >[.>]//Print(Prease Input place_)
  <.   //改行を出力
  [<]<[<]<[<]
  <<< <<< <<< <<< <<< //位置ポインタに移動
  [-]//位置ポインタの値を初期化
  +//繰り返しフラグを立てる
]//End of 石を置くメインループ

検索用 : Brinfuck Brainf**k リバーシ

Delegate?.Invoke(); がスレッドセーフだという話。

皆さん、デリゲード使ってますか? デリゲードを使ってNullReferenceExceptionを出したことがあるのは私だけではないと思います。
nullチェックはしっかりしましょう。

ここでnullチェックをする上で、重要となるのがスレッドセーフかどうか。という話になります。 スレッドセーフじゃないコードを書いてしまうと、「低確率で例外が発生するけど、再現性が無い」といった事態になりかねません。

とりあえず適当なコードを書きます。

namespace Test
{
    class Test
    {
        public static void MainFunc()
        {
            new Test().Do1();
            new Test().Do2();
            new Test().Do3();
        }

        delegate void NoneMethod();

        NoneMethod _noneMethod = null;

        private void Do1()
        {
            if (_noneMethod != null)
                _noneMethod();
        }

        private void Do2()
        {
            var tempMethod = _noneMethod;
            if (tempMethod != null)
                tempMethod();
        }

        private void Do3()
        {
            _noneMethod?.Invoke();
        }
    }
}

Do1()が安直な書き方、 Do2()がスレッドセーフな書き方、 Do3()が私が使用している環境(VS2017/C#7.0)で推奨される書き方です。
ちなみにVS2017を使ってますが、Do1(),Do2()Do3()で書く事をVSに推奨されます。

Do1()if (_noneMethod != null)_noneMethod()の間で別スレッドから変更が加わった際にNullReferenceExceptionを吐きうるというわけです。 一方でDo2()は一度ローカルにコピーをとっているため、フィールドである_noneMethodに変更が加わろうが、問題ないわけです。
で、問題はDo3()。推奨されているぐらいだからスレッドセーフでしょうけど。
まぁ、結論はタイトルにある通りスレッドセーフ(っぽい)ですね。
なぜ「っぽい」にしてるかというと、ILを読んだわけですが、ILを読むのが初めてなのでなにか勘違いがあるかも。という話です。
以降の話は、にわか知識なので、なにか間違い・変な点があったらコメントください。
ちなみに参考にしたのはこのPDF

https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf

それぞれのをReleseビルドでコンパイルした物のILのコードを載せます。
ちなみにILでの表示はildasm.exeを使用してます。検索したら出てくるかと。
まずはDo1()

.method private hidebysig instance void  Do1() cil managed
{
  // Code size       20 (0x14)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class Test.Test/NoneMethod Test.Test::_noneMethod
  IL_0006:  brfalse.s  IL_0013
  IL_0008:  ldarg.0
  IL_0009:  ldfld      class Test.Test/NoneMethod Test.Test::_noneMethod
  IL_000e:  callvirt   instance void Test.Test/NoneMethod::Invoke()
  IL_0013:  ret
} // end of method Test::Do1

次にDo2()

.method private hidebysig instance void  Do2() cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init ([0] class Test.Test/NoneMethod tempMethod)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class Test.Test/NoneMethod Test.Test::_noneMethod
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldloc.0
  IL_000b:  callvirt   instance void Test.Test/NoneMethod::Invoke()
  IL_0010:  ret
} // end of method Test::Do2

最後にDo3()

.method private hidebysig instance void  Do3() cil managed
{
  // Code size       17 (0x11)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class Test.Test/NoneMethod Test.Test::_noneMethod
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_000b
  IL_0009:  pop
  IL_000a:  ret
  IL_000b:  callvirt   instance void Test.Test/NoneMethod::Invoke()
  IL_0010:  ret
} // end of method Test::Do3

この時点で、Do3()はスレッドセーフな気がしますね。(だって一回しか呼んでないですし)
1行目はメソッドの説明、3行目はコードのサイズ、4行目はスタックの最大数ですかね。 この辺は重要でないですし省略しましょう。
つぎにDo2()の5行目.locals init ([0] class Test.Test/NoneMethod tempMethod)これはローカル変数の初期化ですかね。これも重要じゃないので飛ばします。

ちなみにIL_0000とかIL_000aとかはコードラベルです。フロー制御に使われていますね。
重要なのは、その次から。

まずDo1()に関して。
ldarg.0:0番目の引数をスタックに積みます。関数に引数はないですが、呼び出し元のインスタンス情報が入っています。
ldfld:スタックの一番上のインスタンスのフィールドの値をスタックに積みます。
brfalse.s:スタックをポップし、その値が0なら指定ラベルに飛びます。
ldarg.0ldfld:先ほどと同じです。_noneMethodを呼んできています。ここがスレッドセーフじゃない理由ですね。
callvirt:指定したメソッドをスタックの一番上を元に実行します。
ret:呼び出し元に制御を返します。
以上です。
上にも書きましたが、フィールドを2回呼んでるのでスレッドセーフじゃないわけです。

ではDo2を見てみましょう。
ldarg.0ldfld:先ほどと同じです。_noneMethodを呼んできています。
stloc.0:0番目のローカル変数にスタックの一番上を代入します。
ldloc.0:0番目のローカル変数をスタックの一番上におきます。
brfalse.s:スタックをポップし、その値が0なら指定ラベルに飛びます。
ldloc.0:0番目のローカル変数をスタックの一番上におきます。
callvirt:指定したメソッドをスタックの一番上を元に実行します。
ret:呼び出し元に制御を返します。
以上です。
スタックは利用してしまうと値がpopされ再利用できませんが、ローカルにコピーすることで、再利用しています。
フィールドは一回しか呼んでいないので、スレッドセーフになります。

最後にDo3()
ldarg.0ldfld:先ほどと同じです。_noneMethodを呼んできています。
dup:スタックの一番上の値を取り出し、その値を2回プッシュします。すなわち、一番上の値をコピーします。
brtrue.s:スタックをポップし、その値が非0なら指定ラベルに飛びます。
pop:スタックの一番上の値を取り出します。
ret:呼び出し元に制御を返します。
callvirt:指定したメソッドをスタックの一番上を元に実行します。
ret:呼び出し元に制御を返します。
以上です。
Do2()と同じくフィールドを一回しか呼んでないので、スレッドセーフになります。
また、Do2()と比べて、コード長は等しいものの、実行が速いであろうことが予測されます。まぁ誤差でしょうけど。

長々と書きましたが、デリゲードのnullチェックはDelegate?.Invoke();が良いということですね。伊達に推奨されているわけではないということです。

ではさようなら。

(IL覗いてると、難解プログラミング言語勉強している気になりますね。)

浮動小数点型(double型)の最小値

みなさんこんにちは、こんなしょうもない記事をもてくれる人は片手で数えれるほどしかいないと思います。

そんなみなさん、こんな疑問を抱いたことはないですか?

浮動小数点型の中で最小の値ってなんだろう?」

と。 ないですかね? 私はないです。

いや、だってdouble.MinValueという値があるじゃないですか。

ちなみに、皆さん答え分かりますか?そう、double.NegativeInfinityがありますね

double.MinValueは最小値なのに、double.NegativInfinityは負の無限大だからいかなる定数よりも小さいはず。果たしてどちらが小さいのか。

ってわけで確認していきましょう。

最小値を出す上でいくつかの数字をピックアップ。

var array = new[]
{
    0,
    double.MinValue,
    double.MaxValue,
    double.Epsilon,
    double.PositiveInfinity,
    double.NegativeInfinity,
    double.NaN
};

MaxValueとかは間違いなく違うのですが、とりあえず入れておきます。

最小値の算出のために以下のメソッドを書きました。

private static double GetMin(double[] array)
{
    var min = array[0];

    for (int i = 1; i < array.Length; i++)
        if (array[i] < min) min = array[i];
            
    return min;
}


private static double GetMin2(double[] array)
{
    var min = array[0];

    for (int i = 1; i < array.Length; i++)
        min = Math.Min(min, array[i]);

    return min;
}

メソッドを二つ用意はしましたが、実行結果は同じになるはずです。

実行のコードは以下の通り。

Console.WriteLine($"Min by MyMethod1 : {GetMin(array)}");
Console.WriteLine($"Min by MyMethod2 : {GetMin2(array)}");

で、気になる実行結果は

Min by MyMethod1 : -∞
Min by MyMethod2 : NaN

あれ?

二つのメソッドで違う答えが出てきました。どういうことでしょう。

これは、Microsoftの設計理念として、NaNはいかなる値よりも小さい値であるというものがあります。*1 ただし比較演算子を使用した場合は、NaNは値を持たないので、常にfalseを返すようになっています。*2 すなわち下記のmin1min2は違う結果になるわけです。

var val = doubel.NaN;
var min1 = (val < min )? val : min;
var min2 = Math.Min(val , min);

ほかの方法で最小値を出すと以下の通りになります。

var sortArray = array.ToArray();
Array.Sort(sortArray);
Console.WriteLine($"Min by Sort      : {sortArray.First()}");
Console.WriteLine($"Min by OrderBy   : {array.OrderBy(p => p).First()}");
Console.WriteLine($"Min by Aggregate : {array.Aggregate((p, q) => Math.Min(p, q))}");
Console.WriteLine($"Min by Aggregate : {array.Aggregate((p, q) => q < p ? q : p)}");
Console.WriteLine($"Min by Min       : {array.Min()}");
Min by Sort      : NaN
Min by OrderBy   : NaN
Min by Aggregate : NaN
Min by Aggregate : -∞
Min by Min       : NaN

double.NaNはいろいろ特殊ですね。 double.NaNの特異性についてもまた書きたいです。

試してないですが、floatも一緒だと思います。

ではまた。

*1:Enamerable.csのMinのソースコードあたりに書いてます。

*2:double.NaN != double.NaNはtrueを返しますが

Enumelable.GroupBy()のオーバーロード

概要

自分でプログラム書いていた時にふと、「これはGroupByでうまく実装できないのか」という疑問が発生して、いろいろ調べた後、
Twitterで「GroupByについてまとめようかなぁ」とつぶやいたところ、「はよ」と脅されたために作成しました。

GroupByのオーバーロードによる違いを軽く解説します。

前提条件/知識

この記事は、脅された自分の忘備録のためであり、詳しい解説はしていません。
使用環境はC#7.1、 . NET Framework 4.7 を基準に書いているので、それ以前のバージョンだと動かない部分があります。
前提知識として、ジェネリックLinq・参照渡し/値渡し・遅延評価などは知っているものとしています。
GroupByの基本的説明はしていない。オーバーロードメソッドのそれぞれの違いを述べているだけです。GroupByの基本動作がわからない人はそれを勉強して読んでください。

GroupByの概要

GroupByメソッド

GroupByは全部で8種類で、型名を省略して書くとそれぞれこんな感じである。

GroupBy(source, keySelector);
GroupBy(source, keySelector, comparer);
GroupBy(source, keySelector, resultSelector);
GroupBy(source, keySelector, elementSelector, resultSelector);
GroupBy(source, keySelector, resultSelector, comparer);
GroupBy(source, keySelector, elementSelector, resultSelector, comparer);
GroupBy(source, keySelector, elementSelector, comparer);
GroupBy(source, keySelector, elementSelector);
引数の型

それぞれの引数の型は以下の通りである。

IEnumerable<TSource> source                              //グループ化するデータ
Func<TSource, TKey> keySelector                          //グループ化のキー
Func<TSource, TElement> elementSelector                  //グループ化の要素のセレクター
Func<TKey, IEnumerable<TSource>, TResult> resultSelector //グループ化の列挙のセレクター
IEqualityComparer<TKey> comparer                         //Keyの比較関数

source , keySelector は知っているものとして解説は省略する,
残りの3つが分かれば、必要に応じたGroupByの使い方ができることになる。

コード例

コード例及び実行例を示す前に、例に使うclass等を先に示しておきます(最初だけusing書いておきます)

使用コード

各学年と各クラスの代表生徒2人をデータとして持っている感じです。

using System;
using System.Collections.Generic;
using System.Linq;

partial class Program
{
    private static Character[] CreateDataBase()
    {
        return new[]
        {
            new Character(new ClassRoom(1, 1), "響"),
            new Character(new ClassRoom(1, 1), "暁"),
            new Character(new ClassRoom(1, 2), "電"),
            new Character(new ClassRoom(1, 2), "雷"),
            new Character(new ClassRoom(1, 3), "夕立"),
            new Character(new ClassRoom(1, 3), "時雨"),
            new Character(new ClassRoom(2, 1), "白露"),
            new Character(new ClassRoom(2, 1), "村雨"),
            new Character(new ClassRoom(2, 2), "島風"),
            new Character(new ClassRoom(2, 2), "大鳳"),
            new Character(new ClassRoom(2, 3), "五月雨"),
            new Character(new ClassRoom(2, 3), "涼風"),
            new Character(new ClassRoom(3, 1), "睦月"),
            new Character(new ClassRoom(3, 1), "如月"),
            new Character(new ClassRoom(3, 2), "弥生"),
            new Character(new ClassRoom(3, 2), "卯月"),
            new Character(new ClassRoom(3, 3), "皐月"),
            new Character(new ClassRoom(3, 3), "水無月"),
        };
    }


    static void ShowResult<T, U>(string header, IEnumerable<IGrouping<T, U>> source)
    {
        Console.WriteLine(header);
        foreach (var group in source)
        {
            Console.WriteLine($"└Key : {group.Key}");
            foreach (var item in group)
            {
                Console.WriteLine($"│└Value : {item}");
            }
        }
        Console.WriteLine();
    }

    static void ShowResult<T>(string header, IEnumerable<IEnumerable<T>> source)
    {
        Console.WriteLine(header);
        foreach (var group in source)
        {
            Console.WriteLine($"└IEnumerable");
            foreach (var item in group)
            {
                Console.WriteLine($"│└Value : {item}");
            }
        }
        Console.WriteLine();
    }

    //教室のデータを格納するクラス
    class ClassRoom
    {
        public ClassRoom(int grade, int classNumber)
        {
            Grade = grade;
            Class = classNumber;
        }

        public int Grade { get; }
        public int Class { get; }

        public override string ToString()
        {
            return $"{Grade}-{Class}";
        }

        public static ClaasRoomComparer Comparer => ClaasRoomComparer.Instance;

        //教室の等しさを確認するためのクラス
        public class ClaasRoomComparer : IEqualityComparer<ClassRoom>
        {
            private static ClaasRoomComparer _instance;

            public static ClaasRoomComparer Instance
                => _instance = _instance ?? new ClaasRoomComparer();


            public bool Equals(ClassRoom x, ClassRoom y)
            {
                return x.Grade == y.Grade && x.Class == y.Class;
            }

            public int GetHashCode(ClassRoom obj)
            {
                return obj.Grade.GetHashCode() ^ obj.Class.GetHashCode();
            }
        }
    }

    //キャラクタのデータを格納するクラス
    class Character
    {
        public Character(ClassRoom room, string name)
        {
            Name = name;
            Room = room;
        }

        public string Name { get; }
        public ClassRoom Room { get; }

        public int Grade => Room.Grade;
        public int Class => Room.Class;

        public override string ToString()
        {
            return $"{Room} : {Name}";
        }

    }
}

※ClassRoomが構造体ではなくクラスなのは意図的です。

comparer

概要
IEqualityComparer<TKey> comparer

これはKeyの比較を行う関数を示します

ソースコード
static void Main(string[] args)
{
    var baseData = CreateDataBase();

    var group0 = baseData.GroupBy(p => p.Room);
    var group1 = baseData.GroupBy(p => p.Room, ClassRoom.Comparer);
    
    ShowResult("group0", group0);
    ShowResult("group1", group1);
}
実行結果

実行結果は以下の通りとなります。(ただし途中以降は省略しています。)

group0
└Key : 1-1
│└Value : 1-1 : 響
└Key : 1-1
│└Value : 1-1 : 暁
└Key : 1-2
│└Value : 1-2 : 電
└Key : 1-2
│└Value : 1-2 : 雷
└Key : 1-3
│└Value : 1-3 : 夕立
└Key : 1-3
│└Value : 1-3 : 時雨
//以下省略

group1
└Key : 1-1
│└Value : 1-1 : 響
│└Value : 1-1 : 暁
└Key : 1-2
│└Value : 1-2 : 電
│└Value : 1-2 : 雷
└Key : 1-3
│└Value : 1-3 : 夕立
│└Value : 1-3 : 時雨
//以下省略
軽い説明

CrassRoomをクラスにしているせいで、普通にGroupByを行うとすべて独立したグループになりますが、きちんと比較子を付けることでそれを回避できます。
この場合では、構造体にすればいいが大きいデータクラスだと有効なのかなと思います。
しかし、別の回避方法がある気がもしますので、あまり使わないと思います。

elementSelector と resultSelector

概要

次に

Func<TSource, TElement> elementSelector 
Func<TKey, IEnumerable<TSource>, TResult> resultSelector 

について。
これらは、Linqでも特に有名なSelect句に近い動きをしてくれます。
elementSelectorはグループ化される各要素に対して、射影を行います。ToDictionaryのelementSelectorと一緒と説明したほうが早いかもしれません。
resultSelector は各グループの要素群に対して何かしらの処理を行ったものをグループの要素として持つものです。このままだとelementSelectorとの違いが分かりにくいが、処理対象が要素ではなく、列挙(≒配列)に対して行えるので選択の幅が広くなります。欠点としては、返り値がIEnumerable<IGruping>ではなくなるため、Kyeの参照ができなくなることです。

ソースコード
static void Main(string[] args)
{
    var baseData = CreateDataBase();

    var group0 = baseData.GroupBy(p => p.Grade, p => p.Name);
    var group1 = 
        baseData.GroupBy(p => p.Grade,  (p, q) => q.OrderByDescending(r => r.Class));

    ShowResult("group0", group0);
    ShowResult("group1", group1);
}
実行結果

実行結果は以下の通り

group0
└Key : 1
│└Value : 響
│└Value : 暁
│└Value : 電
│└Value : 雷
│└Value : 夕立
│└Value : 時雨
└Key : 2
│└Value : 白露
│└Value : 村雨
│└Value : 島風
//以下省略

group1
└IEnumerable
│└Value : 1-3 : 夕立
│└Value : 1-3 : 時雨
│└Value : 1-2 : 電
│└Value : 1-2 : 雷
│└Value : 1-1 : 響
│└Value : 1-1 : 暁
└IEnumerable
│└Value : 2-3 : 五月雨
│└Value : 2-3 : 涼風
│└Value : 2-2 : 島風
│└Value : 2-2 : 大鳳
//以下省略
軽い説明

一応、下記二つは同じ実行結果となります。

var group0 = baseData.GroupBy(p => p.Grade).Select(p=>p.OrderByDescending(r => r.Class));
var group1 = baseData.GroupBy(p => p.Grade, (p, q) => q.OrderByDescending(r => r.Class));

しかし、resultSelectorでは指定した場合Keyの情報も取れるので、無名タプルとかにしておけば、後からDictionaryにすることも可能となります。

ちなみに、GroupByの返り値は

IEnumerable<IGrouping<TKey, TSource>> 
IEnumerable<TResult>

の二つがありますが、resultSelectorを引数に取るメソッドの場合は下になります。

ところで

GroupByの返り値に対して、GetEnumerator()を呼び出すと、Lookupのインスタンスが生成されますが、それならToLookupすればいいんじゃないかなと思ってしまいます。
一応、ToLookupはメソッドを実行した時にインスタンスを生成し、GroupByはGetEnumeratorを実行するまではインスタンス化されないという違いがありますが、果たしてその違いが重要となる場合はあるのでしょうか。

素数を計算する

何を思ったかよくわからない言語に手を出してしまった。

とりあえず練習がてら素数を求めるプログラムを書いてみる。

出来上がったのがこち

f:id:unidentifiedexe:20170610202705j:plain

う~ん気持ち悪い。

言語はBefunge。
この言語の特徴は実行方向が4方向に変わること。^v<>で単純な方向転換。
|が縦方向条件分岐、_が横方向条件分岐。最初は左上から始まって実行方向は右。
で、普通のプログラムは最終行まで来たら終わるように作られるものだけど、この言語端まで行くと反対側に出てくるという謎仕様…どうやってプログラムを終えるかというと@がプログラムを終了する命令。この仕様何が問題って、最初に空白行を入れてしまうとその時点で無限ループ。何も書かなくても(おそらく)無限ループ。
ざっくりした流れを示すとこんな感じ

f:id:unidentifiedexe:20170610204008j:plain

う~ん分かりにくい。#は次の命令を飛ばすから#v_vを右に読むと条件に応じて右のvで下に行くか左のvで下に行く。多分コレを利用すればこのプログラムも2行でかけると思う。

プログラムの構造的には

for(int num = 3; num<5*5*4; num++){

    for(int j = 2;j*j<=num){

        if(num%2 == 0) goto cont;

    }

    printf("%d ",num);

    cont:

}

といった感じ。goto文使うなって追われるかもしれないけど、プログラム内容的にgoto文の方が正しいかなって。

だいぶ文字数少なくしたけど、縦方向の移動が少ないから、まだまだ改善できるのかなって思うけど、それをはじめると大変そう。

 

if文の{}の存在

プログラミング触り始めの頃、if文の{}は欠かさず書いてたけど、if内の命令文が1つなら{}を省略できるわけで、

if (i == 0) a = 10;
else if (i == 1) a = 4;
else a = 5;

と書ける。

ここでちょっとした疑問。
elseとifの間にスペースを入れるわけだけど、else文とif文別々としても見れるんじゃないかなと。
即ち、
 
if (i == 0) a = 10;
else

{

  if (i == 1) a = 4;
  else a = 5;

}

こういうコードに。
ここで{}をのけたらどうなるんだろうと。


if (i == 0) a = 10;
else

if (i == 1) a = 4;
else a = 5;

コレを書いて動かしてみる。elseの後に2文あるわけだから、エラー吐くかなと思ったら思いのほかしっかり動いてくれた。

コンパイラ的に改行があるだけで、きちんとelse ifとして読み取ってるのか、else/ifと分けても問題ないのか。まぁ、おそらく前者かなぁと。

実際にプログラミングするときは最初に書いた書き方でやるだろうけど。