【対決! 人力 vs Python】結局、センター試験の数学はPythonで解くのと、自力で解くのと、どっちが速いのか？

# どこかで見たことあるネタだなと思ったら

やっぱり、先人がいました(ほかにもあるかも)。
**素晴らしい！**

* [2015年センター試験数学IAを全てプログラム(Python)で解く](https://qiita.com/akai_banana/items/b328fe0116d248127a36)
* [2015年センター試験数学ⅡBをPythonで解く](https://qiita.com/massa142/items/b224a07c1b33e9a39cd8)

# 1. センター試験に持ち込めるもの

以下が持ち込めるようですが、現時点で、**<font color="Crismon">Pythonのインタプリタは持ち込めない</font>**ようなので、注意が必要です。
開発者にとって、開発環境は鉛筆のようなものという主張も**通じない**と思います。
**<font color="Crismon">※許可されているもの以外を持ち込むのは絶対に絶対にやめましょう！</font>**

```
* 黒鉛筆(H，F，HBに限る。和歌・格言等が印刷されているものは不可。)、鉛筆キャップ。
* シャープペンシル(メモや計算に使用する場合のみ可、黒い芯に限る。)
* プラスチック製の消しゴム
* 鉛筆削り(電動式・大型のもの・ナイフ類は不可。)
* 時計(辞書、電卓、端末等の機能があるものや、それらの機能の有無が判別しづらいもの・秒針音のするもの・キッチンタイマー・大型のものは不可。)
* 眼鏡、ハンカチ、目薬、ティッシュペーパー(袋又は箱から中身だけ取り出したもの。)
```

# 2. 今回、勝負する問題

**2017年 大学入試センター試験 数学Ⅱ・数学Bの第1問 [1]だけ**です。

> この問題を選んだ理由: 「この問題ならPythonで勝てそうだから。」


**第1問 (必答問題) (配点 30)**

[1] 連立方程式

``` math
\left\{
\begin{array}{ll}
cos \, 2\alpha + cos \, 2\beta = \frac{4}{15} \quad\quad\quad\,\,\,\,...(1) \\
cos \, \alpha \, \, cos \, \beta = -\frac{2\sqrt{15}}{15}  \quad\quad\quad\,\,\,\,\,...(2)
\end{array}
\right.
```
を考える。ただし、$0 \leqq \alpha \leqq \pi, \quad 0 \leqq \beta \leqq \pi$ であり, $\alpha < \beta$ かつ、

``` math
|cos \, \alpha| \geqq |cos \, \beta| \quad\quad\quad\quad\quad\quad...(3)
```
とする。このとき, $cos \, \alpha$ と $cos \, \beta$ の値を求めよう。

2倍角の公式を用いると, (1)から

``` math
cos^2 \alpha + cos^2 \beta = \frac{[アイ]}{[ウエ]}
```
が得られる。また, (2)から,

``` math
cos^2 \alpha \, \, cos^2 \beta = \frac{[オ]}{15}
```
である。
したがって, 条件(3)を用いると

``` math
cos^2 \alpha = \frac{[カ]}{[キ]} \quad, \quad\quad cos^2 \beta = \frac{[ク]}{[ケ]}
```

である。よって, (2)の条件 $0 \leqq \alpha \leqq \pi, \quad 0 \leqq \beta \leqq \pi, \quad \alpha < \beta$ から

``` math
cos \, \alpha = \frac{[コ]\sqrt{[サ]}}{[シ]} \quad, \quad\quad cos \, \beta = \frac{[ス]\sqrt{[セ]}}{[ソ]}
```
である。


# 3. 「Python + 私」側の勝利条件

全体の問題量からして、この問題は、たぶん6分とか7分以内で解かないと厳しいと思います。
その辺を考慮して、「Python + 私」側の勝利条件は以下に設定します。

* コーディングに要する時間と、実行する時間の合計が5分以内。
* 完勝と言えるレベルに達するには、3分、いや、2分を切ることが必要。
* Pythonの開発環境(IDE含む)は事前インストール、起動しておいてよい。

参考までに準備した環境は、以下です。

* Windows 10 Pro
* Python 3.6
* Visual Studio Code + Python Extensionインストール済み

# 4. いざ尋常に勝負!
(完成したコード全体は、本記事一番最後に掲載します。)

## 4.1. 出題者の誘導など、はなっから無視します！

2倍角の公式とかわけわかんないこと言ってるので、
いきなり、最後の、$cos \alpha, cos \beta$ から求めます。
PythonとSymPy使えばできるはず。
まずは、SymPyインストール！

``` shell
pip install sympy
```

では、一気に解いちゃいましょう！

$f_1 = cos \, 2\alpha + cos \, 2\beta - \frac{4}{15}$
$f_2 = cos \, \alpha \, \, cos \, \beta + \frac{2\sqrt{15}}{15}$
として、$f_1 = 0$ と $f_2 = 0$の連立方程式を、
$cos \, \alpha$ と $cos \, \beta$ に関して解いていきます。

``` python
from sympy import symbols, expand, cos, Rational, sqrt, solve

# シンボルの定義。今回の問題ではαとβを、それぞれaとbのシンボルとして扱う。
a, b = symbols('a b')

# (1)と(2)それぞれの式をf1 = 0, f2 = 0となるような関数f1, f2として定義。
# この後、cos(a)とcos(b)に関して連立方程式を解くので、(1)は展開(expand)しておく。
f1 = expand(cos(2*a) + cos(2*b) - Rational(4, 15), trig = True)
f2 = cos(a) * cos(b) + 2 * sqrt(15) / 15

# f1とf2の連立方程式を、cos(a)とcos(b)に関して解きます(solve)。
answers = solve([f1, f2], [cos(a), cos(b)])
```

## 4.2. 条件から目的の解に絞り込む。

この時点で、`answers`にはいくつかの解が入っているはずですが、条件から目的のものを見つけます。

$0 \leqq \alpha \leqq \pi, \quad 0 \leqq \beta \leqq \pi$ であり, $\alpha < \beta$ という条件ですが、
$0 \leqq \theta \leqq \pi$ の範囲では、$\theta$ が大きくなるにつれて、$cos \theta$ は小さくなっていくので、
$cos \alpha > cos \beta$ となる解を探します。
かつ、(3)の条件 $|cos \, \alpha| \geqq |cos \, \beta|$ も評価して目的の解を探します。

絶対値出てきたので、`import`に加えておきます。

``` python
from sympy import symbols, expand, cos, Rational, sqrt, solve, Abs
```

あとは、`answers`に入っている解の組のリストから条件に合う解の組を探していきます。
`if`文では、単に条件をそのまま、条件式にするだけです。

``` python
# 0 <= a <= π, 0 <= b <= πでは、a, bが大きい方がcos(a), cos(b)の値は小さくなる。
# a < bなので、cos(a) > cos(b)になるものを探す。
# かつ、(3)の条件に合うものを探す。
for cos_a, cos_b in answers:
    if cos_a > cos_b and Abs(cos_a) >= Abs(cos_b):
        break
```

## 4.3. [コ][サ][シ][ス][セ][ソ]の攻略完了!

これで、$cos \alpha, cos \beta$　が求まります。

``` python
# 目的のcos(a)とcos(b)が求まったので、表示します([コ][サ][シ][ス][セ][ソ]の答え)。
print("[コサシ] -> {}, [スセソ] -> {}".format(cos_a, cos_b))
```

## 4.4. あとは簡単！

$ cos^2 \alpha,  cos^2 \beta$ と $cos^2 \alpha + cos^2 \beta$、$cos^2 \alpha \, \, cos^2 \beta$ を求めるだけですが、
 $cos \alpha, cos \beta$が既に求まっているので単に計算していくだけで求まります。

``` python
# cos(a)とcos(b)それぞの2乗を求めます。
squared_cos_a = cos_a ** 2
squared_cos_b = cos_b ** 2

# cos(a)の2乗と、cos(b)の2乗を表示します([カ][キ][ク][ケ]の答え)。
print("[カキ] -> {}, [クケ] -> {}".format(squared_cos_a, squared_cos_b))

# cos(a)の2乗と、cos(b)の2乗の和と積もそれぞれ表示します([ア][イ][ウ][エ][オ]の答え)。
print("[アイウエ] -> {}".format(squared_cos_a + squared_cos_b))
print("[オ] -> {}".format(squared_cos_a * squared_cos_b))
```

(完成したコード全体は、本記事一番最後に掲載します。)


# 5. 勝敗

完成したPythonコードの実行に要した時間(すべての解を求めるまでの時間)は、
私の手元の環境では、**400ミリ秒未満** (あくまでも、コードが実行開始されてからの時間)でした。

ただし、コーディングに要した時間が **14分41秒524** だったので、
Python側(というか、私)の完敗、**圧倒的敗北**でした。。。

(この記事自体は、勝負決着後に書き始めているので、記事の執筆時間は含まれません。)

## 5.1. 敗因の分析

1. 私のプログラミング能力の未熟さ。
1. まじめに、コメントを書いてしまったこと。
1. 連立方程式解いた後の解の絞り込みの実装で、デバッガ使いながら、多少手こずったこと。
1. 思った以上に、SymPyのインストールに時間がかかったこと。

# 6. 完成したコード

``` python
from sympy import symbols, expand, cos, Rational, sqrt, solve, Abs
import time

# 解くのにかかった時間を出力したいので、最初の時刻を覚えておく。
offset = time.time()

# シンボルの定義。今回の問題ではαとβを、それぞれaとbのシンボルとして扱う。
a, b = symbols('a b')

# (1)と(2)それぞれの式をf1 = 0, f2 = 0となるような関数f1, f2として定義。
# この後、cos(a)とcos(b)に関して連立方程式を解くので、(1)は展開(expand)しておく。
f1 = expand(cos(2*a) + cos(2*b) - Rational(4, 15), trig = True)
f2 = cos(a) * cos(b) + 2 * sqrt(15) / 15

# f1とf2の連立方程式を、cos(a)とcos(b)に関して解きます(solve)。
answers = solve([f1, f2], [cos(a), cos(b)])

# 0 <= a <= π, 0 <= b <= πでは、a, bが大きい方がcos(a), cos(b)の値は小さくなる。
# a < bなので、cos(a) > cos(b)になるものを探す。
# かつ、(3)の条件に合うものを探す。
for cos_a, cos_b in answers:
    if cos_a > cos_b and Abs(cos_a) >= Abs(cos_b):
        break

# 目的のcos(a)とcos(b)が求まったので、表示します([コ][サ][シ][ス][セ][ソ]の答え)。
print("[コサシ] -> {}, [スセソ] -> {}".format(cos_a, cos_b))

# cos(a)とcos(b)それぞの2乗を求めます。
squared_cos_a = cos_a ** 2
squared_cos_b = cos_b ** 2

# cos(a)の2乗と、cos(b)の2乗を表示します([カ][キ][ク][ケ]の答え)。
print("[カキ] -> {}, [クケ] -> {}".format(squared_cos_a, squared_cos_b))

# cos(a)の2乗と、cos(b)の2乗の和と積もそれぞれ表示します([ア][イ][ウ][エ][オ]の答え)。
print("[アイウエ] -> {}".format(squared_cos_a + squared_cos_b))
print("[オ] -> {}".format(squared_cos_a * squared_cos_b))

# 解くのにかかった時間を表示します。
elapsed = time.time() - offset
print('解くのにかかった時間: {0:.4f}ミリ秒'.format((elapsed * 1000)))
```