SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

よろずプログラマーのためのPython導入ガイド

Java meets Python - 第2回 配列と別れる50の方法

よろずプログラマーのためのPython導入ガイド (4)

伝統的な手法による問題解決:2次元配列

 伝統的な手法による問題解決では、2次元配列を使うのが定石です。

 ボードに配置したコマを、2次元配列で表現します。配列要素には、コマを識別する整数値を代入します。空いたマス目は-1で識別します。移動できるコマは、空いたマス目と隣接(上下左右)するものだけです。コマを移動するときには、移動元の整数値を移動先の配列要素に代入すると共に、移動元には値-1を代入します。

 Javaで記述したコードの断片は、次のようになるでしょう。

void move(int n) {
    if (x>0 && board[y][x-1] == n) {
        board[y][x-1] == -1; board[y][x] == n;
    }
    if (x<3 && board[y][x+1] == n) {
        board[y][x+1] == -1; board[y][x] == n;
    }
    if (y>0 && board[y-1][x] == n) {
        board[y-1][x] == -1; board[y][x] == n;
    }
    if (y<3 && board[y+1][x] == n) {
        board[y+1][x] == -1; board[y][x] == n;
    }
}

 ここで着目して欲しいのは、添字が範囲内にあるかを判定する条件式です。ボードの左端では、条件式x>0を満たすかどうかで判定します。同様に、右端ではx<3で判定します。上端/下端も同様です。その範囲外で値を代入しようとすると、実行時に例外を生成します。そのため、伝統的な手法では、この条件式は必要悪とされてきました。

諸悪の根源は何処に

 このプログラムは、意図したように動作します。動かないプログラムより、動くプログラムが望ましいのは確かです。しかし、問題がないわけではありません。例えば、「マス目の数を増やしたい」という仕様変更があったとします。すると、すべての条件式を見直す必要があります。条件式x>0およびx<3などに伴う「マジックナンバー」と呼ばれる定数を排除していたら、仕様変更にも柔軟に対処できたかもしれません。しかし、例外が発生するのを防ぐためだけに条件式が必要になるという不条理には、納得がいきません。なぜなら、開発者の都合に、利用者が左右されたくないからです。

 この不条理を避けて、見通しの良いコードを記述するにはどうすればよいでしょう。諸悪の根源は何でしょうか。伝統的な手法では、ゲーム盤を表現するのに2次元配列を利用しました。「配列」さえ利用しなければ、この問題を回避できるかもしれません。もしかすると、自分が招いた問題を、自分で解決する羽目に陥っていたのでしょうか。これは、確かめてみるだけの価値がありそうです。

愛が止まらない:隘路を切り開く

 「その一口がブタになる」と分かっていても、甘いものにはつい手が伸びてしまう。頭では分かっていても、身体が言うことを聞きません。配列の「甘い」誘惑に負けそうになったら、この話を思い出してください。

 まず、コマを単なる値ではなく、オブジェクトとして表現することから始めます。

class Tile(Shape):
    def __init__(self, x, y, value):
        self.x = x
        self.y = y
        self.value = value
    def __str__(self):
        return "%r(%d,%d)"%(`self.value`, self.x, self.y)

 番号'5'が振られたコマは、x座標0、y座標2、整数値5を持つインスタンスと見なせます。ここで、コマの左上の隅を、その座標値とします。インスタンスに固有の情報を文字列として出力すると、次のようになります。

'5'(0,2)

 この結果は、メソッド__str__で規定した文字列表現から得られます。

 配列に値を保持させるのではなく、その対象を「オブジェクト」として実現します。番号を振られたコマは、単なる整数値ではありません。自分がどこに位置すべきかは、コマ自身で判断します。つまり、オブジェクト自身が「思考」するというわけです。

デザインパターン:Null Object

 GoFのカタログにはない、古典的なパターンの一つがNull Objectです。

class Tile(Shape):
    def backgroundColor(self):
        return Color.white
    def drawValue(self, g, x, y):
        g.drawString(`self.value`, x+10, y+20)

 コマには、2つのメソッドを定義します。メソッドbackgroundColorは、コマの背景色(白色 Color.white)を規定します。メソッドdrawValueは、コマを識別するために、整数値self.valueを座標x,yに表示します。

 Null Objectは、空のマス目を表現するのに好都合で、これを特殊なコマと見なします。これは、すべてのオブジェクトを平等に扱いつつ、その個性を尊重するという管理手法に適います。Null Objectに関する広範な論議は、Smalltalkが参考になります。

インスタンスに固有のメソッド定義

N = PuzzlePanel.dim - 1;
Null = Tile(N, N, 0)
Null.backgroundColor = lambda: Color.lightGray
Null.drawValue       = lambda g, x, y: None

 空のマス目Nullには、2つのメソッドを再定義します。メソッドbackgroundColorは、コマの背景色(灰色 Color.LightGray)を規定します。メソッドdrawValueは、何も表示する必要がないので、その本体は空Noneとなっています。すると、Nullは、他のコマと違う個性を発揮します。

class Tile(Shape):
    def paintBackground(self, g):
        width = self.width(g)
        height = self.height(g)
        x = self.x*width;
        y = self.y*height;
        g.color = self.backgroundColor()
        g.fillRect(x, y, width, height)
    def paintItem(self, g):
        width = self.width(g)
        height = self.height(g)
        x = self.x*width;
        y = self.y*height;
        g.color = Color.black
        g.drawRect(x, y, width, height)
        self.drawValue(g, x, y);

 メソッドpaintBackgroundは、コマの背景を描きます。背景色を得るために、backgroundColorを再利用します。コマ(Tile インスタンス)なら白色が、空いたマス目(Null)なら灰色が得られます。

 メソッドpaintItemは、コマの番号を描きます。番号を描くために、drawValueを再利用します。コマ(Tile インスタンス)なら数字を表示しますが、空いたマス目(Null)なら何も表示しません。

 Nullを導入すると、Noneかどうかを判定する条件式が不要になるので、コードの見通しがよくなります。

次のページ
岬めぐり:コードを巡る旅の縁(よすが)に

この記事は参考になりましたか?

よろずプログラマーのためのPython導入ガイド連載記事一覧

もっと読む

この記事の著者

小泉ひよ子とタマゴ倶楽部(コイズミヒヨコトタマゴクラブ)

http://tamago-club.cocolog-nifty.com/「楽しくなければ仕事じゃない」が私たちのモットー。99%の苦悩の連続も、1%の成功に報われます。だからこそ、この仕事が楽しくて仕方がないのです。楽をするための努力なら惜しみません。何もせず楽をしているのと、努力をしたから楽ができるのと...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

伊藤うさぎ(イトウ ウサギ)

ペンネームの「由来は」と言うと。苗字の方は、セミナー研修で同じチームになった、3人の合体ユニット名 [I:石塚, T:田川, O:尾沢] から来ています。名前の方は、同じ干支(卯:1987 年生)に因んだものです。既に2人は卒業して、残る1人がその名跡を継承しています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1860 2007/11/13 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー