RubyGem 'oekaki' でアナログ時計を作ってみた


 
グラフィック機能のデモの定番である時計です。Gem 'oekaki' についてはこちら。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
 
oekaki_sample16.rb

require 'oekaki'

Oekaki.app title: :clock do
  Oekaki::W.move(100, 100)
  
  palegreen = color(0x98 * 256, 0xfb * 256, 0x98 * 256)
  draw {clear(palegreen)}
  
  hand = proc do |r, θ, f = true|
    θ1 = PI * θ / 180
    a = Vector[sin(θ1), cos(θ1)]
    if f
      a1 = Vector[cos(θ1), -sin(θ1)]
      v1 = -a * 6 + a1 * 3
      v2 = -a * 6 - a1 * 3
      ar = []
      ar << [150 + r * a[0], 150 - r * a[1]]
      ar << [150 + v1[0], 150 - v1[1]]
      ar << [150 + v2[0], 150 - v2[1]]
      polygon(true, ar)
    else
      line(150, 150, 150 + r * a[0], 150 - r * a[1])
    end
  end
  
  timer(1000) do
    clear(palegreen)
    
    #日付と曜日の窓
    l, r = 120, 0.8
    color(0x98ff * r, 0xfbff * r, 0x98ff * r)
    rectangle(true, 150 - l / 2, 190, l, 20)
    
    d = Date.today
    color(0xffff, 0xffff, 0xffff)
    week = %w(日 月 火 水 木 金 土)
    text("%02d月%02d日 %s" % [d.month, d.day, week[d.wday]], 150 - 46, 190, 13 * 1000)
    
    #一時間を表す小さな円
    12.times do |i|
      color(0xcd * 256, 0x85 * 256, 0x3f * 256)   #peru
      θ = PI * i * 30 / 180
      circle(true, 150 + 130 * cos(θ), 150 - 130 * sin(θ), 4)
    end
    
    #針
    t = Time.now
    color(65535, 0, 0)
    hand.(100, t.sec * 6, false)    #秒針
    
    color(0, 0, 0)
    hand.(110, t.min * 6)           #長針
    hand.(90, (t.hour % 12 * 60 + t.min) * 0.5)    #短針
  end
end

 
 
※追記(2018/2/16)
日にちと曜日を表示するようにしました。

paiza オンラインハッカソン vol.1 をやってみた

前回のエントリでやったのがおもしろかったので、最初からやってみた。いつもながら Ruby です。
 
結果。
20170929203512
まあ悪くないのだけれど…。

いや、これは苦しみました。最初はソートしてバイナリサーチというアルゴリズムを考えていたのだけれど、バグに悩まされて挫折。もうこの方法は自分の力ではできないと諦めて、バケットソートの応用で解く攻め方に替えました。なんとかそれが上手くいったという次第。実行時間はまあまあというところらしい。コードは全然複雑でないですよね。でも、富豪プログラミング…。配列の大きさが 100万くらいなので、まあ許されるかなあ…。

コード。

n, d = gets.split.map(&:to_i)
price, target = [], []
n.times {price  << gets.to_i}
d.times {target << gets.to_i}

field = Array.new(100_0001, 0)
price.each_with_index do |x, i|
  if field[x].zero?
    field[x] = i
  elsif field[x] > 0
    field[x] = -1
  end
end

target.each do |t|
  min = Float::INFINITY
  price.size.times do |i|
    a = t - price[i]
    co = 0
    loop do
      break if co >= min
      if not field[a - co].zero? and i != field[a - co]
        min = co if min > co
        break
      else
        co += 1
        break if a - co < 0
      end
    end
    break if min.zero?    #高速化のためにあとで加えた(追記参照)
  end
  min = t if min == Float::INFINITY
  puts t - min
end

同じ値段の別商品があるというのが面倒ですよね。それなのに、同じ商品を二つ買ってはいけないという。

あ、これ、課題は通ったけれども、おかしいところがありますね。if文のところは

      if (field[a - co] > 0 and i != field[a - co]) or field[a - co] < 0

であるべきしょうね。たまたま誤った答えが出る条件ではなかったのだな。


※追記
ふと思いついて 1行付け加えたら、だいぶ高速化しました。こちらが以前で、こちらが 1行加えたあとの結果です。


paiza Online Hackathon をやってみた

もうとっくに応募は締め切られているけれど、とにかく Ruby でやってみました。
 
結果。
20170929050602
やったね!

コードです。

need = gets.to_i
company = gets.to_i
number, cost = [], []
company.times {|i| number[i], cost[i] = gets.split.map(&:to_i)}

h = {}
company.times do |i|
  h1 = h.dup
  h.each do |n, c|
    num = n + number[i]
    ct  = c + cost[i]
    h1[num] = ct if not h1[num] or h1[num] > ct
  end
  h1[number[i]] = cost[i]
  h = h1
end
puts h.select {|n, c| n >= need}.min {|a, b| a[1] <=> b[1]}.last

典型的な動的計画法かと思ったので、それでコーディングしました。


※追記(2017/10/18)
コードの可読性を上げました。それにともないパフォーマンスが微妙に落ちたようです(結果)。
それから下から 4行目は正しくは

  h1[number[i]] = cost[i] if not h1[number[i]] or h1[number[i]] > cost[i]

であるべきですね(結果)。コードの可読性を上げたので気がつきました。読みやすく書くというのはやはり大切ということですね。

GTK+ で落書き 13(Ruby)


自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
 

require 'oekaki'

Width, Height = 500, 400
ColorMax = 65536
R = 20

class Star
  def initialize(ob)
    @x, @y = rand(Width), rand(Height)
    @v = Vector[rand - 0.5, rand - 0.5] * (rand + 2) * 4
     = 0
    @f = (rand < 0.5) ? 1 : -1
    @slot = ob
    @color = @slot.color(rand(ColorMax), rand(ColorMax), rand(ColorMax))
  end
  
  def draw
    x1 = @x + R * cos()
    y1 = @y - R * sin()
    @slot.star(true, @x, @y, x1, y1, @color)
    
    @x += @v[0]
    @y += @v[1]
    @x = Width  + R if @x < -R
    @y = Height + R if @y < -R
    @x = -R if @x > Width  + R
    @y = -R if @y > Height + R
    
     += @f * PI * 10 / 180
  end
end


Oekaki.app width: Width, height: Height do
  draw {clear}
  
  stars = []
  timer(50) do
    clear

    stars << Star.new(self) if stars.size <= 40 and rand < 0.1
    stars.each(&:draw)
  end
end

星形を描くメソッド star を使っています。

斜方投射の数値計算と描画(Ruby)

4次のルンゲ−クッタ法の練習としてやってみました。斜方投射(斜め上にものを投げるということです)じゃ全然威力を発揮しないのですが、とりあえず動くかどうかだけ確かめました。描画は gnuplot でやっています。
 

 
runge_kutta_sample1.rb

require 'numo/gnuplot'
require 'matrix'

def runge_kutta(f, dt, x, v, t, m)    #x, v は Vector, f[x, v, t] は Vector を返す
  k1 = v * dt
  l1 = f[x, v, t] * dt / m
  k2 = (v + l1 / 2) * dt
  l2 = f[x + k1 / 2, v + l1 / 2, t + dt / 2] * dt / m
  k3 = (v + l2 / 2) * dt
  l3 = f[x + k2 / 2, v + l2 / 2, t + dt / 2] * dt / m
  k4 = (v + l3) * dt
  l4 = f[x + k3, v + l3, t + dt] * dt / m
  
  nx = x + (k1 + 2 * k2 + 2 * k3 + k4) / 6
  nv = v + (l1 + 2 * l2 + 2 * l3 + l4) / 6
  
  [nx, nv]
end

g = 9.80665
m = 1.0
x = Vector[0.0, 0.0]
v = Vector[1.0, 1.0]
f = lambda {|x, v, t| Vector[0, -m * g]}
dt = 0.001

t = 0
ax, ay = [], []
while x[1] >= 0
  nx, nv = runge_kutta(f, dt, x, v, t, m)
  ax << x[0]
  ay << x[1]
  x, v = nx, nv
  t += dt
end

Numo.gnuplot do
  unset :key
  plot ax, ay, w: :l
end
gets    #終了待ち

初期値はいい加減に設定したのですが、グラフを見ると 5cm くらいの高さまでしか上がらないですね(笑)。初速はだいたい 1.5m/s くらいなのですが、これは時速 5km くらい(おおよそ人の歩く速さ)で、これが小さすぎるのですね。高校生で速い球を投げる人だと時速 100km 以上出るので、それくらいにしてみると(斜めに投げ上げても)高さ 20m くらいまで上がります。また、80m くらい先まで飛びます。なお、物理を知っている人には自明ですが、これらは球の質量には関係しません。重い球だろうが軽い球だろうが関係ないのですね。不思議ですか? このプログラムで実際に質量 m の値を変えてやれば、はっきりとわかります。
 
空気抵抗を入れてみます。はっきりとグラフに出るよう、非常に強く効かせています。力を次の関数で与えます。

f = lambda {|x, v, t| Vector[0, -m * g] - v * 10}

グラフはこんな風になります。なかなか飛ばない感じが出ているでしょうか。

 
RubyVector クラスはじつに手軽ですね。こういうことに使うにはすばらしいと思います。以上は 2次元でやっていますが、このまま 1次元でも 3次元でも動きます。
 

※参考
運動方程式の数値計算法(pdf)

「なぜ関数プログラミングは重要か」解読(Ruby)

 
メモです。
melborne 氏のブログを参考に lambda のみで実装。
関数合成に関してはここを参考にしたが、これではうまくない。多少改変した。
 

cons = ->(a, list) do
  return [a] if !list or list == []
  [a] + list
end.curry

#Ruby にはパターンマッチがないので、パターンマッチには cons1 を使う
cons1 = ->(list) do
  return [list, []] if list.class != Array 
  list.empty? ? [] : [list[0], list[1..-1]]
end

sum0 = ->(list) do
  return 0 if !list or list == []
  num, l = cons1[list]
  num + sum0[l]
end

p sum0[[1, 2, 3, 4, 5]]    #=>15

###

add = ->(x, y) {x + y}

#高階関数 reduce
reduce = ->(f, x, list) do
  return x if list == []
  a, l = cons1[list]
  f[a, reduce[f, x, l]]
end.curry

sum = reduce[add, 0]
p sum[[1, 2, 3, 4, 5]]     #=>15

###

multiply = ->(x, y) {x * y}

product = reduce[multiply, 1]
p product[[3, 7, 10]]     #=>210

#リストの結合
append = ->(list1, list2) {reduce[cons, list2, list1]}.curry
p append[[5, 6], [1, 2]]     #=>[5, 6, 1, 2]

###

double = ->(x) {x * 2}

#関数合成
class Proc
  def *(f)
    ->(*args) do
      ar = []
      a = f.arity.abs
      if a < args.size
        args, ar = args.first(a), args[a..-1]
      elsif a > args.size
        b = f.curry[*args]
        return self.curry * b
      end
      self.curry[f.curry[*args], *ar]
    end.curry
  end
end

#高階関数 map
map = ->(f, list) {reduce[cons * f, nil, list]}.curry

#dlc = ->(num, list) {(cons * double)[num, list]}
#doubleall = reduce[dlc, nil]
#p doubleall[[3, 4, 6]]

doubleall = map[double]
p doubleall[[1, 2, 3]]    #=>[2, 4, 6]

#####
#####
#####

node = cons

n4 = node[4, nil]
n3 = node[3, cons[n4, nil]]
n2 = node[2, nil]
n1 = node[1, cons[n2, cons[n3, nil]]]
tree = n1

redtree1 = nil

redtree = ->(f, g, a, list) do
  label, subtrees = cons1[list]
  f[label, redtree1[f, g, a, subtrees]]
end.curry

redtree1 = ->(f, g, a, list) do
  return a if !list or list == []
  subtree, rest = cons1[list]
  g[redtree[f, g, a, subtree], redtree1[f, g, a, rest]]
end.curry

sumtree = redtree[add, add, 0]
p sumtree[tree]    #=>10

labels = redtree[cons, append, nil]
p labels[tree]    #=>[1, 2, 3, 4]

maptree = ->(f, list) {redtree[node * f, cons, nil, list]}.curry
st = ->(x) {x.to_s}
p maptree[st, tree]    #=>["1", ["2"], ["3", ["4"]]]

#####
##### 遅延評価(Enumerator を使う)
#####    ニュートン法による平方根の計算
#####

nxt = ->(n, x) {(x + n / x) / 2}.curry

repeat = ->(f, a) do
  Enumerator.new {|y| loop {y << a; a = f[a]}}
end

within = ->(eps, enum) do
  a, b = enum.next, enum.peek
  return b if (a - b).abs <= eps
  within[eps, enum]
end

sqrt = ->(a0, eps, n) {within[eps, repeat[nxt[n], a0]]}
p sqrt[1.0, 0.0001, 2.0]    #=>1.4142135623746899

relative = ->(eps, enum) do
  a, b = enum.next, enum.peek
  return b if (a - b).abs <= eps * b.abs
  within[eps, enum]
end

sqrt = ->(a0, eps, n) {relative[eps, repeat[nxt[n], a0]]}
p sqrt[1.0, 0.0001, 3]    #=>1.7320508100147274

 
意外と Ruby でも関数型プログラミングが出来るものだな。

しかし思うのだけれど、ブロックを使った Ruby のメソッドチェーンは関数合成や高階関数のいいところ取りという気もする。ちがいますかね。

Ruby で不自由なのはいわゆる「部分適用」。これはカリー化を使って第一引数で(というか前の引数から)しかできない。しかし「部分適用」は、コードにバグがあると対応が面倒そうなので、まあいいかという気もする。