Home日記コラム書評HintsLinks自己紹介
 

フィンローダのあっぱれご意見番 第131回「玄人の目」

← 前のをみる | 「フィンローダのあっぱれご意見番」一覧 | 次のをみる →

最近はコミックの影響で囲碁がブームだというが、 いや、もうブームは下降線なんでしょうか、もしかして。 インターネットの対戦ゲームといえば、 Final Fantasy 10 はあれだけ宣伝したのだから言うまでもないとして、 Ultima Online だとか、 Ragnarok Online とか、 そういうのが有名らしい。 考えてみると、 RPGとか大好きな私が、 ふと気付くとこの手のオンラインゲームをやったことがないというのは不思議だが、 現実時間がないというのが最大の壁なのだと思う。 常時接続が家庭に普及したら、 意外と囲碁や将棋のようなゲームも大ヒットするんじゃないか、 と思う割にあまりパッとした話は聞いたことがない。 麻雀は割とよくやっているのだが、 これもいまいちパッとしないのだが、 それは単にネット雀荘の選択の問題なのだろうか。

 

※ 「ヒカルの碁」というコミック、アニメが流行した。 タイトルの「目」という言葉は囲碁の「目」という意味もある。

囲碁の技…という程でもないが…に、カケツギというものがある。 大昔にどこかで読んだのだと思うが、 少し碁を打てるようになった人が、カケツギを好むようだが、 プロはむしろツギを好む、という感じの逸話を読んだことがあった。 場面とか、完璧に忘れたのだが、 そのインパクトは強烈で、何となく頭から離れない話なのである。

今でっち上げた場面なので実際にこんな場面があるかどうか知らないし、 囲碁の世界から長年離れているから、 例として適切かどうか自信もない。 というか、むしろ不適切だという自信があったりするが、 適切かどうかというのは今回は度外視して、 話のあらすじだけ紹介するために想像してみる。 fig.1 で、白が左上の○を打った所だとする。

---- fig. 1 (白が攻めてきた図) ----

 +++++++
 +○+++++
 ++×●+●+
 +●●○+++
 ++○○+++
 +++++++

---- fig. 1 end ----

うーむ、こんな感じじゃなかったというか、 もっと序盤の布石に近い話だったと思うのだが、 あまり広い盤面を書くのが面倒なので、この程度で勘弁してください。 ここで白石は何を狙っているのかというと、 次に×の所に攻めてくるというのだ。 囲碁のルールでは、 縦横に同じ色の石が連続していたら同じグループとみなすことになっている。 ×の所に白石が置かれてしまったら、 左右の黒石が縦横に繋がらない状態になってしまう。 これは囲碁的には左右に分断されたことを意味する。 一般論としても、 大集団の方が分断されたグループよりも有利というのは雰囲気的に分かると思うが、 とにかく分断されるとよろしくない。

この白のアクションに対して、 fig.2 のような受け方をするのがカケツギである。

---- fig. 2 (カケツギ) ----

 +++++++
 +○●++++
 ++×●+●+
 +●●○+++
 ++○○+++
 +++++++

---- fig. 2 end ----

囲碁は全く分からないという方には恐縮だが、 このように受けると、次に白石は×の場所に入ることができないのだ。 いや、別に入っても構わないのですが、 黒はその石をすぐに撃破できるので、殆ど意味がない。 で、例えばこういう状況で、アマの上級者はカケツギをするのではないか、 という話だったと思う。 なぜカケツギかというと、その方がいろいろと役に立ちそうな気がするわけで、 実際そうかもしれないのだが。

では、プロはどうするかというと、単にツギだというのである。 つまり、fig.3 のように受けるのだ。

---- fig. 3 (ツギ) ----

 +++++++
 +○+++++
 ++●●+●+
 +●●○+++
 ++○○+++
 +++++++

---- fig. 3 ----

こうすれば、 既に黒石がその場所にあるから、白石が×の場所に置けないのは明白である。 黒石は縦横に繋がった状態で、まさに磐石である。

.※ この棋譜は、ツギとカケツギの見た感じの違いを説明するためだけに、 今でっちあげたものである。 実際にこの場面でツギの方がいいかどうかは定かではない。

§

いきなりですが、箱にケーキを詰めるという処理を考えてみましょう。 一つの箱に入るケーキはn個、ケーキはm個あるとする。 いくつ箱が必要か、という問題だ。 簡単ですね。 小学生でも解けるかな。

まず、最も安直に考えてみる。 1箱にn個ずつ入るのだから、m/n個なのでは。 発想はあまり間違ってないが、これは安直すぎる。 明らかにおかしな状況はすぐに思いつく。 6個入る箱を考えてみましょう。 ケーキが1個の場合、m/n=1/6 か? 分数というのもヘンだが、小数になってもおかしい。 箱の数は整数という縛りがあると考えるのが自然だ。 ということで、当たり前のようだが、 以後は整数演算であると仮定する。 では、切り捨てて m/n = 0 ですか。 箱が0個だと一つも入らないのだが。

そこで少し考える。 箱の数を1足せばいいのではないか。 つまり、(m/n) + 1 だ。 これもだいたいうまいのだが、ちょっとおかしい。 6個入る箱にケーキを6個入れるとどうなるのか。 (6/6) + 1 = 2 だが、箱は一つで足りるからだ。 だいたい、(m/n) + 1 だと、 0個のケーキを入れるのに1個の箱が必要になってしまう。 0個だと箱も0箱になって欲しい。

ということで、より正解に近い考え方は、次のようになる。

((m-1)/n) + 1

ここで、 0個を指定したときに、 (-1/n) + 1 の計算結果が一体どうなるのかというのが気になるかもしれない。 個数を考える途中でマイナスの数値が出てくるのはイマイチなのでは、 ということで、 1 = n/n を使って、

((m-1)/n) + 1 = ((m-1)/n) + (n/n) = (m - 1 + n) / n

と変形して、このあたりで手を打ちませんか。

n が 0だとどうなるんだ、という人もいると思うが、 ケーキが0個入る箱というのが謎だし、 今回はそこまで面倒を見なくてもいいような気がする。 もちろん、関数にするのなら最初にチェックすればいいだけの話だ。 実際、プログラミングのコラムなんだから関数にしてみたのが、 List 1である。

---- List 1 ----

int getBoxCount(int nCake, int nBoxSize)
{
    if (nCake < 0 || nBoxSize < 1)
      return -1; /* 不正 */

    return (nCake - 1 + nBoxSize) / nBoxSize;
}

---- List 1 end ----

事前にチェックしているし、問題ないでしょう。 ところで、これって何の言語? 自分でも曖昧だが、とりあえず C言語かな。 C++ でも Java でもいいのだが、 もっと仮想的な何かこれ系の言語という解釈の方がありがたいかもしれない。

  

さて、これで何の問題もないように見えるし、実際ないわけだ。 と思った人は、もしこの問題を実際に与えられたら、 そんな考え方をするだろうか、と自問して欲しい。 思考の流れを先に見せられてしまうと、つい「ああそうか」と思ってしまったりするが、 実際はもっと簡単な考え方もあるかもしれないのだ。 最初まで戻って考えてみよう。 箱の個数がm/n個という発想は「あまり間違ってない」と書いた。 どこが間違ってないかというと、 ケーキが全部の箱にきっちり入る数だけあればこれで正しい結果が出る というあたりだ。 実際は、 常にケーキの数が箱の数の倍数になるとは限らないから、 そこがややこしくなっているのである。

 

※ 不正な引数で呼ばれたときに -1 を返すのはなぜか、 説明が必要だと思う。

ということは、ケーキがちょうど入らないような場合、 つまり、余りのケーキが出てしまう場合には、1箱余計に必要になるのだ、 と考えれば、どうなるか。 算数の問題だとすれば、1つの式で書くのは難しい。 場合分けという考え方で対応すればいい。 幸い、場合分けという発想は、プログラミングに対応させて実現することが簡単である。

ケーキの数が箱に入る数で割り切れる場合、m/n 個。
割り切れない場合、(m/n) + 1 個

と考えるとよい。 これをプログラム化すると、List 2 のようになる。

---- List 2 ----

int getBoxCount(int nCake, int nBoxSize)
{
    if (nCake < 0 || nBoxSize < 1)
      return -1; /* 不正 */

    if (nCake % nBoxSize == 0)
        return nCake / nBoxSize;
    else
        return (nCake / nBoxSize) + 1;
}

---- List 2 end ----

ここに至って、やっと核心の問題を出せるのである。 List 1と、List 2は、どちらがいいプログラムなのか。 あるいは、あなたならどちらを選びますか、というのが問題だ。 究極の回答は多分「どっちでもいい」だと思うのだが、 まあどちらか選んでくださいといわれたら、 何を基準に選択すべきか。 効率ですか? 効率だったら List 1 か。 どの程度違うだろうか。 保守性というのはどうだろうか。 ケーキの箱詰め関数に保守性って何かあるのか? 箱の種類が増えたりしたら凄いことになるが、あまり保守したくないような気もする。

§

  

そこで囲碁の話がまた出てくるのだが、 これまた異議大有りなのは承知で言い切ってしまうと、 最近になって、 List 1 がカケツギ、List 2 がツギに対応しているような気がするのである。 つまり、これもヤバい主張であることを承知で言い切ってしまうと、 ホンマモンのプロなら List 2を好むのではないか、 というような気がするのだ。 ホンマモンって何? という疑問もあるのだが、 やはり最終的に一番大きいのは可読性じゃないかと思う訳である。

 

※ 流れの美しさとか、 堅さだとか、そのあたりが評価の基準になるだろう。 で、プログラムの堅さって何、とかいわれたる結構困るわけだが。

(nCake - 1 + nBoxSize) / nBoxSize;

この式を見て、皆さんは一瞬怯まないだろうか、 これは本当に期待しているロジックを実現しているのか、のように。 List 2 は、これに比べるとダサいやり方で、 効率もよくないかもしれないが、 プログラムに出てくる世界は まずできるだけ箱詰めしてから余ったのはもう一つの箱、 という現実世界の模写であり、 その単純明快さが何ともいえないのである。

ただ、ここで一番問題なのは、玄人の目とは何なのかということだ。 囲碁の話に限らず、 その良し悪しが一瞬で判別できるというのが重要だと思うわけで、 そのあたりも、なかなか味わい深いものがあると思う。 ちなみに、 この話は、実は私の最近の経験がネタになっている。 最初は List 1 のように書いてしまって、 リファクタリングでやっと List 2 になったのだ。 つまり、まだ修行が足りないのだろう、多分。

ここまで来たら、List 3 はどうか、 という話も、 言いたいことは分かってもらえると思う。

  
---- List 3 ----

int getBoxCount(int nCake, int nBoxSize)
{
    if (nCake < 0 || nBoxSize < 1)
      return -1; /* 不正 */

    int nBoxCount = nCake / nBoxSize;
    if (nCake % nBoxSize != 0)
        nBoxCount++;

    return nBoxCount;
}

---- List 3 end ----
 

※ nBoxCount という一時変数を使うのは吉か凶か、という話になる。

(C MAGAZINE 2003年5月号掲載)
内容は雑誌に掲載されたものと異なることがあります。

修正情報:
2006-03-02 裏ページに転載。

(C) Phinloda 2003-2006, All rights reserved.