ルモーリン

Perlサンプル20 数学パズルを解く

投稿:2020-02-12、更新:2020-02-23

2020-02-23 再帰バージョンを書きましたのでご覧ください。 Perlサンプル21 数学パズルを解く~改~

こちらのツイートを拝見して挑戦しました。

#!/usr/bin/env perl

use utf8;
use strict;
use warnings;
use feature qw/ say /;

use DateTime;
use Encode::Locale;

binmode STDIN, ":encoding(console_in)";
binmode STDOUT, ":encoding(console_out)";

$| = 1;

say DateTime->now(time_zone => "local")->strftime("開始 %T");

my %result;

# 元の式は
# □/□□ + □/□□ + □/□□ = 1
# □の中に入る数字をa~iとする
# a / bc + d / ef + g / hi = 1
# bc ef hi はそれぞれ2桁の数字
# 通分、変形して除算を排除(計算誤差をなくす)
# a * ef * hi / bc * ef * hi + d * bc * hi / ef * bc * hi + g * bc * ef / hi * bc * ef = 1
# a * ef * hi / bc * ef * hi + d * bc * hi / bc * ef * hi + g * bc * ef / bc * ef * hi = 1
# (a * ef * hi + d * bc * hi + g * bc * ef) / bc * ef * hi = 1
# a * ef * hi + d * bc * hi + g * bc * ef = bc * ef * hi

my @a = 1 .. 9; # 変数aの候補
for my $a (@a) { # aの候補を1つずつ試す
	my @b = grep {$a != $_} @a; # aで試す候補以外をbの候補にする
	for my $b (@b) { # bの候補を1つずつ試す
		my @c = grep {$b != $_} @b; # bで試す候補以外をcの候補にする
		for my $c (@c) {
			my @d = grep {$c != $_} @c;
			for my $d (@d) {
				my @e = grep {$d != $_} @d;
				for my $e (@e) {
					my @f = grep {$e != $_} @e;
					for my $f (@f) {
						my @g = grep {$f != $_} @f;
						for my $g (@g) {
							my @h = grep {$g != $_} @g;
							for my $h (@h) {
								my @i = grep {$h != $_} @h;
								for my $i (@i) {
									# 2桁の数字
									my $ef = 10 * $e + $f;
									my $hi = 10 * $h + $i;
									my $bc = 10 * $b + $c;
									# 成立する場合
									if ($a * $ef * $hi + $d * $bc * $hi + $g * $bc * $ef == $bc * $ef * $hi) {
										# 3つの項は可換なので昇順にする
										my @simplex = sort "$a / $bc", "$d / $ef", "$g / $hi";
										# 解をキーにして重複解を整理
										$result{"$simplex[0] + $simplex[1] + $simplex[2] = 1"} = 1;
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

# 解を表示
say for sort keys %result;
say DateTime->now(time_zone => "local")->strftime("終了 %T");
開始 18:08:23
5 / 34 + 7 / 68 + 9 / 12 = 1
終了 18:08:25

変数a~iの9重ループを力まかせに回します。 変数が進むa→iにつれて選択できる数字が減りますからgrepで除外して次の変数へ渡すようにしました。 ですので変数iのループは実は候補が1個しかありません。 一貫性を持たせるほうを優先し同じ書き方にしました。 重複解は、実際に動作させてみて初めて気付きました。 答をハッシュのキーにすると、同一キーは上書きされて1つ残るので重複を取り除けます。 解を表示する行ですが、
say for sort keys %result;
これはハッシュの%resultからキーを取り出して文字列比較で昇順に並べ替えたものを1つずつsayします。 ほぐして書くと次のようになりますが5行に膨れあがりますし不必要な変数@result_keys、@result_sort、$itemもあり読みづらいです。

my @result_keys = keys(%result);
my @result_sort = sort(@result_keys);
for my $item (@result_sort) {
	say $item;
}