副題は「アルゴリズムが脳にしみこむ70問」。プログラマ脳を鍛える数学パズルの続編にあたる。 前著はプログラムのほとんどが Ruby で書かれていたが、 本書は Ruby のプログラムのほか JavaScript のプログラムも掲載されている。
自分で問題を作ってみるのがこの先いいのだろう。 たとえば、Q.12 「円周率に近似できる分数」では、小数第 n 位まで円周率と一致する分数のうち、 分母が最小のものを `π(n)` とするときの `π(n)` を求めている。すなわち、`π(1) = 19/6 = 3.166 cdots, π(2) = 22/7 = 3.1428cdots, π(3) = 245/78 = 3.14102cdots`である。 では `π(n)` の定義を「真の`π`の値との差が `1/10^n` 以内で円周率と一致する分数のうち分母が最小のもの」とするとどうなるだろうか。
Q01 は、「一発で決まる多数決」である。`N` 人でじゃんけんをするとき、グーチョキパーの数を比べ、それらを比較するだけで、(すなわち、グーチョキパーの順序関係を使わずに) 比較多数となった場合に決定が完了すると定義する。このとき、じゃんけんをただ一度だけして決定するときのパターンが何通りあるかを求める、という問題である。 本書で紹介されている解法は `O(N^2)` であるが、amazon でのあるレビュアは `O(N)` の解法を見つけたという。 気になって探すと(私が独力で考えたのではないのが悔しいが)、 ange1のブログ「今週のお題:一発で決まる多数決」問題解答 ( CodeIQ ) (ange1.hateblo.jp) があり、 `O(1)` の解法が載せられていた。
本書ではパズルを解くためにメモ化を多用している。たとえば、p.013 の問題に対してメモ化を取り入れ JavaScript でプログラムしたコード pre1_2.js を多少書き直して掲げる。 ただし、私のほうで、定数には const を明示し、かつ var は let に直し、また比較の == を === にするなど変更している。
// pre1_2.js
const M = 10;
const N = 100;
let memo = {};
function check(remain, pre) {
// 一度計算したものがあれば、それを返す
if (memo[[remain, pre]]) return memo[[remain, pre]];
// 配置する人がいなくなると終了
if (remain < 0) return 0;
if (remain === 0) return 1;
let cnt = 0;
for (let i=pre; i <= M; i++) {
cnt +=check(remain - i, i);
}
// 計算結果をメモする
return memo[[remain, pre]]=cnt;
}
console.log(check(N, 2));
このメモ化の手法は強力だが、このコードのような memo をグローバル変数とするファイルが複数あり(たとえば js1.js, js2.js, js3.js)、 1 つの HTML ファイルがこれらの JavaScript ファイルを同時に読み込むと、 相互の memo 変数が衝突してしまい、目的とするプログラムが得られない。
<!DOCTYPE html>
<html>
<head>...</head>
<body>
...
<script src="js1.js"></script>
<script src="js2.js"></script>
<script src="js3.js"></script>
</body>
</html>
そこで、このグローバルスコープをブロックスコープに閉じ込めるために、クロージャを使う。クロージャを使うと次のように書き直せる。
function check(n, pre) {
const M = 10;
let memo = {};
function _check(remain, pre) {
// 一度計算したものがあれば、それを返す
if (memo[[remain, pre]]) return memo[[remain, pre]];
// 配置する人がいなくなると終了
if (remain < 0) return 0;
if (remain===0) return 1;
let cnt=0;
for (let i=pre; i <= M; i++) {
cnt += check(remain - i, i);
}
// 計算結果をメモする
return memo[[remain, pre]]=cnt;
}
return _check(n, pre)
}
console.log(check(100, 2));
下請関数用の名前をもう一つ用意しないといけないのが難点だが、こうしておけばグローバル変数 memo が衝突する危険はない。 なお、他のグローバル変数のうち M はブロックスコープに閉じ込めている。 N は引数で渡したので表向きは消えている。
サポートページ(www.shoeisha.co.jp)からたどれる正誤表のほかは、次のとおりである。
p.113 上から 7 行目、(誤)移行すると、→(正)移項すると、
それから誤植ではないのだろうが、pp.143-144 にある q29_2.rb や q29_2.js にある階乗を求めるプログラムの名前が facorial となっている。
正しいスペリングは factorial だろう。他のページで階乗を求めるプログラムでは factorial が用いられている。
| 書名 | もっとプログラマ脳を鍛える数学パズル |
| 著者 | 増井敏克 |
| 発行日 | 2018 年 2 月 19 日 (初版) |
| 発行元 | 翔泳社 |
| 定価 | 2,580 円(本体) |
| サイズ | A5判 343 ページ |
| ISBN | 978-4-7981-5361-2 |
| その他 | 越谷市立図書館で借りて読む |
まりんきょ学問所 > コンピュータの部屋 > クイズ・パズル > 増井 敏克:もっとプログラマ脳を鍛える数学パズル