GTK+ で落書き 15(Ruby)

GTK+ で落書きです。よくあるやつですね。
20180630162129
 
Ruby の自作の Gem 'oekaki' のタートルグラフィックスの機能を使っています。
oekaki | RubyGems.org | your community gem host
Gem 'oekaki' にタートルグラフィックスを追加 - Camera Obscura
 
コード。
oekaki_sample19.rb

require 'oekaki'

Oekaki.app width: 500, height: 500 do
  draw do
    clear(color(0x1694, 0x3447, 0x8d60))
    
    t = Oekaki::Turtle.new
    t.color(0xfe5f, 0xaa9a, 0x212a)
    ln = 10
    
    60.times do
      4.times do
        t.forward(ln)
        t.left(90)
      end
      t.left(6)
      ln += 4
    end
  end
end

 

関係ないですが、Ruby から flip-flop の実装がなくなる方向らしいですね。確かに使ったことがないけれど、おもしろいから別になくさなくてもいいのに。まつもとさん、Ruby の人気が落ちてきているのが、機能が多すぎる(?)せいだと思っておられるらしい。弱気になっておられますね。自分は Ruby、書きやす過ぎて抵抗感がないくらいに感じるのに。もっと面倒な言語でもやるかなとは思ったりするけれど。Ruby すばらしいですよ。流行じゃなくなっているだけです。敢て言えば、Ruby っていうと Rails ばかりなのがつまらない。RubyRails だけじゃないです。なのに Rails の記事ばかりなのが Ruby の弱点。

Python は C とか C++ のライブラリが出るとすぐに Python で使えるようになるのがすごいですね。それで Ruby と圧倒的な差がついた。いまや量子コンピュータPython から使えるくらい(すごい!)。でも自分は Ruby が好きですね。


※追記
アニメーション版も作ってみました。最初は Fiber がぴったりだと思ったのですが、「fiber called across stack rewinding barrier」というエラーが出てダメでした。よくわかりません。なので素直に Enumerator で実装しました。コードはこちら

Linux でディレクトリのバックアップ

rsync コマンドで簡単にディレクトリのバックアップが取れます。

.bashrc に以下の alias を定義しました。home フォルダをバックアップします。

alias backup_mint='sudo rsync -au --delete /home/TA/ "/media/TA/Transcend/Linux Mint 18 backup/TA"'

TA のところには自分のユーザー名が入っています。

実行例。

$ time backup_mint
[sudo] TA のパスワード: 

real	1m43.144s
user	0m3.268s
sys	0m25.652s

GTK+ で電卓(Ruby)

20180619021620
GUI で電卓なんて簡単にできるだろうと思ったら大間違いでした。

Ruby コード。
dentaku.rb

require 'gtk2'

class Calculator
  def initialize(entry)
    @entry = entry
    clear
  end
  
  def input(n)
    a = ["AC", "C" , "", "**", 7, 8, 9, "+", 4, 5, 6,
         "-", 1, 2, 3, "*", 0, ".", "=", "/"][n]
    parse(a)
  end
  
  def show(st)
    @entry.set_text(st)
  end
  
  def clear
    @buffer = []
    @left = ""
    @operator = nil
    show("")
  end
  
  def parse(c)
    case c
    when "AC"
      clear
    when "C"
      return if @buffer.empty?
      @buffer = []
      show("")
    when 0..9, "."
      if /^0/.match(number) and !/^0\./.match(number)
        @buffer = @buffer.drop_while {|x| x == 0}
      end
      if @buffer.empty? and c == "."
        @buffer = [0, "."]
      elsif @buffer.count(".").zero? or c != "."
        @buffer << c
      end
      show(number)
    when "*", "/", "+", "-", "**", "="
      right = eval(number).to_s
      return if right.empty? and !@operator and @left.empty?    #最初であるとき
      @buffer = []
      if @operator and !right.empty?
        begin
          right = eval(@left + @operator + right).to_s
          raise ZeroDivisionError if right == "Infinity"
          show(right)
        rescue ZeroDivisionError
          clear
          show("ZeroDivisionError")
          return
        end
      end
      @left = right unless right.empty?
      @operator = (c == "=") ? nil : c
    when ""
      st = eval(number).to_s
      @buffer = []
      if !st.empty?
        @left = eval("Math.sqrt(#{st})").to_s
      elsif !@left.empty?
        @left = eval("Math.sqrt(#{@left})").to_s
      else
        return
      end
      show(@left)
    else
      raise "error"
    end
  end
  
  def number
    @buffer.join
  end
end

class MainWindow < Gtk::Window
  def initialize
    super("電卓")
    set_resizable(false)
    
    entry  = Gtk::Entry.new
    entry.set_editable(false)
    entry.set_xalign(1)    #右詰め
    entry.set_height_request(40)
    entry.modify_font(Pango::FontDescription.new("12"))    #フォントのサイズ
    
    @calc = Calculator.new(entry)
    
    box = Gtk::VBox.new
    add(box)
    box.pack_start(entry, false, true, 0)
    box.pack_start(buttons_area, true, true, 0)
    
    signal_connect("destroy") {Gtk.main_quit}
    show_all
  end
  
  def buttons_area
    characters = %W(AC C √ ^ 7 8 9 + 4 5 6 - 1 2 3 × 0 . = ÷)
    hboxes = Array.new(5) {Gtk::HBox.new}
    i = 0
    area = Gtk::VBox.new
    hboxes.each do |box|
      4.times do
        b = Gtk::Button.new
        b.set_size_request(50, 40)
        l = Gtk::Label.new(characters[i])
        l.modify_font(Pango::FontDescription.new("12"))
        b.add(l)
        n = i
        b.signal_connect("clicked") {@calc.input(n)}
        box.pack_start(b, true, true, 0)
        i += 1
      end
      area.pack_start(box, true, true, 0)
    end
    area
  end
end

MainWindow.new
Gtk.main

GUI の部分は簡単でしたが、電卓のボタン処理をする部分(parse()メソッド)がぐちゃぐちゃになってしまいました。明らかにクソコードですね(^^;


ぐぐってみると普通の電卓を GUI でエミュレートしたような実装例はなかなかないですね。少なくとも Ruby では見つけられませんでした。Ruby でいうと簡易 irb みたいなのを実装する話が多いです。

騎士の巡歴、あるいはナイト・ツアー(Ruby)

20180616015832騎士の巡歴」とは、チェスの「騎士(ナイト)」の駒を使って、チェス盤のマスを一度づつ移動していき、最終的にすべてのマスを訪れるというパズルです。「騎士」の駒は将棋の「桂馬」とよく似た動きをしますが、すべての方向に飛べるだけ桂馬とちがいます。


Ruby で解いてみました。しかし、実際のチェス盤の大きさである 8×8 では、盤が大きすぎて解を求めることができませんでした。以下のコードでは(左上隅を出発点とする)すべての解を出力しますが、6×5 の盤面(解は4542通り)くらいが限界のようです。実行例。

$ time ruby tour_of_knight.rb

(省略)
-----------------------
No. 4540
01 04 21 08 15 06 
22 13 02 05 26 09 
03 20 27 14 07 16 
12 23 18 29 10 25 
19 28 11 24 17 30 
-----------------------
No. 4541
01 04 21 08 15 06 
26 09 02 05 22 13 
03 20 27 14 07 16 
10 25 18 29 12 23 
19 28 11 24 17 30 
-----------------------
No. 4542
01 04 21 08 15 06 
22 09 02 05 26 13 
03 20 27 14 07 16 
10 23 18 29 12 25 
19 28 11 24 17 30 

real	2m32.799s
user	2m32.652s
sys	0m0.112s

 
Ruby コード。
tour_of_knight.rb

X, Y = 6, 5

jump = [[-1, 2], [1, 2], [-1, -2], [1, -2], [2, -1], [2, 1], [-2, -1], [-2, 1]]
a = Array.new(X + 4, -1)
board = [a] * 2 + Array.new(Y) {[-1] * 2 + [0] * X + [-1] * 2} + [a] * 2
co = 1

try = ->(x, y, n) {
  if n == X * Y
    puts "-----------------------"
    puts "No. #{co}"
    board[2, Y].each {|a| puts a[2, X].map{|b| "%02d " % b}.join}
    co += 1
  else
    jump.each do |right, down|
      x1, y1 = x + right, y + down
      next if board[y1][x1].nonzero?
      board[y1][x1] = n + 1
      try.(x1, y1, n + 1)
      board[y1][x1] = 0
    end
  end
}

board[2][2] = 1
try.(2, 2, 1)

ふつうのバックトラック法で求めています。変数 jump には「騎士」の相対的な移動可能方向が入っています。変数 board は盤面です。周囲には番兵(-1)が置いてあって盤外かどうかをチェックしています。


なおここによると、「騎士の巡歴」を解くのに「ワーンスドロフの規則」というのがあって、このアルゴリズムを使うととても高速に解けるらしいのだが、その規則によって確実に解けるということはまだ証明されていないらしい。検索してみても「ワーンスドロフの規則」を使ったものは多いが、証明がないということでここでは採用しなかった。また、自分で考えたアルゴリズムでもないしね。

宣教師と人喰い人(Ruby)

次の有名な問題があります。

問題:
宣教師 3人と人喰い人 3人が、船で川を渡ろうとしています。船は 2人まで乗れますが、最低 1人いなければ動かせません。ここで、こちらの岸もあちらの岸も、また船の上でも、宣教師の数が人喰い人の数を下回ると喰われてしまうので、そのような仕方は許されません。

さて、6人全員があちらの岸へ渡ることは可能でしょうか?

 
Ruby で解いてみました。
missionaries_and_cannibals1.rb

M, C = 3, 3
Boat = 2

possiblity = ->(m, c) {
  result = []
  Boat.downto(1) do |n|    #nは舟に乗る人数
    0.upto(n) do |i|       #iは舟に乗る人喰い人の人数
      boat_m, boat_c = n - i, i
      next if boat_m < boat_c and boat_m.nonzero?
      here_m = m - boat_m
      here_c = c - boat_c
      there_m = M - here_m
      there_c = C - here_c
      next if here_m < 0 or here_c < 0
      next if here_m < here_c and here_m.nonzero?
      next if there_m < there_c and there_m.nonzero?
      result << [boat_m, boat_c]
    end
  end
  result
}

try = ->(m, c, turn, procedure, memo) {
  possiblity.(m, c).each do |x|
    next_m, next_c = M - m + x[0], C - c + x[1]
    next if x == procedure[-1]    #同じ構成で引き返すのは無意味
    if next_m == M and next_c == C and turn.zero?
      p procedure + [x]
    else
      nxt = turn.zero? ? [M - next_m, C - next_c, turn] : [next_m, next_c, turn]
      next if memo.include?(nxt)    #調べたパターンが既にあるか?
      try.(next_m, next_c, 1 - turn, procedure + [x], memo + [nxt])
    end
  end
}

try.(M, C, 0, [], [[M, C, 1]])

深さ優先探索(DFS)ですべての解を求めています。mc はそれぞれ岸にいる宣教師、人喰い人の人数です。next_mnext_c はその反対岸の人数が次にどうなるかを表しています。turn は0なら行き、1なら帰りです。possibility.() は岸の状態から舟に乗れる人数のすべての組み合わせを返します。procedure には舟の状態が入っていって、全員が川を渡れれば答えになります。nxt は「こちら岸」の次の状態です。調べたパターンは memo に入れておいて、重複しないようにします。また、船の同じ構成での往復は無意味なので、これも行わないようにします。
 
結果は次のようです。

[[1, 1], [1, 0], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [1, 0], [1, 1]]
[[1, 1], [1, 0], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [0, 1], [0, 2]]
[[0, 2], [0, 1], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [1, 0], [1, 1]]
[[0, 2], [0, 1], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [0, 1], [0, 2]]

解は 4とおりありますが、ほぼ同じでちがいは些細なものです。それぞれの手続きで、中の配列 [a, b] は a が(船に乗る)宣教師の数、b が(船に乗る)人喰い人の数を表しています。いちばん上の解を表にしてみましょう。
 

移動 あちらの岸 こちらの岸
[0, 0] [3, 3]
←[1, 1] [1, 1] [2, 2]
→[1, 0] [0, 1] [3, 2]
←[0, 2] [0, 3] [3, 0]
→[0, 1] [0, 2] [3, 1]
←[2, 0] [2, 2] [1, 1]
→[1, 1] [1, 1] [2, 2]
←[2, 0] [3, 1] [0, 2]
→[0, 1] [3, 0] [0, 3]
←[0, 2] [3, 2] [0, 1]
→[1, 0] [2, 2] [1, 1]
←[1, 1] [3, 3] [0, 0]

確かに 6人全員があちらの岸へ渡ることができています。

宣教師がひとりもいない場合は(当然ながら)喰べられないというのを上手く使わないといけないわけですね。

フーリエ級数による簡単な温度分布(Ruby)

下部が高温熱源、両端が低温熱源の際の平衡状態の温度分布を図示してみました。微分方程式
  
を、境界条件
  
  
  
で解くと、
  
となります。

これを図示すると
20180529011622
という感じになります。


Ruby コード。表示には自作の Gem 'oekaki' を使っています。
oekaki_sample18.rb

require 'oekaki'

u = ->(x, y, n) {
  a = 0
  n.times do |i|
    j = (2 * i + 1).to_f
    a += (-1) ** i / j * exp(-j * y) * cos(j * x)
  end
  4 * a / PI
}

Width, Height = 300, 350
step_x, step_y = PI / Width, 2.5 / Height

Oekaki.app width: 400, height: 400 do
  draw do
    clear
    (-Width / 2).upto(Width / 2) do |x|
      0.upto(Height) do |y|
        t = u.(x * step_x, y * step_y, 50)
        color(t * 0xffff, 0, 0)
        point(x + 200, Height - y)
      end
    end
    color(0xffff, 0xffff, 0xffff)
    line(0, Height + 1, 400, Height + 1)
  end
end

クソコードを書きたい

このところ全然コードを書いていないのですが、プログラミングに関心がなくなったわけではありません。というか、しばらくは AIZU ONLINE JUDGE にかなり熱中していました。

 
でも、このところちょっとコーディングする気がないですね。というか、どうもある種の「クソコード」を書かないといけないような気がしています。ただ、どんな種類の、どんな感じの「クソコード」を書かないといけないか、それがわからないですね。いままで書いてきたのも「クソコード」なのですが、別種のですね。や、一時の気の迷いかな。よくわかりません。

例えば、こんな無意味なプログラムとか。Ruby です。

loop.with_index(1) do |_, i|
  print "#{i} "
  sleep(1)
end

だから何、ですよね。
 
あと、必要以上に複雑なコードとか。ちょっと思いつかないけれど(笑)。とにかく、下らないコードですね。でも、無意味なプログラムって、それはそれであまり思いつかない。リファレンス・マニュアルを見て、使ったことのないメソッドをテキトーに使ってみるとか。いや、それはふつう? Ruby で Thread とかあんまり使ったことがないから、やってみるか。

いわゆる「写経」とか、これも無意味っぽいからやってみるか。あんまり目的がない方がいいのだが。

以上、無意味なエントリでした。