Make 10 の数理と実装
なぜ「全探索」が最もエレガントな証明になるのか?
ファームウェアエンジニアの視点で解く、数遊びのアルゴリズム。
1. ナンバープレートの脳内パズル
車を運転しているとき、前を走る車のナンバープレートが気になってしまうことはありませんか? 特に、4つの数字がバラバラ(すべて異なる数字)だった時、私の脳内では自動的にある処理が走り始めます。
「この4つの数字を四則演算(+, -, ×, ÷)して、10を作れ」
いわゆる「Make 10(テンパズル)」と呼ばれる遊びです。 私は昔からこれが得意で、信号待ちで前の車を見かけた瞬間、ほぼ反射的に解法が浮かびます。例えば「1, 3, 7, 9」なら $(7+3) \times (9-1) \div 8$ ...いや、もっと単純に $(9-7) \times (1+3) + \dots$ といった具合に、瞬時に式を組み立ててしまいます。
経験則として、「1から9までの異なる4つの数字」を選んだ場合、解けない組み合わせに出会うことはまずありません。 では、これを数学的に証明することはできるでしょうか? 「異なる4つの数字を選べば、必ず10が作れる」という命題を、$x$ や $y$ を使った美しい数式で証明しようとすると、意外な壁にぶつかります。
2. 代数学の敗北:演算子は変数になれない
通常、数学の証明といえば、代数的な変形を思い浮かべます。 しかし、Make 10 の難しさは、数字($a, b, c, d$)だけでなく、「演算子($+, -, \times, \div$)」と「計算順序(カッコ)」そのものが変数である点にあります。
一般的な方程式 $f(x) = 10$ のような形では、演算子の組み合わせという「構造の変化」を表現しきれません。 連続的な数値の変化を扱う解析学(微分積分など)のアプローチは、このような離散的(飛び飛び)な構造に対しては無力なのです。
3. エンジニアの解法:「全探索」という証明
ここで登場するのが、私たちエンジニアが得意とするアルゴリズム的アプローチです。 数式で一般化できないのなら、「有り得るすべてのパターンをしらみつぶしに検証(Proof by exhaustion)」してしまえばいいのです。
「美しくない」と思われるでしょうか? いいえ、これこそが離散数学における最も強力で確実な証明です。
- 数字の組み合わせ: 1~9から異なる4つを選ぶ(${}_9C_4$) = 126通り
たったの126通りです。 それぞれの組み合わせに対して、演算子と順序(逆ポーランド記法などの木構造)の全パターンを適用しても、コンピュータにとっては瞬きする間もない計算量です。
これはファームウェア開発における「状態遷移テスト」と同じ考え方です。 無限の入力があるアナログ回路とは異なり、デジタルシステム(離散系)は状態が有限であるため、「全網羅」こそが最強の品質保証(証明)となります。
4. 検証コード(C言語)
論より証拠。実際にC言語でソルバーを書き、全126通りを検証してみました。 以下のコードは、再帰探索(DFS)を用いて数値を縮約しながら、可能なすべての演算パターンを探索するプログラムです。
/**
* Make 10 Solver & Prover
* Author: Dr. WataWata (M.A.L.)
* Description: Verifies that all combinations of 4 distinct digits (1-9) can make 10.
*/
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#define TARGET 10.0
#define EPSILON 1e-6
// 再帰的に演算を行う関数 (Depth First Search)
bool can_make_10(double nums[], int n) {
if (n == 1) {
return fabs(nums[0] - TARGET) < EPSILON;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) continue;
double next_nums[4];
int m = 0;
for (int k = 0; k < n; k++) {
if (k != i && k != j) next_nums[m++] = nums[k];
}
double a = nums[i];
double b = nums[j];
// Addition
next_nums[m] = a + b;
if (can_make_10(next_nums, n - 1)) return true;
// Subtraction
next_nums[m] = a - b;
if (can_make_10(next_nums, n - 1)) return true;
// Multiplication
next_nums[m] = a * b;
if (can_make_10(next_nums, n - 1)) return true;
// Division
if (fabs(b) > EPSILON) {
next_nums[m] = a / b;
if (can_make_10(next_nums, n - 1)) return true;
}
}
}
return false;
}
int main() {
int count = 0;
int success_count = 0;
printf("Exploring all combinations of 4 distinct digits from 1-9...\n");
for (int i = 1; i <= 9; i++) {
for (int j = i + 1; j <= 9; j++) {
for (int k = j + 1; k <= 9; k++) {
for (int l = k + 1; l <= 9; l++) {
count++;
double initial_set[4] = {(double)i, (double)j, (double)k, (double)l};
if (can_make_10(initial_set, 4)) {
success_count++;
} else {
printf("[FAIL] %d, %d, %d, %d -> Impossible\n", i, j, k, l);
}
}
}
}
}
printf("------------------------------------------------\n");
printf("Total combinations tested: %d\n", count);
printf("Solvable combinations: %d\n", success_count);
if (count == success_count) {
printf("Result: PROVED. All distinct combinations can make 10.\n");
} else {
printf("Result: DISPROVED.\n");
}
return 0;
}
このプログラムを実行すると、Result: PROVED が出力され、「1~9の異なる4つの数字」の組み合わせ全126通りにおいて、解が存在することが数学的(計算機的)に証明されます。
5. Dr.WataWata Insight
「数式で解けない」ということは、決して「数学的ではない」ということではありません。 有限の離散構造において、計算機(アルゴリズム)を用いた全探索は、立派な数学的証明の一つです。
私が車のナンバープレートを見て瞬時に「10」を作れるのは、私の脳内に高性能なCPUがあるわけではなく、「解が存在することは数学的に保証されている」という安心感が、直感を支えているからなのかもしれません。
次に前の車のナンバーが「異なる4つの数字」だったら、安心して挑んでみてください。必ず答えはそこにありますから。