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
    hboxes.map do |box|
      4.times do
        b = Gtk::Button.new(characters[i])
        b.set_size_request(50, 40)
        n = i
        b.signal_connect("clicked") {@calc.input(n)}
        box.pack_start(b, true, true, 0)
        i += 1
      end
    end
    area = Gtk::VBox.new
    hboxes.each {|box| area.pack_start(box, true, true, 0)}
    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|
    0.upto(n) do |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]])

深さ優先探索ですべての解を求めています。調べたパターンは 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 とかあんまり使ったことがないから、やってみるか。

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

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

ズンドコキヨシ(Ruby)


 
やってみた。Ruby コード。

z, d = zd = ["ズン", "ドコ"]
line = []
until line == [z, z, z, z, d]
  line << zd[rand(2)]
  line = line.last(5)
  print line.last
  sleep(1)
end
puts "キ・ヨ・シ!"

意外と出ないものですね。あ、これ、関数ではないか。

※参考
ズンドコキヨシまとめ - Qiita
 

追記

ここにこういうコードがあった。

def zundoko
  z, d = zd = ['ズン', 'ドコ']

  loop.lazy.map { zd.sample }.each_cons(5).with_index do |nominees, i|
    i.zero? ? nominees.each(&method(:print)) : print(nominees.last)
    break if nominees == [z, z, z, z, d]
  end

  print 'キ・ヨ・シ!'
end

zundoko

プロの優れたプログラマがネタとして書いたコードなのでマジレスするわけではないが、これは Ruby らしい「悪しきコード」だと思う。凝りすぎ。出力のためだけに with_index三項演算子を使ったり、each {|x| print x} の代わりに(ブロック変数を省略するために ) each(&method(:print)) と書いたり。Ruby を使っているとついこういうコードを書きたくなってしまうけれど。まあ初心者の冗談レスですが。

もしこれが「悪いコード」だとするならば、ここで Enumerator と each_cons(5) を使おうと決めた選択がよくなかったということになると思う。

選択ソート(C言語、Go言語)

選択ソートは決められた範囲の中から最小値(もしくは最大値)を選んでいくという、わかりやすいソートです。

C言語版。
selection_sort.c

#include <stdio.h>

void selection_sort(int ar[], int len) {
    int i, j, min, tmp;
    
    for (i = 0; i < len - 1; i++) {
        min = i;
        for (j = i + 1; j < len; j++) {
            if (ar[j] < ar[min]) min = j;
        }
        tmp = ar[i]; ar[i] = ar[min]; ar[min] = tmp;
    }
}

int main () {
    int ar[] = {3, 1, 5, 2, 9, 7, 0};
    int len = sizeof ar / sizeof(int);
    
    selection_sort(ar, len);
    
    printf("[");
    for (int i = 0; i < len; i++) {
        printf("%d, ", ar[i]);
    }
    printf("\b\b]\n");
    
    return 0;
}

 
Go言語版。C言語版と変わりません。
selection_sort.go

package main
import "fmt"

func selection_sort(ar []int) []int {
    for i := 0; i < len(ar) - 1; i++ {
        min := i
        for j := i + 1; j < len(ar); j++ {
            if ar[j] < ar[min] { min = j }
            ar[min], ar[i] = ar[i], ar[min]
        }
    }
    return ar
}

func main() {
    ar := selection_sort([]int{3, 1, 5, 2, 9, 7, 0})
    fmt.Println(ar)
}

 

おまけ。Ruby 版。
selection_sort.rb

class Array
  def selection_sort
    l = size
    (l - 1).times do |i|
      min = i
      (i + 1).upto(l - 1) {|j| min = j if self[j] < self[min]}
      self[min], self[i] = self[i], self[min]
    end
    self
  end
end