検索:イルカいるか?
二分木に限らず、任意の要素を検索する操作が、いつか必要になります。
class Tnone(BinTree):
def _detect(self, key): return None
class Tnode(BinTree):
def _detect(self, key):
if key == self.word: return self
node = self.left ._detect(key)
if node: return node
node = self.right._detect(key)
if node: return node
補助関数_detectは、指定したキー(単語)keyを含む部分木(ノード)をリターン値とします。クラスTnoneでは、左右の部分木を持たないので、単にNoneをリターン値とします。クラスTnodeでは、二分木を構成する各ノードの中から、再帰呼び出し_detect()によって得られたノードnodeが存在するなら、そのnodeをリターン値とします。このとき、ifに続く条件式nodeの真偽を判定するのは、先の__nonzero__の責務です。これが、再帰呼び出しの終了条件にもなります。
包含:イルカいないか?
組み込み演算子inを利用すると、任意のキーが辞書に含まれるかを判定できます。
>>> m
{'A': 65, 'C': 67, 'B': 66}
>>> "A" in m
True
>>> "@" in m
False
2項演算子inの左項にキーを、右項に辞書を指定すると、そのキーが辞書に含まれるかどうかを判定した結果True/Falseが得られます。ここでは、キー"A"は辞書mに含まれるので、Trueが得られます。また、キー"@"は辞書mに含まれないので、Falseが得られます。
メソッド __contains__ は何処に
メソッド__contains__は、演算子inの操作を規定します。
class Tnode(BinTree):
def __contains__(self, key):
return (True, False)[not self._detect(key)]
ここでは、先の補助関数_detectを再利用しています。指定したキー(単語)keyを含む部分木(ノード)が存在するならTrueを、存在しないならFalseを、それぞれリターン値とします。
真偽値の「存在」意義とは?
>>> issubclass(bool, int) True >>> isinstance(True, bool), isinstance(False, bool) (True, True) >>> True == 1, False == 0 (True, True)
組み込み型boolをクラスとして見なすと、intの子孫クラスにあたるので、組み込み関数issubclassによってTrueと評価されます。そのため、クラスboolのインスタンスであるTrue/Falseは、同値演算子 == を使って、任意の整数と同値比較できます。すると、True/Falseは、それぞれ 1/0 と同値なのが分かります。
存在しないものの「存在」意義とは?
>>> issubclass(type(None), object) True >>> (not None) == 0, (not None) == 1 (False, True) >>> not None True
Noneは「存在しないこと」を表現する実体(唯一の存在 sole instance)です。「存在しない」ことの否定表現は「存在する」です。そのため、式not Noneは、Trueと評価されます。
3項演算子に代わるもの?
先の考察から、否定演算子notを利用すると、C言語の3項演算子「?:」と同様に、式を簡潔に表現できます。
>>> ("no","yes")[not None]
'yes'
式not Noneは、Trueと評価されるので、整数 1 と同値です。そのため、タプルの(先頭からのオフセット 1 に存在する)第2要素'yes'が得られます。つまり、False/0でないなら、True/1と評価されるので、C言語の3項演算子と等価です。
>>> ("not empty","empty")[not {}]
'empty'
>>> ("not empty","empty")[not {"a":1}]
'not empty'
同様に、式not {}は、Trueと評価される(辞書は空である)ので、整数 1 と同値です。そのため、タプルの第2要素'empty'が得られます。つまり「否定」の否定は「肯定」となります。また、式not {"a":1}は、Falseと評価される(辞書は空でない)ので、整数 0 と同値です。そのため、タプルの第1要素'not empty'が得られます。つまり「肯定」の否定は「否定」となります。
包含:いないかイルカ?
では実際に、これらのメソッドの動作を確認してみましょう。
>>> s = "good as happy as happy can be"
>>> p = None
>>> for e in s.split(" "): p = addtree(p, e)
>>> p.tree()
+--
+-- 'as'(2)
+--
+-- 'be'(1)
+--
+-- 'can'(1)
+--
+-- 'good'(1)
+--
+-- 'happy'(2)
+--
まず、後の動作を確認するために、二分木pを生成しておきます。
>>> p good(1,as(2,,can(1,be(1,,),)),happy(2,,)) >>> "happy" in p True >>> "god" in p False
2項演算子inの左項にキーを、右項に二分木を指定すると、そのキーが二分木に含まれるかどうかを判定した結果True/Falseが得られます。ここでは、単語"happy"は二分木pに含まれるので、Trueが得られます。また、単語"god"は二分木pに含まれないので、Falseが得られます。
例外:TypeError は語る
ここで再び、メソッド__contains__を実現する前まで、話を戻します。
>>> p good(1,as(2,,can(1,be(1,,),)),happy(2,,)) >>> "happy" in p Traceback (innermost last): ... TypeError: iteration over non-sequence
実行時に例外オブジェクトTypeErrorを生成すると共に、エラーメッセージ「iteration over non-sequence」を出力します。これは、シーケンス(文字列、リストなど)でない対象に対して、イテレーション操作を適用しようとしたからです。
このことから、__contains__を実現したので、イテレーション操作の対象となり、演算子inなどを適用できるようになったことが分かります。Pythonに限らず、与えられたフレームワークを効果的に活用するためには、ハリウッドの原則に従って、プログラマーが「自分からは呼び出さない」メソッド群を用意しておく必要があります。この話題については、今後の連載で紹介しますので、話を先に進めます。
包含:いるいるイルカ?
メソッドhas_keyを利用すると、任意のキーが辞書に含まれるかを判定できます。
>>> m
{'A': 65, 'C': 67, 'B': 66}
>>> m.has_key("A")
True
>>> m.has_key("@")
False
引数に指定したキーが、辞書に含まれるかどうかを判定した結果True/Falseが得られます。ここでは、キー"A"は辞書mに含まれるので、Trueが得られます。また、キー"@"は辞書mに含まれないので、Falseが得られます。
class Tnode(BinTree):
def has_key(self, key):
return key in self
メソッドhas_keyでは、演算子inを再利用しています。つまり、演算子inを介して、メソッド__contains__に記述した処理を実行します。すると、演算子inと同じ結果が得られるようになります。
では実際に、これらのメソッドの動作を確認してみましょう。
>>> p
good(1,as(2,,can(1,be(1,,),)),happy(2,,))
>>> p.has_key("happy")
True
>>> p.has_key("god")
False
has_keyの引数に指定した単語が、二分木に含まれるかどうかを判定した結果True/Falseが得られます。ここでは、単語"happy"は二分木pに含まれるので、Trueが得られます。また、単語"god"は二分木pに含まれないので、Falseが得られます。
