Last active
January 11, 2020 01:28
-
-
Save takehiko/205a718717bf9a70d439347ec03c5ec8 to your computer and use it in GitHub Desktop.
Four-8 Puzzle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# foureights.rb : "8-((8+8)/8)" Puzzle | |
# by takehikom | |
# see also: https://takehikom.hateblo.jp/entry/2020/01/11/062120 | |
# 環境変数LANGの値がja_JP.UTF-8などのときは, | |
# 日本語文字で出力する(実行時オプションで変更可能) | |
$option_zenkaku = /jp/i === ENV["LANG"] | |
class String | |
TO_J_PATTERN1 = "-+*/()=0123456789" | |
TO_J_PATTERN2 = "-+×÷()=0123456789" | |
TO_J_PATTERN3 = "-+×/()=0123456789" | |
def to_j(option_slash = false) | |
return self if !$option_zenkaku | |
tr(TO_J_PATTERN1, option_slash ? TO_J_PATTERN3 : TO_J_PATTERN2) | |
end | |
end | |
class FourNumberPuzzleSolver | |
def initialize(opt) | |
@num = opt[:num] || [8, 8, 8, 8] | |
raise if !(Array === @num) || @num.length < 4 | |
@num = @num[0, 4].map { |item| item.to_i } | |
@result_only = opt[:res] | |
@result_h = {} # "number" => [expr, ...] | |
end | |
attr_reader :num, :result_h | |
def start | |
setup_result(!@result_only) | |
if !@result_only | |
puts; puts "=" * 64; puts | |
print_result_all | |
puts; puts "=" * 64; puts | |
end | |
print_result((0..10).to_a) | |
end | |
def op_sym(o) | |
["+", "-", "*", "/"][o] | |
end | |
def op(o, x, y) | |
return nil if x.nil? || y.nil? | |
case o | |
when 0 | |
x + y | |
when 1 | |
x - y | |
when 2 | |
x * y | |
when 3 | |
if y == 0 | |
return nil | |
end | |
Rational(x, y) | |
end | |
end | |
def expr(v1, v2, v3, v4, o1, o2, o3, d = 0, opt_j = $option_zenkaku) | |
case d | |
when 0 | |
# d == 0: ((8 o1 8) o2 8) o3 8 | |
pat = "((%d%s%d)%s%d)%s%d" | |
when 1 | |
# d == 1: (8 o1 8) o2 (8 o3 8) | |
pat = "(%d%s%d)%s(%d%s%d)" | |
when 2 | |
# d == 2: (8 o1 (8 o2 8)) o3 8 | |
pat = "(%d%s(%d%s%d))%s%d" | |
when 3 | |
# d == 3: 8 o1 ((8 o2 8) o3 8) | |
pat = "%d%s((%d%s%d)%s%d)" | |
when 4 | |
# d == 4: 8 o1 (8 o2 (8 o3 8)) | |
pat = "%d%s(%d%s(%d%s%d))" | |
else | |
raise | |
end | |
pat % [v1, op_sym(o1), v2, op_sym(o2), v3, op_sym(o3), v4] | |
end | |
def eval(v1, v2, v3, v4, o1, o2, o3, d = 0) | |
case d | |
when 0 | |
# d == 0: ((8 o1 8) o2 8) o3 8 | |
v = op(o3, op(o2, op(o1, v1, v2), v3), v4) | |
when 1 | |
# d == 1: (8 o1 8) o2 (8 o3 8) | |
v = op(o2, op(o1, v1, v2), op(o3, v3, v4)) | |
when 2 | |
# d == 2: (8 o1 (8 o2 8)) o3 8 | |
v = op(o3, op(o1, v1, op(o2, v2, v3)), v4) | |
when 3 | |
# d == 3: 8 o1 ((8 o2 8) o3 8) | |
v = op(o1, v1, op(o3, op(o2, v2, v3), v4)) | |
when 4 | |
# d == 4: 8 o1 (8 o2 (8 o3 8)) | |
v = op(o1, v1, op(o2, v2, op(o3, v3, v4))) | |
else | |
raise | |
end | |
(Rational === v && v.denominator == 1) ? v.numerator : v | |
end | |
def setup_result(option_print = false) | |
(0..4).each do |d| | |
(0..3).each do |o1| | |
(0..3).each do |o2| | |
(0..3).each do |o3| | |
s = expr(*@num, o1, o2, o3, d) | |
v = eval(*@num, o1, o2, o3, d) | |
vs = v.to_s | |
vs = "NaN" if v.nil? | |
puts [s.to_j, "=".to_j, vs.to_j(true)].join if option_print | |
if !@result_h.key?(vs) | |
@result_h[vs] = [] | |
end | |
@result_h[vs] << s | |
end | |
end | |
end | |
end | |
end | |
def print_result(output_numbers) | |
output_numbers.each do |v| | |
vs = v.to_s | |
if result_h.key?(vs) | |
print_freq(vs, result_h[vs].length) | |
#puts "#{vs} ... #{result_h[vs].length}#{$option_zenkaku ? '通り' : ' expression(s)'}" | |
puts result_h[vs].map { |s| " " + s.to_j } | |
else | |
print_freq(vs, 0) | |
#puts "#{vs} ... #{$option_zenkaku ? 'なし' : 'nothing'}" | |
end | |
end | |
end | |
def print_result_all | |
@result_h.keys.sort.each do |vs| | |
print_freq(vs, result_h[vs].length) | |
#puts "#{vs} ... #{result_h[vs].length}#{$option_zenkaku ? '通り' : ' expression(s)'}" | |
puts @result_h[vs].map { |s| " " + s.to_j } | |
end | |
end | |
def print_freq(vs, freq) | |
if freq == 0 | |
puts "#{vs} ... #{$option_zenkaku ? 'なし' : 'nothing'}" | |
else | |
puts "#{vs} ... #{freq}#{$option_zenkaku ? '通り' : ' expression(s)'}" | |
end | |
end | |
end | |
if __FILE__ == $0 | |
opt = {} | |
while /^-/ =~ ARGV.first | |
case ARGV.shift | |
when /^-R/ | |
opt[:res] = true | |
when /^-j/ | |
$option_zenkaku = true | |
when /^-J/ | |
$option_zenkaku = false | |
when /-h/ # -h or --help | |
puts "Usage: #{$0} [-j][-J][-R] number..." | |
exit | |
end | |
end | |
opt[:num] = ARGV.dup if ARGV.length >= 4 | |
FourNumberPuzzleSolver.new(opt).start | |
end | |
__END__ | |
# ruby foureights.rb -J -R | |
0 ... 44 expression(s) | |
((8+8)-8)-8 | |
((8-8)+8)-8 | |
((8-8)-8)+8 | |
((8-8)*8)*8 | |
(snip) | |
1 ... 32 expression(s) | |
((8+8)-8)/8 | |
((8-8)+8)/8 | |
((8*8)/8)/8 | |
((8/8)+8)-8 | |
(snip) | |
2 ... 1 expression(s) | |
(8/8)+(8/8) | |
3 ... 2 expression(s) | |
((8+8)+8)/8 | |
(8+(8+8))/8 | |
4 ... 4 expression(s) | |
(8*8)/(8+8) | |
(8/(8+8))*8 | |
8/((8+8)/8) | |
8*(8/(8+8)) | |
5 ... nothing | |
6 ... 1 expression(s) | |
8-((8+8)/8) | |
7 ... 1 expression(s) | |
((8*8)-8)/8 | |
8 ... 9 expression(s) | |
((8-8)*8)+8 | |
((8-8)/8)+8 | |
(8*(8-8))+8 | |
8+((8-8)*8) | |
(snip) | |
9 ... 2 expression(s) | |
((8*8)+8)/8 | |
(8+(8*8))/8 | |
10 ... 2 expression(s) | |
((8+8)/8)+8 | |
8+((8+8)/8) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment