4つの数で 10 を作る(Ruby)

テンパズル - Wikipedia
1桁の4つの数と四則演算で、10 を作るコードを Ruby で書いてみました。括弧は使ってもよいことにします。

実行例。

$ ruby make_ten.rb
[2, 7, 3, 9] で 10 を作る
(2 + 3) * (9 - 7)
(7 + 9) - (2 * 3)
9 + (7 - (2 * 3))
9 - ((2 * 3) - 7)
7 + (9 - (2 * 3))
7 - ((2 * 3) - 9)
(7 * 3) - (2 + 9)
2 * (9 - (7 - 3))
2 * (9 + (3 - 7))
((7 * 3) - 2) - 9
((7 * 3) - 9) - 2
2 * (3 - (7 - 9))
(9 - 7) * (2 + 3)
2 * (3 + (9 - 7))
2 * ((3 + 9) - 7)
7 - ((3 - 9) / 2)
7 + ((9 - 3) / 2)
((3 * 9) - 7) / 2
$ ruby make_ten.rb 1185
[1, 1, 8, 5] で 10 を作る
8 / (1 - (1 / 5))

括弧を使っているので事実上は同じ演算が重複して出力されてしまいますが、そこはお許しを。
4 / (1 - (3 / 5)) のように分数を使ったものも解けます。

コード。
make_ten.rb

class Integer
  def /(a) Rational(self, a) end
end

def solve(ary)
  if ary.size <= 1
    @ans << ary[0][1..-2] if eval(ary[0]) == 10
  else
    idxs = [*0...ary.size]
    idxs.combination(2) do |i, j|
      a, b = ary[i], ary[j]
      nxt = (idxs - [i, j]).map{|x| ary[x]}
      nums = ["(#{a} + #{b})", "(#{a} - #{b})", "(#{b} - #{a})", "(#{a} * #{b})"]
      nums << "(#{a} / #{b})" if eval(b).nonzero?
      nums << "(#{b} / #{a})" if eval(a).nonzero?
      nums.each {|n| solve(nxt + [n])}
    end
  end
end

@ans = []
given = ARGV[0] ? ARGV[0].chars : Array.new(4) {[*0..9].sample.to_s}
puts given.map(&:to_i).inspect + " で 10 を作る"
solve(given)
puts @ans.uniq

メソッド solve() は solve(["2", "7", "3", "9"]) のように各数字を String にして呼ぶのがミソです。eval の活躍しどころなのです。分数を使った場合に対応するため、演算子/を再定義*1しています(Ruby!)。

例えば 9999 とか 3478 とか、むずかしいのがあるそうですよ。わからなかったら上のプログラムに解かせてみて下さいな。


実際には数字は 1桁でなくともよいですし、4つでなくとも構いません。

*1:Fixnum や Bignum のなくなった Ruby 2.4 以降でしかうまく働きません。それ以前のバージョンでは、再定義を Fixnum, Bignum でおこなう必要があります。

Ruby で ANSIエスケープシーケンスを扱う

Ruby を使っているとき、Linux でのターミナル表示の処理の仕方をすぐ忘れてしまうので、簡単なライブラリを作った。


20181212173044
以下はこんな画面を作るコード。

require_relative 'es'

res = ES.reset

print ES.clear
puts ES.color(:style, :blink) + ES.color(:green, :bright) + "blink" + res
puts ES.color(:style, :underline) + ES.color(:blue, :background) + "undlerline"
print ES.down

print res
16.times do |i|
  puts (0...16).map {|j| ES.color(:ex_b, n = 16 * i + j) + "%02X" % n + res + " "}.join
end

print res + ES.push
print ES.cursor_r(5, 2)
print ES.color(:cyan) + ES.color(:style, :reverse) + "cursor move" + ES.pop

凡例。

  • ES.color(:blue) で文字色を青にする。背景は ES.color(:red, :backgroud) という風に指定する。
  • 拡張文字色(256色)は ES.color(:ex_c, n)、拡張背景色は ES.color(:ex_b, n)
  • 文字の点滅、下線、反転、太字は ES.color(:style, :blink), ES.color(:style, :underline), ES.color(:style, :reverse), ES.color(:style, :bold) のように。
  • 指定を解除するには ES.reset とする。

 

  • 画面クリアは ES.clear、行頭へのカーソル移動は ES.top
  • カーソルの一行上、一行下はそれぞれ ES.up, ES.down
  • カーソルの絶対位置の指定は ES.cursor(x, y)(x, y は自然数)、相対的な移動は ES.cursor_r(x, y)(x, y は 0 や負数も許す)。
  • 状態の保存は ES.push、復帰は ES.pop

その他、ここのサイトの記述を取り入れております。

ライブラリのコード

es.rb

require 'io/console'

module ES
  S = "\e["
  Col = %i(black red green yellow blue magenta cyan white ex_c ex_b reset style code)
  Cmd1 = %i(CUU CUD CUF CUB CNL CPL CHA CHT CBT ECH DCH IL DL SU SD REP DSR)
  Do1  = %w(A B C D E F G I Z X P L M S T b n)
  Cmd2 = %i(DECSC DECRC RIS DECALN IND NEL HTS VTS PLD PLU RI DSC SOS ST)
  Do2  = %w(7 8 c #8 D E H J K L M P X /)
  
  def color(col, opt = nil)
    ch = Col.map.with_index {|c, i| [c, i + 30]}.to_h[col]
    case ch
    when 38
      return S + "38;5;#{opt.to_i}m"
    when 39
      return S + "48;5;#{opt.to_i}m"
    when 40
      return S + "39;40m"
    when 41
      n = case opt
          when :bold           then 1
          when :italic         then 3
          when :blink          then 5
          when :reverse        then 7
          when :blink_stop     then 25
          when :underline      then 4
          when :bold_stop      then 22
          when :underline_stop then 24
          else opt
          end
      return S + "#{n}m"
    when 42
      return S + "#{opt}m"
    end
    raise "Undefind color name: #{col}" unless ch
    m = case opt
        when :background then        10
        when :bright     then        60
        when :bright_background then 70
        else 0
        end
    S + (ch + m).to_s + "m"
  end
  
  def csi(*args)
    cm = Cmd1.zip(Do1).to_h
    if (a = cm[args[0]])
      S + args[1].to_s + a
    else
      case args[0]
      when :CUP then S + "#{args[1]};#{args[2]}H"
      when :ED  then S + (args[1] ? args[1].to_s : "") + "J"
      when :EL  then S + (args[1] ? args[1].to_s : "") + "K"
      when :TBC then S + (args[1] ? args[1].to_s : "") + "g"
      when :DECSTBM
        S + (args[1] ? "#{args[1]};#{args[2]}" : "") + "r"
      when :DECTCEM
        S + "?25" + args[1]
      else
        raise "#{args[0]} is undefined CSI."
      end
    end
  end
  
  def cmd(given)
    cm = Cmd2.zip(Do2).to_h[given]
    cm ? "\e" + cm : raise("#{given} is undefined command.")
  end
  
  def esc(str)
    "\e" + str
  end
  
  def clear() ES.csi(:ED, 2) + ES.csi(:CUP, 1, 1) end
  def down()  ES.cmd(:NEL) end
  def up()    ES.cmd(:RI) end
  def reset() S + "0m" end
  def top()   ES.csi(:CHA, 1) end
  def home()  ES.cursor(0, 0) end
  def push()  ES.cmd(:DECSC) end
  def pop()   ES.cmd(:DECRC) end
  
  # カーソルの移動
  def cursor(x, y = nil)
    y ? ES.csi(:CUP, y, x) : ES.csi(:CHA, x)
  end
  
  # 相対的なカーソルの移動
  def cursor_r(x, y)
    st = ""
    st += if x > 0
      ES.csi(:CUF, x)
    elsif x < 0
      ES.csi(:CUB, -x)
    else
      ""
    end
    st += if y > 0
      ES.csi(:CUD, y)
    elsif y < 0
      ES.csi(:CUU, -y)
    else
      ""
    end
  end

  def console_size
    [`tput cols`, `tput lines`].map(&:to_i)
  end
  
  # スクロールする行の範囲を指定する(引数がなければ範囲の解除)
  def scroll(rn = nil)
    return case rn
           when Range then ES.csi(:DECSTBM, rn.first, rn.max)
           when 0     then ES.csi(:DECSTBM)
           else ES.push + ES.csi(:DECSTBM) + ES.pop
           end
  end
  
  def cursor_position
    puts "\x1B[6n"
    res = ""
    STDIN.raw do |io|
      until (c = io.getc) == 'R'
        res << c if c
      end
    end
    m = /(\d+);(\d+)/.match(res)
    [m[2], m[1]].map(&:to_i)
  end
  
  # 下にn行開けてカーソルを始めの位置にもってくる
  # (既に下にn行以上開いていたら何もしない)
  def safe_scroll(n)
    return "" if n <= 0
    str = ""
    y = ES.cursor_position[1]
    if (h = ES.console_size[1]) < y + n
      str = ES.scroll_up(n - 1)
      y = h - n
    end
    str + ES.cursor(1, y)
  end
  
  def scroll_up(n)  "\n" * n end
  def clear_below() ES.csi(:ED) end
  
  module_function :color, :csi, :cmd, :clear, :down, :up, :reset, :top,
    :cursor, :cursor_r, :home, :push, :pop, :esc, :console_size, :scroll,
    :scroll_up, :cursor_position, :clear_below, :safe_scroll
end

 

使用例1

20181219202437

これのコードは以下です。
ANSIエスケープシーケンスで遊ぶ · GitHub
 

使用例2

require_relative 'es'

y = ES.console_size[1]
print ES.safe_scroll(1)
print ES.push
print ES.scroll(1..y - 1) + ES.cursor(1, y) + ES.color(:green, :bright) + "FIXED"
print ES.reset + ES.pop
10.times do |i|
  puts i
  sleep(0.1)
end
print ES.scroll + ES.clear_below + ES.reset

 
※参考
ANSIエスケープコード - コンソール制御 - 碧色工房
VT100のエスケープシーケンス - BK class

クロスワードパズルを作成せよ!(Ruby)

アルゴリズム・パズルです。
 

クロスワードを作る場合、空白と黒マスについて次のルールがあります。

  • 黒マスは縦横に連続しない。
  • 黒マスによって盤面が分断されてはいけない。
黒マスルール - Wikipedia

これを「黒マスルール」といいます。
 
さて、縦 5、横 6 のクロスワードパズルを作るとき、空白と黒マスの配置の仕方(盤面)は何とおりあるでしょうか。

 
Ruby で解いてみました。コード。
q67.rb

X, Y = 6, 5
@field = Array.new(L = X * Y, 0)    #盤面。最初はすべて空白
@co = 0

#盤面が分断されていないか?
def check
  area = @field.dup
  set = ->(n) {
    area[n] = 2
    [-1, 1, X, -X].each do |dir|
      next if dir == -1 and (n % X).zero?
      next if dir ==  1 and n % X == X - 1
      next if dir ==  X and n >= L - X
      next if dir == -X and n < X
      m = n + dir
      set.(m) if area[m].zero?
    end
  }
  set.(area.find_index(0))
  !area.find_index(0)
end

def try(i)
  if i >= L
    @co += 1 if check
  else
    #空白をセットしてひとつ先へ
    try(i + 1)
    #可能であれば黒マスをセットしてひとつ先へ
    if ((i % X).zero? or @field[i - 1].zero?) and (i < X or @field[i - X].zero?)
      @field[i] = 1
      try(i + 1)
      @field[i] = 0
    end
  end
end

try(0)
puts @co

結果。

$ time ruby q67.rb
149283

real	0m9.822s
user	0m9.788s
sys	0m0.000s

 
結構むずかしかったですね。盤面の小さい場合で確かめながらコーディングしてようやくできました。
 

浮動小数点演算の謎


Ruby 2.5.1 でやってみたらこうなりました。

a = 0
10000.times {a += 0.01}
puts a    #=>100.00000000001425

笑。

しかし、Ruby には BigDecimal がある。

require 'bigdecimal'

a = BigDecimal("0")
10000.times {a += BigDecimal("0.01")}
puts a.to_s("F")    #=>100.0

きちんと正確に計算できました。
 

追記

Rational を使ってもよいね。

a = 0
10000.times {a += 1/100r}
puts a.to_f    #=>100.0

Ruby/SDL でランダム・ウォーク

20181111000203
Ruby/SDL参照)を使ってランダム・ウォークしてみました。Linux Mint 19、Ruby 2.5.1 で確認しました。

コード。
sdl_random_walk.rb

require_relative 'sdl_draw'

WindowWidth = 300
FieldWidth = 50.0
N = 30

class Agent
  def initialize(ob)
    @x = @y = 0
    @slot = ob
  end
  
  def next_step
    @x += rand(-0.5..0.5)
    @y += rand(-0.5..0.5)
  end
  
  def paint
    ratio = WindowWidth / FieldWidth
    x = WindowWidth / 2 + @x * ratio
    y = WindowWidth / 2 - @y * ratio
    @slot.draw_circle(x, y, 5, @slot.color(255, 0, 0), true, true)
  end
end

draw(WindowWidth, WindowWidth) do
  agents = (1..N).map { Agent.new(self) }
  
  loop do
    fill_rect(0, 0, WindowWidth, WindowWidth, color(0, 0, 0))
    agents.each(&:next_step)
    agents.each(&:paint)
    flip
    sleep(0.05)
  end
end

sdl_draw.rb に関してはここを参照して下さい。簡単なライブラリみたいなものです。

平方根(ルート)を計算して遊ぶライブラリを作った(Ruby)

遊びで平方根(二乗根)を扱うクラス(Root)を Ruby で書いてみました。三乗根とかそれ以上は扱えません(笑)。

コードは下(Gist)にあります。
平方根の計算 · GitHub


オブジェクトの生成。Integer, Rational, Float のルートが扱えます。Root.new(n) または Root.new(a, b) でも、 Root(n) または Root(a, b) でもどちらでも OK です。分数(Rational)のルートを取った場合など、有理化は自動的になされます。Float の場合は to_r してルートを取ります。また、根号の外へ出せる数がある場合は自動的に外へ出します

$ pry
[1] pry(main)> require_relative "root"
=> true
[2] pry(main)> Root.new(3)
=> Root(3)
[3] pry(main)> Root(7, 3)
=> (7)Root(3)
[4] pry(main)> Root(1/3r)
=> (1/3)Root(3)
[5] pry(main)> Root(2.5)
=> (1/2)Root(10)
[6] pry(main)> Root(12)
=> (2)Root(3)
[7] pry(main)> Root(4)
=> (2)Root(1)

根号の中はかならず自然数であり、係数は整数あるいは分数(Rational)です。
 
係数と根号の中身を取り出すことができます。

[4] pry(main)> x = Root(12)
=> (2)Root(3)
[5] pry(main)> x.content
=> 3
[6] pry(main)> x.coefficient
=> 2

 
掛け算(*)、割り算(/)、累乗(**)ができます。マイナスの単項演算子が使えます。

[2] pry(main)> a = Root(3)
=> Root(3)
[3] pry(main)> a * Root(6)
=> (3)Root(2)
[4] pry(main)> a * Root(3)
=> 3
[5] pry(main)> a * Root(1/5r)
=> (1/5)Root(15)
[6] pry(main)> a / Root(6)
=> (1/2)Root(2)
[7] pry(main)> a * 5
=> (5)Root(3)
[8] pry(main)> (2/7r) * a
=> (2/7)Root(3)
[9] pry(main)> a / 1.5
=> (2/3)Root(3)
[10] pry(main)> a ** 2
=> 3
[11] pry(main)> a ** 3
=> (3)Root(3)
[12] pry(main)> 3 / a
=> Root(3)
[13] pry(main)> 2 ** a
=> 3.3219970854839125
[14] pry(main)> a ** 1.5
=> 2.2795070569547775
[15] pry(main)> -a
=> (-1)Root(3)

 
計算できる場合に限り、足し算と引き算ができます。

[2] pry(main)> a = Root(3)
=> Root(3)
[3] pry(main)> a + Root(12)
=> (3)Root(3)
[4] pry(main)> a - Root(3) * 2
=> (-1)Root(3)
[5] pry(main)> 4 + Root(9)
=> 7
[6] pry(main)> 3.0 - Root(4)
=> 1.0
[7] pry(main)> Root(2) + 3.0
=> 4.414213562373095
[8] pry(main)> a + Root(5)
TypeError: Root(5) (Class Root) can not be used at this place.
from /home/***/Documents/Ruby/root.rb:197:in 'error'
[9] pry(main)> 3 - Root(5)
TypeError: (-1)Root(5) (Class Root) can not be used at this place.
from /home/***/Documents/Ruby/root.rb:224:in `error'

 
比較ができます。定義されている演算子は、< > <= >= == <=> です。

[1] pry(main)> Root(5) > Root(3)
=> true
[2] pry(main)> Root(3) > 2
=> false
[3] pry(main)> Root(3) <= 2
=> true
[4] pry(main)> Root(4) == 2
=> true
[5] pry(main)> Root(5) == Root(3)
=> false
[6] pry(main)> Root(3) > 1.7
=> true
[7] pry(main)> 2 < Root(5)
=> true
[8] pry(main)> 3 <=> Root(10)
=> -1

 
なのでソートもできます。

[2] pry(main)> a = [3.0, -1, Root(3), Root(12), Root(1/5r), 6.5]
=> [3.0, -1, Root(3), (2)Root(3), (1/5)Root(5), 6.5]
[3] pry(main)> a.sort
=> [-1, (1/5)Root(5), Root(3), 3.0, (2)Root(3), 6.5]

 
プラスの単項演算子は、オブジェクトが整数なら Integer に変換します。そうでなければ self をそのまま返します。

[2] pry(main)> a = Root(4)
=> (2)Root(1)
[3] pry(main)> +a
=> 2
[4] pry(main)> +Root(5)
=> Root(5)

なお、計算をする場合はこの変換が自動でなされます。

Float への変換は to_f。Integer への変換 to_i は内部で to_f.to_i になります(小数点以下切り捨て)。Integer, Rational, Float からのオブジェクトの生成は to_root

[5] pry(main)> Root(7).to_f
=> 2.6457513110645907
[6] pry(main)> Root(7).to_i
=> 2
[7] pry(main)> 2.to_root
=> (2)Root(1)
[8] pry(main)> 1.5.to_root
=> (3/2)Root(1)

 
計算例。

[7] pry(main)> Root(6) * Root(1/3r) + 8 / Root(2)
=> (5/1)Root(2)
[8] pry(main)> Root(60) / Root(3/10r)
=> (10/1)Root(2)

 
例えばここのほとんどの計算ができます。

[2] pry(main)> Root(180)
=> (6)Root(5)
[3] pry(main)> Root(50) + Root(18)
=> (8)Root(2)
[6] pry(main)> Root(7, 3) - Root(27)
=> (4)Root(3)
[7] pry(main)> Root(5, 2) * Root(3, 2)
=> 30
[8] pry(main)> Root(4, 3) * Root(2, 5)
=> (8)Root(15)
[9] pry(main)> Root(4, 6) / Root(2, 2)
=> (2/1)Root(3)
[10] pry(main)> Root(4, 30) * Root(3, 21) / Root(6, 14)
=> (6/1)Root(5)
[11] pry(main)> 2 / Root(3)
=> (2/3)Root(3)
[12] pry(main)> Root(3) / Root(2, 7)
=> (1/14)Root(21)

 

作っていてなかなか楽しかったです。

31ゲーム(Go言語)

たけしのコマ大数学科」の問題をいろいろ Ruby で解いているときに、こんな問題がありました。

問題:
1から6までのトランプ24枚を使い、2人が交互に1枚ずつ取り、2人の取ったカードの合計を先に31にした方が勝ち、というゲームをする。(31を超えたら負け。)
 
このゲームで先手が勝つためには始めに何を取ればよいか。

(問題の書いてあるサイトはここです。元記事に感謝します。)

「先手が勝つためには始めに何を取ればよいか」とあるように、このゲームは先手必勝です。ちなみにこの問題はここRuby で解いています。

で、実際にこのゲームをコンピュータ相手に対戦するためのプログラムを組んでみました。本当はこれも Ruby で書きたかったのですが、コンピュータの思考時間の関係で Ruby では(自分のスキルでは)無理だったので、Go で書いてみました。ソースコードはここにあります。
31ゲーム · GitHub
 
あなたが先手なので、先手必勝ということはわかっていますから、やり方によっては必ずあなたが勝てます。しかし、コンピュータ側は完璧な対応をしてくるので、ひとつでも手をまちがえれば絶対にコンピュータには勝てません。こんな感じです。

$ go build thirty_one_play.go
$ ./thirty_one_play

**第1手目**
合計: 0
カードを選んで下さい(1~6): [4 4 4 4 4 4]6

**第2手目**
---あなたの負けは確定しています
コンピュータの手は 4 です

**第3手目**
合計: 10
カードを選んで下さい(1~6): [4 4 4 3 4 3]4

**第4手目**
---あなたの負けは確定しています
コンピュータの手は 3 です

**第5手目**
合計: 17
カードを選んで下さい(1~6): [4 4 3 2 4 3]2

**第6手目**
---あなたの負けは確定しています
コンピュータの手は 5 です

**第7手目**
合計: 24
カードを選んで下さい(1~6): [4 3 3 2 3 3]1

**第8手目**
コンピュータの手は 6 です
合計 31 で、あなたの負けです!

こんな風に、あなたが一回でもミスをすると「あなたの負けは確定しています」と出ます。コンピュータのくせに、なまいきですね。なお、カードを選ぶところの例えば「[4 4 4 3 4 3]」というのは、左から順に 1~6 のカードの残りの枚数を表しています(この例の場合だと、4 と 6 のカードがそれぞれ 3 枚づつ残っていて、残りのカードはすべて 4 枚残っているということです。)

じつのところ、なかなか勝てないと思います。是非必勝法を編み出してみて下さい!