Pythonで〇×ゲームのAIを一から作成する　その147　αβ 法での枝狩りが行われたノードの数などの視覚化

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/abe910c5067cb7143faa

## これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

| リンク | 説明 |
|:--|:--|
| [marubatsu.py](https://github.com/ysgeso/marubatsu/blob/master/147/marubatsu.py)  | Marubatsu、Marubatsu_GUI クラスの定義|
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/147/ai.py)  |  AI に関する関数 |
| [test.py](https://github.com/ysgeso/marubatsu/blob/master/147/test.py)  |  テストに関する関数 |
|  [util.py](https://github.com/ysgeso/marubatsu/blob/master/147/util.py) |  ユーティリティ関数の定義。現在は `gui_play` のみ定義されている |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/147/tree.py)  | ゲーム木に関する Node、Mbtree クラスの定義  |
| [gui.py](https://github.com/ysgeso/marubatsu/blob/master/147/gui.py)  |  GUI に関する処理を行う基底クラスとなる GUI クラスの定義  |

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

https://qiita.com/ysgeso/items/10d1d01192c014173e4b

# 枝狩りが行われたノードの数などの視覚化

__αβ 法の目的__ は __枝狩り__ を行うことで、処理を行う __ノードの数を減らし__ て __処理時間を短くする__ ことです。従って、__αβ 法__ の __性能を評価__ するためには、__枝狩りが行われたノードの数__ について知る必要があります。しかし、現状の Mbtree_Anim は αβ 法で評価値の計算を行う際の処理の視覚化を行いますが、その処理によってどのくらいの数の枝狩りが行われているかの情報は表示されません。

そこで、今回の記事では __Mbtree_Anim__ が __枝狩りを行ったノードの数などを表示__ するように改良することにします。どのように改良すればよいかについて少し考えてみて下さい。

## 視覚化の方針

αβ 法の性能を評価するためには、枝狩りが行われたノードの数の情報だけでは __不十分__ です。αβ 法の性能に限らず、__何らかのものの性能を評価するため__ には、__他のものと比較する必要がある__ からです。αβ 法はミニマックス法を改良したものなので、__比較の対象__ となるのは __ミニマックス法__ です。従って、__同じノードの評価値__ を αβ 法とミニマックス法で計算した際の、__下記の 2 つを比較__ することで αβ 法の評価を行うことができます。

* αβ 法で計算を行った __ノードの数__。以後は alpha beta の頭文字から __A と表記__ する
* ミニマックス法で計算を行った __ノードの数__。以後は mini max から __M と表記__ する

上記の 2 つの数の __比率__ である __A / M__ が、それぞれのアルゴリズムで同じノードの評価値を計算した際の __計算時間の比率にほぼ一致__ します。例えば A が 10、M が 100 の場合は、10 ÷ 100 = 0.1 なので αβ 法によってミニマックス法の約 10 %（10 分の 1）の時間で評価値を計算できることがわかります。

以後は __この比率__ のことを割合を表す ratio の頭文字を取って __R と表記__ することにします。

そこで、本記事では上記の A、M、R を表示するように Mbtree_Anim を改良することにします。そのためには、上記の A と M を計算する必要があるので、その計算方法について少し考えてみて下さい。

## A と M の計算方法

A と M を計算するために、同じノードに対して αβ 法と、ミニマックス法の両方で評価値を計算する必要があると思った人がいるかもしれませんが、実際には __αβ 法による評価値の計算を行うだけ__ で A と M の __両方を計算__ することができます。

その理由は、αβ 法では __枝狩りを行ったノードの数だけ__、ミニマックス法と比較して __計算するノードの数が減る__ ので、αβ 法で __枝狩り（pruning）を行ったノードの数__ を __P と表記__ した場合に __下記の式が成り立つ__ からです。

__M = A + P__

従って、αβ 法で計算を行う際に A と P の数を数えることで M を計算することができます。

## 表示の仕様の具体化

__Mbtree_Anim__ では、αβ 法で評価値を計算する手順をアニメーションのフレームで __順番に表示__ しているので、本記事では Mbtree_Anim で表示している __アニメーションのフレームまでに行われた処理__ での __A、M、R を表示__ することにします。また、M を計算する際に P を計算する必要があるので、__P も表示する__ ことにします。

そのようにすることで、αβ 法の処理の __途中経過__ での __A、M、P、R の値を観察__ することができるようになります。また、__全ての処理が完了__ したアニメーションの __最後のフレーム__ での A、M、P、R を見ることで、それらの __最終的な値__ を知ることができます。

具体的には、以下の 2 つの図のように A、P、M、R の順でそれぞれの値を上の行から順に表示します。なお、A、P、M、R ではわかりづらいので、それぞれの __見出し__ を「計算済」、「枝狩り」、「合計」、「割合」と表示しました。

下図は計算を開始する前の最初のフレームの図なので、A、P、M はいずれも 0 で、R は 0.0 % と表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/256cecff-7073-4a00-b3f7-c3aafd354f23.png" width="450">

下図はルートノードの __評価値の計算が完了__ した最後のフレームの図で、A は 18297、P は 531649、M は 549946[^1]、R は 3.3 % と表示されるので、__αβ 法__ では __ルートノードの評価値__ の計算を __ミニマックス法の約 3.3 %__ というかなり短い時間で計算できることを表します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/eff6110c-d9fd-8449-c7fc-c4fb3efff84c.png" width="450">

本記事で利用する 4 つの記号について下記の表にまとめます。__4 つの記号__ はいずれも Mbtree_Anim で表示している __アニメーションのフレームまでに行われた処理__ を表します。

| 記号 | 意味 | 見出し | 
|:-:|:--|:--|
| __A__  | αβ 法で計算を行ったノードの数  | 計算済 |
| __P__  | αβ 法で枝狩りを行ったノードの数  | 枝狩り |
| __M__  | ミニマックス法で計算を行ったノードの数。M = A + P で計算できる  | 合計 |
| __R__  | M に対する A の比率。A / M で計算できる  | 割合 |

上記のような図の表示を行う方法について少し考えてみて下さい。

## Mbtree クラスの `calc_score_by_ab` メソッドの修正

__A と P__ は以下のような方法で計算することができます。

* `calc_score_by_ab` で αβ 法による評価値の計算を開始する前に、__A と P を記録する属性を 0 で初期化__ する
* `calc_score_by_ab` の処理の中で、__ノードの評価値が確定した時点__ で __A の値に 1 を足す__
* `calc_score_by_ab` の処理の中で、__ノードの枝狩りの処理を行う際__ に __P の値に 1 を足す__

A と P を記録する属性の名前を `A` や `P` にするのはわかりづらいので、それぞれ `num_calculated`[^2] と `num_pruned` という名前にします。

また、__A と P__ の値は __各フレームごとに記録__ する必要があるので、各フレームのデータを記録する __[`ablist_by_score`](https://qiita.com/ysgeso/items/fd421d90cd1f47e38cdb#mbtree-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE-calc_node_by_ab-%E3%81%AE%E4%BF%AE%E6%AD%A3)__ の要素の tuple を以下のように変更することにします。太字が変更の際に A と P を記録するために追加した部分です。

* 変更前：`(α 値, β 値, 子ノードの評価値, フレームの状態を表す文字列)`
* 変更後：<code>(α 値, β 値, 子ノードの評価値, フレームの状態を表す文字列, <strong>A の値, P の値</strong>)</code>

なお、`calc_score_by_ab` では、αβ 法で __計算を行ったノードの数__ を __`count` という属性で計算__ していますが、`count` は __ノードの計算が開始された時点で 1 を足す__ ので、__`num_calculated`__ とはよく似ていますが __少し異なる処理を行っています__。以下の理由から、__`count` の処理を削除__ して __`num_calculated` に統合する__ ことにします。

* ノードの評価値の計算が __すべて終了した時点__ では `count` と `num_calculated` には __同じ値が計算される__
* __`count`__ は `calc_score_by_ab` によるノードの評価値の計算が __すべて終了した後でのみ利用されている__ ので、その時点で同じ値が代入されている __`num_caluculated` で代替できる__

下記は、`num_calculated` と `num_pruned` を計算して `ablist_by_score` に記録するように `calc_score_by_ab` を修正したプログラムです。

* 4、14、38 行目の後にあった `score` 属性に関する処理を削除する
* __7 行目__：枝狩りが行われたノードの処理を行う際に、`num_pruned` 属性の値に 1 を加算する処理を追加する
* __13 ~ 22、25、26 行目__：`ablist_by_score` に追加する tuple の要素の末尾に `num_calculated` と `num_pruned` 属性を追加する
* __24 行目__：このノードの評価値が確定したので、`num_calculated` 属性に 1 を加算する処理を追加する
* __36、37 行目__：αβ 法の処理を開始する前に、`num_calculated` 属性と `num_pruned` 属性の値を 0 で初期化するようにする
* __39 ~ 44 行目__：`debug` が `True` の場合に最終的な A、P、M、R の値を `dprint` で表示するように修正する。なお、プログラムがわかりやすくなるように 39、40 行目で M と R の値を計算して変数に代入している。また、44 行目で M を表示する際の単位は % なので 100 で掛け算し、小数点以下 1 桁まで表示するように `:.1f` を記述している。この点について忘れた方は、[以前の記事](https://qiita.com/ysgeso/items/bf42b7b578bc2d5da949#ai_match-%E3%81%AE%E6%94%B9%E8%89%AF-%E3%81%9D%E3%81%AE-2%E6%AF%94%E7%8E%87%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E6%94%B9%E8%89%AF)を復習すること

```python
 1  from marubatsu import Marubatsu
 2  from tree import Mbtree
 3  
 4  def calc_score_by_ab(self, abroot, debug=False):           
 5      def assign_pruned_index(node, index):
 6          node.pruned_index = index
 7          self.num_pruned += 1
 8          for childnode in node.children:
 9              assign_pruned_index(childnode, index)
10          
11      def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
12          self.nodelist_by_score.append(node)
13          self.ablist_by_score.append((alpha, beta, None, "start",
14                                       self.num_calculated, self.num_pruned))
元と同じなので省略
15                      self.ablist_by_score.append((alpha, beta, score, "score",
16                                                   self.num_calculated, self.num_pruned))
元と同じなので省略
17                      self.ablist_by_score.append((alpha, beta, None, "update",
18                                                   self.num_calculated, self.num_pruned))
元と同じなので省略
19                      self.ablist_by_score.append((alpha, beta, score, "score",
20                                                   self.num_calculated, self.num_pruned))
元と同じなので省略
21                      self.ablist_by_score.append((alpha, beta, None, "update",
22                                                   self.num_calculated, self.num_pruned))
元と同じなので省略
23          self.nodelist_by_score.append(node)
24          self.num_calculated += 1     
25          self.ablist_by_score.append((alpha, beta, None, "end",
26                                       self.num_calculated, self.num_pruned))
27          node.score_index = len(self.nodelist_by_score) - 1     
28          return node.score
29  
30      from ai import dprint       
31      for node in self.nodelist:
32          node.score_index = float("inf")
33          node.pruned_index = float("inf")
34      self.nodelist_by_score = []
35      self.ablist_by_score = []
36      self.num_calculated = 0
37      self.num_pruned = 0
38      calc_ab_score(abroot)
39      total_nodenum = self.num_pruned + self.num_calculated
40      ratio = self.num_calculated / total_nodenum * 100
41      dprint(debug, "計算したノードの数",  self.num_calculated)
42      dprint(debug, "枝狩りしたノードの数",  self.num_pruned)
43      dprint(debug, "合計",  total_nodenum)
44      dprint(debug, f"割合 {ratio:.1f}%")
45      
46  Mbtree.calc_score_by_ab = calc_score_by_ab
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from marubatsu import Marubatsu
from tree import Mbtree

def calc_score_by_ab(self, abroot, debug=False):           
    def assign_pruned_index(node, index):
        node.pruned_index = index
        self.num_pruned += 1
        for childnode in node.children:
            assign_pruned_index(childnode, index)
        
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
        self.ablist_by_score.append((alpha, beta, None, "start",
                                     self.num_calculated, self.num_pruned))
        if node.mb.status != Marubatsu.PLAYING:
            self.calc_score_of_node(node)
            if node.mb.turn == Marubatsu.CIRCLE:
                alpha = node.score
            else:
                beta = node.score
        else:
            if node.mb.turn == Marubatsu.CIRCLE:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, score, "score",
                                                 self.num_calculated, self.num_pruned))
                    if score > alpha:
                        alpha = score
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, None, "update",
                                                 self.num_calculated, self.num_pruned))
                    if score >= beta:
                        index = node.children.index(childnode)
                        for prunednode in node.children[index + 1:]:
                            assign_pruned_index(prunednode, len(self.nodelist_by_score) - 1)
                        break
                node.score = alpha
            else:
                for childnode in node.children:
                    score = calc_ab_score(childnode, alpha, beta)
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, score, "score",
                                                 self.num_calculated, self.num_pruned))
                    if score < beta:
                        beta = score
                    self.nodelist_by_score.append(node)
                    self.ablist_by_score.append((alpha, beta, None, "update",
                                                 self.num_calculated, self.num_pruned))
                    if score <= alpha:
                        index = node.children.index(childnode)
                        for prunednode in node.children[index + 1:]:
                            assign_pruned_index(prunednode, len(self.nodelist_by_score) - 1)
                        break
                node.score = beta

        self.nodelist_by_score.append(node)
        self.num_calculated += 1     
        self.ablist_by_score.append((alpha, beta, None, "end",
                                     self.num_calculated, self.num_pruned))
        node.score_index = len(self.nodelist_by_score) - 1     
        return node.score

    from ai import dprint       
    for node in self.nodelist:
        node.score_index = float("inf")
        node.pruned_index = float("inf")
    self.nodelist_by_score = []
    self.ablist_by_score = []
    self.num_calculated = 0
    self.num_pruned = 0
    calc_ab_score(abroot)
    total_nodenum = self.num_pruned + self.num_calculated
    ratio = self.num_calculated / total_nodenum * 100
    dprint(debug, "計算したノードの数",  self.num_calculated)
    dprint(debug, "枝狩りしたノードの数",  self.num_pruned)
    dprint(debug, "合計",  total_nodenum)
    dprint(debug, f"割合 {ratio:.1f}%")
    
Mbtree.calc_score_by_ab = calc_score_by_ab
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from marubatsu import Marubatsu
from tree import Mbtree

def calc_score_by_ab(self, abroot, debug=False):    
-   self.count = 0
    def assign_pruned_index(node, index):
        node.pruned_index = index
        self.num_pruned += 1
        for childnode in node.children:
            assign_pruned_index(childnode, index)
        
    def calc_ab_score(node, alpha=float("-inf"), beta=float("inf")):
        self.nodelist_by_score.append(node)
-       self.ablist_by_score.append((alpha, beta, None, "start"))
+       self.ablist_by_score.append((alpha, beta, None, "start",
+                                    self.num_calculated, self.num_pruned))
-       self.count += 1
元と同じなので省略
-                   self.ablist_by_score.append((alpha, beta, score, "score"))
+                   self.ablist_by_score.append((alpha, beta, score, "score",
+                                                self.num_calculated, self.num_pruned))
元と同じなので省略
-                   self.ablist_by_score.append((alpha, beta, None, "update"))
+                   self.ablist_by_score.append((alpha, beta, None, "update",
+                                                self.num_calculated, self.num_pruned))
元と同じなので省略
-                   self.ablist_by_score.append((alpha, beta, score, "score"))
+                   self.ablist_by_score.append((alpha, beta, score, "score",
+                                                self.num_calculated, self.num_pruned))
元と同じなので省略
-                   self.ablist_by_score.append((alpha, beta, None, "update"))
+                   self.ablist_by_score.append((alpha, beta, None, "update",
+                                                self.num_calculated, self.num_pruned))
元と同じなので省略
        self.nodelist_by_score.append(node)
+       self.num_calculated += 1     
-       self.ablist_by_score.append((alpha, beta, None, "end"))
+       self.ablist_by_score.append((alpha, beta, None, "end",
+                                    self.num_calculated, self.num_pruned))
        node.score_index = len(self.nodelist_by_score) - 1     
        return node.score

    from ai import dprint       
    for node in self.nodelist:
        node.score_index = float("inf")
        node.pruned_index = float("inf")
    self.nodelist_by_score = []
    self.ablist_by_score = []
+   self.num_calculated = 0
+   self.num_pruned = 0
    calc_ab_score(abroot)
-   dprint(debug, "count =", self.count) 
+   total_nodenum = self.num_pruned + self.num_calculated
+   ratio = self.num_calculated / total_nodenum * 100
+   dprint(debug, "計算したノードの数",  self.num_calculated)
+   dprint(debug, "枝狩りしたノードの数",  self.num_pruned)
+   dprint(debug, "合計",  total_nodenum)
+   dprint(debug, f"割合 {ratio:.1f}%")
    
Mbtree.calc_score_by_ab = calc_score_by_ab
```
</details>

上記の修正後に下記のプログラムのように 実引数に `debug=True` を記述して `calc_score_by_ab` を呼び出すと、実行結果のように計算された A、P、M、R の値が表示されることが確認できます。

```python
mbtree = Mbtree(algo="df")
mbtree.calc_score_by_ab(mbtree.root, debug=True)
```

実行結果

```
計算したノードの数 18297
枝狩りしたノードの数 531649
合計 549946
割合 3.3%
```

## Mbtree_Anim クラスの `create_widgets` メソッドの修正

A、P、M、R の値を表示するためには、Mbtree_Anim の上部に表示する __Figure の幅を広げる__ 必要があるので、下記のプログラムのように __`create_widgets`__ メソッドを修正します。

* __8 行目__：Figure の大きさを `(5, 1)` から `(7, 1)` に修正する。なお、修正後の大きさは筆者が A、P、M、R がうまく表示されるように試行錯誤して決めた値である

```python
 1  from tree import Mbtree_Anim
 2  import matplotlib.pyplot as plt
 3  import ipywidgets as widgets
 4  
 5  def create_widgets(self):
元と同じなので省略
 6      with plt.ioff():
元と同じなので省略
 7          if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
 8              self.abfig = plt.figure(figsize=(7, 1))
元と同じなので省略
 9          
10  Mbtree_Anim.create_widgets = create_widgets
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from tree import Mbtree_Anim
import matplotlib.pyplot as plt
import ipywidgets as widgets

def create_widgets(self):
    self.play = widgets.Play(max=self.nodenum - 1, interval=500)
    self.prev_button = self.create_button("<", width=30)
    self.next_button = self.create_button(">", width=30)
    self.frame_slider = widgets.IntSlider(max=self.nodenum - 1, description="frame")
    self.interval_slider = widgets.IntSlider(value=500, min=1, max=2000, description="interval")
    widgets.jslink((self.play, "value"), (self.frame_slider, "value"))    
    widgets.jslink((self.play, "interval"), (self.interval_slider, "value"))

    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                        self.height * self.size])
        self.ax = self.fig.add_axes([0, 0, 1, 1])
        self.fig.canvas.toolbar_visible = False
        self.fig.canvas.header_visible = False
        self.fig.canvas.footer_visible = False
        self.fig.canvas.resizable = False 
        if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
            self.abfig = plt.figure(figsize=(7, 1))
            self.abax = self.abfig.add_axes([0, 0, 1, 1])
            self.abfig.canvas.toolbar_visible = False
            self.abfig.canvas.header_visible = False
            self.abfig.canvas.footer_visible = False
            self.abfig.canvas.resizable = False 
        else:
            self.abfig = None
            
    if self.abfig is not None:
        self.node_label = widgets.Label("選択中のノード内の移動")
        self.node_first_button = self.create_button("<<", width=40)
        self.node_prev_button = self.create_button("<", width=30)
        self.node_next_button = self.create_button(">", width=30)
        self.node_last_button = self.create_button(">>", width=40)
        
Mbtree_Anim.create_widgets = create_widgets
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from tree import Mbtree_Anim
import matplotlib.pyplot as plt
import ipywidgets as widgets

def create_widgets(self):
元と同じなので省略
    with plt.ioff():
元と同じなので省略
        if self.isscore and hasattr(self.mbtree, "ablist_by_score"):
-           self.abfig = plt.figure(figsize=(5, 1))
+           self.abfig = plt.figure(figsize=(7, 1))
元と同じなので省略
        
Mbtree_Anim.create_widgets = create_widgets
```
</details>

## Mbtree_Anim クラスの `update_ab` メソッドの修正

次に、下記のプログラムのように __A、P、M、R の値を表示__ するように __`update_ab`__ を修正します。

* __4 行目__：先程の __`ablist_by_score`__ の要素に代入された __tuple の修正に合わせて__、__A と P の値を__ tuple から取り出して __`num_calculated` と `num_pruned` に代入する__ ように修正する
* __5 行目__：Figure の幅を広げたので、Axes の表示範囲をそれに合わせて 23 に広げる。この値も筆者が試行錯誤して決めた値である
* __6、7 行目__：M と R の値を計算して変数に代入する
* __8、9 行目__：4 つの行の見出しとデータを表す list を `textlist` と `datalist` に代入する
* __10 ~ 12 行目__：for 文を使って、4 つの行を繰り返し処理で表示する。この部分について忘れた方は、この処理の直前で同様の処理を行っている[以前の記事](https://qiita.com/ysgeso/items/fd421d90cd1f47e38cdb#%E5%A1%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97%E3%81%AE%E8%89%B2%E3%81%AE%E8%AA%AC%E6%98%8E%E3%81%AE%E6%8F%8F%E7%94%BB)を復習すること

```python
 1  import matplotlib.patches as patches
 2  
 3  def update_ab(self):
 4      alpha, beta, score, status, num_calculated, num_pruned = self.mbtree.ablist_by_score[self.play.value]
元と同じなので省略
 5      self.abax.set_xlim(-4, 23)
元と同じなので省略 
 6      num_total = num_calculated + num_pruned
 7      num_ratio = num_calculated / num_total
 8      textlist = [ "計算済", "枝狩り", "合計", "割合" ]
 9      datalist = [ num_calculated, num_pruned, num_total, num_ratio]
10      for i in range(4):
11           self.abax.text(15, 1 - i * 0.7, textlist[i])
12           self.abax.text(17, 1 - i * 0.7, datalist[i])
13         
14  Mbtree_Anim.update_ab = update_ab
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
import matplotlib.patches as patches

def update_ab(self):
    alpha, beta, score, status, num_calculated, num_pruned = self.mbtree.ablist_by_score[self.play.value]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    acolor = "red" if maxnode else "black"
    bcolor = "black" if maxnode else "red"
                            
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4   
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    
    color = "lightgray" if maxnode else "aqua"
    rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphacoord-minus_inf,
                            height=1, fc=color)
    self.abax.add_patch(rect)
    rect = patches.Rectangle(xy=(alphacoord, -0.5), width=betacoord-alphacoord,
                            height=1, fc="yellow")
    self.abax.add_patch(rect)
    color = "aqua" if maxnode else "lightgray"
    rect = patches.Rectangle(xy=(betacoord, -0.5), width=plus_inf-betacoord,
                            height=1, fc=color)
    self.abax.add_patch(rect)

    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")
        
    arrowprops = { "arrowstyle": "->"}
    self.abax.plot(alphacoord, 0, "or")
    self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                    arrowprops=arrowprops, ha="center", c=acolor)
    self.abax.plot(betacoord, 0, "ob")
    self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                    arrowprops=arrowprops, ha="center", c=bcolor)
    if score is not None:
        self.abax.plot(score, 0, "og")
        self.abax.annotate(f"score = {score}", xy=(score, 0),
                        xytext=((minus_inf + plus_inf) / 2, 1), 
                        arrowprops=arrowprops, ha="center")
            
    facecolorlist = ["aqua", "yellow", "lightgray"]
    textcolorlist = ["black", "black", "black"]
    if maxnode:
        nodetype = f"深さ {self.selectednode.mb.move_count} max node"
        textlist = ["β 狩り (β ≦ score)", "α 値の更新 (α < score < β)", "α 値の更新なし (score ≦ α)"]
        if score is not None :
            if beta <= score:
                textcolorlist[0] = "red"
            elif alpha < score:
                textcolorlist[1] = "red"
            else:
                textcolorlist[2] = "red"
    else:
        nodetype = f"深さ {self.selectednode.mb.move_count} min node"
        textlist = ["α 狩り (score <= α)", "β 値の更新 (α < score < β)", "β 値の更新なし (score ≦ β)"]
        if score is not None :
            if score <= alpha:
                textcolorlist[0] = "red"
            elif score < beta:
                textcolorlist[1] = "red"
            else:
                textcolorlist[2] = "red"
            
    if status == "start":
        facecolor = "white"
        nodetype += " 処理の開始"
    elif status == "score":
        facecolor = "lightyellow"
        nodetype += " 子ノードの評価値"
    elif status == "update":
        facecolor = "lightcyan"
        if maxnode:
            nodetype += " α 値の処理"
        else:
            nodetype += " β 値の処理"
    else:
        facecolor = "lavenderblush"
        nodetype += " 評価値の確定"
    self.abfig.set_facecolor(facecolor)
    self.abax.text(6, 1, nodetype)   
    for i in range(3):
        rect = patches.Rectangle(xy=(5, 0.3 - i * 0.7), width=0.8, height=0.5, fc=facecolorlist[i])
        self.abax.add_patch(rect)
        self.abax.text(6, 0.4 - i * 0.7, textlist[i], c=textcolorlist[i])    
    
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total
    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, num_ratio]
    for i in range(4):
        self.abax.text(15, 1 - i * 0.7, textlist[i])
        self.abax.text(17, 1 - i * 0.7, datalist[i])
        
Mbtree_Anim.update_ab = update_ab
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
import matplotlib.patches as patches

def update_ab(self):
-   alpha, beta, score, status = self.mbtree.ablist_by_score[self.play.value]
+   alpha, beta, score, status, num_calculated, num_pruned = self.mbtree.ablist_by_score[self.play.value]
元と同じなので省略
-   self.abax.set_xlim(-4, 14)
+   self.abax.set_xlim(-4, 23)
元と同じなので省略   
+   num_total = num_calculated + num_pruned
+   num_ratio = num_calculated / num_total
+   textlist = [ "計算済", "枝狩り", "合計", "割合" ]
+   datalist = [ num_calculated, num_pruned, num_total, num_ratio]
+   for i in range(4):
+       self.abax.text(15, 1 - i * 0.7, textlist[i])
+       self.abax.text(17, 1 - i * 0.7, datalist[i])
        
Mbtree_Anim.update_ab = update_ab
```
</details>

:::note
上記の 10 ~ 14 行目の処理は組み込み関数 [`enumerate`](https://qiita.com/ysgeso/items/7cd67869855550d222f5#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E9%96%A2%E6%95%B0-enumerate) とまだ説明していない組み込み関数 `zip` を利用して下記のプログラムのように記述することもできます。

```python
for i, (text, data) in enumerate(zip(textlist, datalist)):
     self.abax.text(15, 1 - i * 0.7, text)
     self.abax.text(17, 1 - i * 0.7, data)
```

ただし、本記事では組み込み関数 `zip` をまだ説明していない点と、`enumerate` と `zip` を併用した場合に 1 行目で下記のようなプログラムを記述するとエラーになってしまう点が慣れていないと間違いやすいので本記事では採用しません。

```python
for i, text, data in enumerate(zip(textlist, datalist)):
```

なお、組み込み関数 `zip` は良く使われるので、今後の記事で解説する予定です。
:::

上記の修正後に下記のプログラムで Mbtree_Anim のアニメーションを表示しようとすると、実行結果のような __エラーが発生__ します。エラーメッセージから、どのようなエラーが発生しているかについて少し考えてみて下さい。

```python
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

```python
略
Cell In[4], line 100
     97     self.abax.text(6, 0.4 - i * 0.7, textlist[i], c=textcolorlist[i])    
     99 num_total = num_calculated + num_pruned
--> 100 num_ratio = num_calculated / num_total
    101 textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    102 datalist = [ num_calculated, num_pruned, num_total, num_ratio]

ZeroDivisionError: division by zero
```

### ゼロ除算のエラー原因と修正

エラーメッセージから、下記のプログラムを実行した際に、__0（zero）で（by）割り算（division）を行ったことが原因__ であることがわかります。

```python
num_ration = num_calculated / num_total
```

Mbtree_Anim で __最初に表示__ されるのはアニメーションの __最初のフレーム__ で、最初のフレームでは αβ 法の __処理がまだ行われていない__ ので、__A も P も 0__ になるため、その合計である __M も 0 になります__。従って、__R = A / M__ の計算は __0 ÷ 0 が計算される__ ことになるため、確かに __0 で割り算を行っています__。

__数学__ では __0 で割り算を行ってはいけない__[^3] という __決まりがあり__、0 で割り算することを __0 除算__ と呼びます。Python を含む __多くのプログラム言語__ では __0 除算を行うとエラーが発生__ してプログラムが止まります[^4]。そのため、上記のプログラムのように __0 除算が行われる可能性がある__ 場合は、「__分母が 0 になる場合は割り算を行わずに別の処理を行う__」ようにプログラムを記事する必要があります。

割り算の __代わりとなる処理__ としては、「__エラーメッセージを表示__」する、「__何らかの値を割り算の答えの代わりとする__」など、__状況に応じて適切な処理を選択する必要__ があります。

今回の場合は、__分子の A が 0__ なので割合として __0 を代わりの答えとして計算__ することにします[^5]。下記はそのように修正したプログラムです。

```python
if num_total != 0:
    num_ratio = num_calculated / num_total
else:
    num_ratio = 0
```

もしくは下記のプログラムのように記述しても良いでしょう。本記事ではこちらの方法を採用することにします。

```python
num_ratio = num_calculated / num_total if num_total != 0 else 0
```

__0 除算__ はプログラムのエラーの中で __比較的良く発生するエラーの一つ__ です。上記の「何らかの __集合の中__ で __特定の性質を持つものの割合__ の計算」で起きるエラーは、初心者が発生させる 0 除算のエラーの典型例と言えるでしょう。

:::note
上記の A / M のような「何らかの集合の中で特定の性質を持つものの割合の計算」の場合は、__0 ≦ A ≦ M__ という性質が必ず満たされるため、A / M の __M が 0 の場合は A も必ず 0__ になります。そのような場合は、下記のプログラムの 1 行目のように、合計を計算する際に組み込み関数 `max` を利用して __分母の最小値を 1 にする__[^6]ことで __M が 0 の場合の答えとして 0 が計算される__ ようにすることもできます。

```python
num_total = max(1, num_calculated + num_pruned)
num_ratio = num_calculated / num_total
```

上記の方法で 0 除算に対処するプログラムを見かけることがあるので紹介しました。

なお、__M が 0 の場合に A が 0 にならないことがある場合__ に上記の方法で計算を行うと、割合を表すはずの R の値が負の値や 1 を超える値になることがあるので、__上記の方法は利用できない__ 点に注意して下さい。
:::

:::note
0 除算に対する他の対処法としては、下記のプログラムのように[以前の記事](https://qiita.com/ysgeso/items/1800a353cb609a0fef4e#%E4%BE%8B%E5%A4%96%E5%87%A6%E7%90%86)で紹介した、エラーが発生した際にプログラムを中断せずに特定の処理を行うという、例外処理を利用するという方法があります。

```python
try:
    num_ratio = num_calculated / num_total
except:
    num_ratio = 0
```
:::

### 数値の右揃えの表示

先程説明したように、`num_ratio` を計算する処理を下記のプログラムのように修正します。

```python
num_ratio = num_calculated / num_total if num_total != 0 else 0
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_ab(self):
    alpha, beta, score, status, num_calculated, num_pruned = self.mbtree.ablist_by_score[self.play.value]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    acolor = "red" if maxnode else "black"
    bcolor = "black" if maxnode else "red"
                            
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4   
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    
    color = "lightgray" if maxnode else "aqua"
    rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphacoord-minus_inf,
                            height=1, fc=color)
    self.abax.add_patch(rect)
    rect = patches.Rectangle(xy=(alphacoord, -0.5), width=betacoord-alphacoord,
                            height=1, fc="yellow")
    self.abax.add_patch(rect)
    color = "aqua" if maxnode else "lightgray"
    rect = patches.Rectangle(xy=(betacoord, -0.5), width=plus_inf-betacoord,
                            height=1, fc=color)
    self.abax.add_patch(rect)

    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")
        
    arrowprops = { "arrowstyle": "->"}
    self.abax.plot(alphacoord, 0, "or")
    self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                    arrowprops=arrowprops, ha="center", c=acolor)
    self.abax.plot(betacoord, 0, "ob")
    self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                    arrowprops=arrowprops, ha="center", c=bcolor)
    if score is not None:
        self.abax.plot(score, 0, "og")
        self.abax.annotate(f"score = {score}", xy=(score, 0),
                        xytext=((minus_inf + plus_inf) / 2, 1), 
                        arrowprops=arrowprops, ha="center")
            
    facecolorlist = ["aqua", "yellow", "lightgray"]
    textcolorlist = ["black", "black", "black"]
    if maxnode:
        nodetype = f"深さ {self.selectednode.mb.move_count} max node"
        textlist = ["β 狩り (β ≦ score)", "α 値の更新 (α < score < β)", "α 値の更新なし (score ≦ α)"]
        if score is not None :
            if beta <= score:
                textcolorlist[0] = "red"
            elif alpha < score:
                textcolorlist[1] = "red"
            else:
                textcolorlist[2] = "red"
    else:
        nodetype = f"深さ {self.selectednode.mb.move_count} min node"
        textlist = ["α 狩り (score <= α)", "β 値の更新 (α < score < β)", "β 値の更新なし (score ≦ β)"]
        if score is not None :
            if score <= alpha:
                textcolorlist[0] = "red"
            elif score < beta:
                textcolorlist[1] = "red"
            else:
                textcolorlist[2] = "red"
            
    if status == "start":
        facecolor = "white"
        nodetype += " 処理の開始"
    elif status == "score":
        facecolor = "lightyellow"
        nodetype += " 子ノードの評価値"
    elif status == "update":
        facecolor = "lightcyan"
        if maxnode:
            nodetype += " α 値の処理"
        else:
            nodetype += " β 値の処理"
    else:
        facecolor = "lavenderblush"
        nodetype += " 評価値の確定"
    self.abfig.set_facecolor(facecolor)
    self.abax.text(6, 1, nodetype)   
    for i in range(3):
        rect = patches.Rectangle(xy=(5, 0.3 - i * 0.7), width=0.8, height=0.5, fc=facecolorlist[i])
        self.abax.add_patch(rect)
        self.abax.text(6, 0.4 - i * 0.7, textlist[i], c=textcolorlist[i])    
    
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, num_ratio]
    for i in range(4):
        self.abax.text(15, 1 - i * 0.7, textlist[i])
        self.abax.text(17, 1 - i * 0.7, datalist[i])
        
Mbtree_Anim.update_ab = update_ab
```
</details>

上記の修正後に下記のプログラムを実行するとエラーが発生しなくなり、実行結果のような表示が行われます。なお、ゲーム木の部分の表示は特に重要ではないので省略しました。

```python
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e8980dab-fe0c-c9d5-4a02-a9ddda586151.png" width="450">

下図は __最後のフレームでの表示__ ですが、以下の点が __わかりづらい__ 表示になっています。

* __数字が左揃えで表示__ されている点がわかりづらい
* 割合の __小数点以下の表示が多すぎて__ わかりづらい
* 割合が __% で表示されていない__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ee2b59b0-12ed-06d2-01d6-e58f8e6c1f4d.png" width="450">

そこで、__数字を右揃えで表示__ し、__割合の数字を % の単位で小数点以下 1 桁までの数値で表示__ するように修正することにします。

後者に関しては[先程 `dprint` で割合を表示](#mbtree-クラスの-calc_score_by_ab-メソッドの修正)したのと同様の方法で修正することができます。

文字を右揃えで表示するには、__Axes__ の __`text`__ メソッドの __実引数に以下のような記述__ を行います。なお、[以前の記事](https://qiita.com/ysgeso/items/bf42b7b578bc2d5da949#%E6%8F%83%E3%81%88)で紹介した __f 文字列__ の __>__ という __書式指定子__ を利用して右揃えを行うこともできますが、その場合は __右揃えの位置__ を __座標で設定できない__ という問題があるので本記事では採用しませんが、そちらの方が良いと思った方は採用しても良いでしょう。

* __x 座標__ を表す実引数に、文字列の __右端の座標を記述__ する
* __[文字の表示の揃えを指定](https://qiita.com/ysgeso/items/903dc60e865ff56191fc#%E5%AF%BE%E6%88%A6%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AE%E8%A1%A8%E7%A4%BA%E5%A0%B4%E6%89%80%E3%81%AE%E5%BE%AE%E4%BF%AE%E6%AD%A3)__ するキーワード引数 __`ha="right"` を記述__ する

下記はそのように __`update_ab`__ を修正したプログラムです。

* __5 行目__：割合を f 文字列を使って % の単位で小数点以下 1 桁までの文字列に修正する
* __8 行目__：数値を表示する x 座標を文字の右端の座標に修正し、キーワード引数 `ha="right"` を記述するように修正する

```python
 1  def update_ab(self):
元と同じなので省略
 2      num_total = num_calculated + num_pruned
 3      num_ratio = num_calculated / num_total if num_total != 0 else 0
 4      textlist = [ "計算済", "枝狩り", "合計", "割合" ]
 5      datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
 6      for i in range(4):
 7          self.abax.text(15, 1 - i * 0.7, textlist[i])
 8          self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
 9          
10  Mbtree_Anim.update_ab = update_ab
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_ab(self):
    alpha, beta, score, status, num_calculated, num_pruned = self.mbtree.ablist_by_score[self.play.value]
    maxnode = self.selectednode.mb.turn == Marubatsu.CIRCLE
    acolor = "red" if maxnode else "black"
    bcolor = "black" if maxnode else "red"
                            
    self.abax.clear()
    self.abax.set_xlim(-4, 23)
    self.abax.set_ylim(-1.5, 1.5)
    self.abax.axis("off")

    minus_inf = -3
    plus_inf = 4   
    alphacoord = max(minus_inf, alpha)
    betacoord = min(plus_inf, beta)
    
    color = "lightgray" if maxnode else "aqua"
    rect = patches.Rectangle(xy=(minus_inf, -0.5), width=alphacoord-minus_inf,
                            height=1, fc=color)
    self.abax.add_patch(rect)
    rect = patches.Rectangle(xy=(alphacoord, -0.5), width=betacoord-alphacoord,
                            height=1, fc="yellow")
    self.abax.add_patch(rect)
    color = "aqua" if maxnode else "lightgray"
    rect = patches.Rectangle(xy=(betacoord, -0.5), width=plus_inf-betacoord,
                            height=1, fc=color)
    self.abax.add_patch(rect)

    self.abax.plot(range(minus_inf, plus_inf + 1), [0] * (plus_inf + 1 - minus_inf) , "|-k")
    for num in range(minus_inf, plus_inf + 1):
        if num == minus_inf:
            numtext = "-∞"
        elif num == plus_inf:
            numtext = "∞"
        else:
            numtext = num
        self.abax.text(num, -1, numtext, ha="center")
        
    arrowprops = { "arrowstyle": "->"}
    self.abax.plot(alphacoord, 0, "or")
    self.abax.annotate(f"α = {alpha}", xy=(alphacoord, 0), xytext=(minus_inf, 1),
                    arrowprops=arrowprops, ha="center", c=acolor)
    self.abax.plot(betacoord, 0, "ob")
    self.abax.annotate(f"β = {beta}", xy=(betacoord, 0), xytext=(plus_inf, 1),
                    arrowprops=arrowprops, ha="center", c=bcolor)
    if score is not None:
        self.abax.plot(score, 0, "og")
        self.abax.annotate(f"score = {score}", xy=(score, 0),
                        xytext=((minus_inf + plus_inf) / 2, 1), 
                        arrowprops=arrowprops, ha="center")
            
    facecolorlist = ["aqua", "yellow", "lightgray"]
    textcolorlist = ["black", "black", "black"]
    if maxnode:
        nodetype = f"深さ {self.selectednode.mb.move_count} max node"
        textlist = ["β 狩り (β ≦ score)", "α 値の更新 (α < score < β)", "α 値の更新なし (score ≦ α)"]
        if score is not None :
            if beta <= score:
                textcolorlist[0] = "red"
            elif alpha < score:
                textcolorlist[1] = "red"
            else:
                textcolorlist[2] = "red"
    else:
        nodetype = f"深さ {self.selectednode.mb.move_count} min node"
        textlist = ["α 狩り (score <= α)", "β 値の更新 (α < score < β)", "β 値の更新なし (score ≦ β)"]
        if score is not None :
            if score <= alpha:
                textcolorlist[0] = "red"
            elif score < beta:
                textcolorlist[1] = "red"
            else:
                textcolorlist[2] = "red"
            
    if status == "start":
        facecolor = "white"
        nodetype += " 処理の開始"
    elif status == "score":
        facecolor = "lightyellow"
        nodetype += " 子ノードの評価値"
    elif status == "update":
        facecolor = "lightcyan"
        if maxnode:
            nodetype += " α 値の処理"
        else:
            nodetype += " β 値の処理"
    else:
        facecolor = "lavenderblush"
        nodetype += " 評価値の確定"
    self.abfig.set_facecolor(facecolor)
    self.abax.text(6, 1, nodetype)   
    for i in range(3):
        rect = patches.Rectangle(xy=(5, 0.3 - i * 0.7), width=0.8, height=0.5, fc=facecolorlist[i])
        self.abax.add_patch(rect)
        self.abax.text(6, 0.4 - i * 0.7, textlist[i], c=textcolorlist[i])    
    
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
    datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, 1 - i * 0.7, textlist[i])
        self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
        
Mbtree_Anim.update_ab = update_ab
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_ab(self):
元と同じなので省略
    num_total = num_calculated + num_pruned
    num_ratio = num_calculated / num_total if num_total != 0 else 0
    textlist = [ "計算済", "枝狩り", "合計", "割合" ]
-   datalist = [ num_calculated, num_pruned, num_total, num_ratio]
+   datalist = [ num_calculated, num_pruned, num_total, f"{num_ratio * 100:.1f}%"]
    for i in range(4):
        self.abax.text(15, 1 - i * 0.7, textlist[i])
-       self.abax.text(17, 1 - i * 0.7, datalist[i])
+       self.abax.text(19.5, 1 - i * 0.7, datalist[i], ha="right")
        
Mbtree_Anim.update_ab = update_ab
```
</details>

上記の修正後に下記のプログラムを実行し、0 フレーム目と最後のフレームを表示すると、実行結果のように __意図通りの表示が行われる__ ことが確認できます。

```python
Mbtree_Anim(mbtree, isscore=True)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4043ef30-1d31-1cf3-5cb8-0480d0a8bc52.png" width="450">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6c1e648f-c605-ccbc-0929-f8b581fa137a.png" width="450">

また、下図のように __途中のフレーム__ での A、P、M、R の値が表示されることも確認できます。他の様々なフレームを表示して確認してみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0bf6bb10-2971-87ec-27c4-89b81642bb01.png" width="600">

# 今回の記事のまとめ

今回の記事では、Mbtree_Anim で表示されているフレームまでの A、P、M、R の値を表示するようにプログラムを改良しました。

# 本記事で入力したプログラム

| リンク | 説明 |
|:--|:--|
| [marubatsu.ipynb](https://github.com/ysgeso/marubatsu/blob/master/147/marubatsu.ipynb)  | 本記事で入力して実行した JupyterLab のファイル |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/147/tree_new.py)  | 本記事で更新した tree_new.py  |

# 次回の記事

https://qiita.com/ysgeso/items/f8fe755341b3cff2d770

[^1]:[以前の記事](https://qiita.com/ysgeso/items/949d18e60939d510c931#%E6%AD%A3%E3%81%97%E3%81%84%E5%90%84%E6%B7%B1%E3%81%95%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E6%95%B0%E3%81%AE%E8%A8%88%E7%AE%97%E6%96%B9%E6%B3%95)で計算した 〇× ゲームの __ゲーム木のノードの数と一致__ します
[^2]:num は数を表す number の略、calculated は計算済という意味です
[^3]:その理由について説明すると長くなるので本記事では説明しません。興味がある方は 0 除算というキーワードで調べてみると良いでしょう
[^4]:JavaScript など、__一部のプログラム言語__ では 0 除算を行っても __エラーは発生せず__、__無限大を表す数値が計算__ されるものもあります
[^5]:0 ÷ 0 の答えが 0 であると勘違いしている人がいるかもしれませんが、__0 ÷ 0__ は数学では __行ってはいけない計算__ であり、__答えは 0 ではありません__
[^6]:0 / 正の数 = 0 になるので、正の数であれば 1 でなくてもかまいません