2012年6月25日月曜日

クラスの削除

irbやpryなどでサンプルを試したりしていると、適当なクラス名を使っていたりするため、クラスが重複することがあります。
もう1度class ... endと書いてしますと、クラスを再オープンすることになるので、思わぬ副作用に遭遇する時があります。
一度セッションを閉じるのも手ですが、remove_constを使用してクラスを削除できます。

pry(main)> class MyClass; end
=> nil
pry(main)> Object.const_defined? :MyClass
=> true
pry(main)> Object.class_eval {remove_const :MyClass}
=> MyClass
pry(main)> Object.const_defined? :MyClass
=> false

MyClassはトップレベルに定義したクラスなので、Objectの中に定義された定数(=クラス)を削除します。
remove_constはプライベートメソッドなので明示的にレシーバを指定できません。
そのため、上記のようにclass_eval / instance_evalまたはsendメソッドで呼び出す必要があります。

pry(main)> class MyClass; end
=> nil
pry(main)> Object.const_defined? :MyClass
=> true
pry(main)> Object.send :remove_const, :MyClass
=> MyClass
pry(main)> Object.const_defined? :MyClass
=> false

Moduleの中で定義されているクラスは、該当するModuleの中から削除します。

pry(main)> module M class MyClass; end; end
=> nil
pry(main)> M.const_defined? :MyClass
=> true
pry(main)> M.instance_eval {remove_const :MyClass}
=> M::MyClass
pry(main)> M.const_defined? :MyClass
=> false

2012年6月14日木曜日

ソート

達人出版社アルゴリズムを学ぼうという本を購入しました。
既に知っている内容から、何となくは知っていたけど理解があやふやだったというものが掲載されているので、頭の体操がてらには丁度いい印象です。
本書ではJavaで書かれていますが、Rubyで書きなおしてみます。
本文中に"Pythonとか、Rubyとかを使うべきなんだろうが、やつらはちょっと適当に書けすぎる"とありますが、気にしないことにします。

下記は、第3講で紹介されているソートアルゴリズムになります。(gist)

  • バブルソート
  • 選択ソート
  • 挿入ソート
  • マージソート
  • クイックソート
  • ヒープソート
実質Arrayへの拡張になっていますが、ちょっとはRubyらしく、ブロックを受け取ることことで昇順、降順をはじめ任意の並び替えに対応させています。
普段は至極当然ですが、Array#sortを使っています。

lib/sort.rb

module Sort
  def compare_to? x, y, &block
    return block ? block.call(x, y) : x < y
  end

  def bubblesort &block
    ary = self.clone
    (ary.size).downto(0) do |i|
      (0...(ary.size - 1)).each do |j|
        break if j > i
        unless compare_to? ary[j], ary[j+1], &block
          ary[j], ary[j+1] = ary[j+1], ary[j]
        end
      end
    end
    ary
  end

  def selectionsort &block
    ary = self.clone
    ary.each_index do |i|
      ((i+1)...(ary.size)).each do |j|
        unless compare_to? ary[i], ary[j], &block
          ary[i], ary[j] = ary[j], ary[i]
        end
      end
    end
    ary
  end

  def insertionsort &block
    ary = self.clone
    (1...ary.size).each do |i|
      j = i
      while (j > 0)
        unless compare_to? ary[j-1], ary[j], &block
          ary[j-1], ary[j] = ary[j], ary[j-1]
        end
        j -= 1
      end
    end
    ary
  end

  def mergesort &block
    mergesort_bang self, &block
  end

  def mergesort_bang ary, &block
    return ary if ary.size <= 1

    mid = ary.size / 2
    left = mergesort_bang ary[0...mid], &block
    right = mergesort_bang ary[mid..-1], &block
    merge left, right, &block
  end

  def merge x, y, &block
    work = []
    loop do
      if x.empty?
        work << y
        break
      end
      if y.empty?
        work << x
        break
      end
      compare_to?(x[0], y[0], &block) ? work << x.shift : work << y.shift
    end
    work.flatten
  end

  def quicksort &block
    quicksort_bang self, &block
  end

  def quicksort_bang items, &block
    pivot = items[0]
    parts = items.partition { |i| compare_to? i, pivot, &block }
    lower, upper = parts[0], parts[1]
    if parts.all? { |i| i.size <= 1}
      return lower.concat(upper).flatten
    end
    # split [x][y,z, ...] if "[][x,y,z, ...]"
    lower = upper.shift(1) if lower.size == 0
    upper = lower.shift(1) if upper.size == 0
    p1 = quicksort_bang(lower, &block)
    p2 = quicksort_bang(upper, &block)
    p1.concat p2
  end

  def heapsort &block
    heap = Heap.new &block
    self.each { |i| heap.push i }
    ary = []
    until (heap.values.empty?)
      ary << heap.pop
    end
    ary
  end
end

class Heap
  attr_reader :values
  def initialize values=[], &block
    @values = values
    @block = Proc.new &block if block_given?
  end

  def compare_to? x, y
    return @block ? @block.call(x, y) : x < y
  end

  def parent c
    (c - 1) / 2
  end

  def left c
    2 * c + 1
  end

  def right c
    2 * c + 2
  end

  def push v
    @values.push(v)
    c = @values.rindex v
    while (c > 0)
      break if compare_to? @values[parent(c)], @values[c]
      @values[parent(c)], @values[c] = @values[c], @values[parent(c)]
      c = parent(c)
    end
  end

  def pop
    value = @values.shift
    return value if @values.empty?
    @values.unshift @values.pop
    c = 0
    while (@values[left(c)] || @values[right(c)])
      child = !@values[right(c)] || compare_to?(@values[left(c)], @values[right(c)]) ? left(c) : right(c)
      if compare_to? @values[child], @values[c]
        @values[c], @values[child] = @values[child], values[c]
        c = child
      else
        break
      end
    end
    return value
  end
end

spec/sort_spec.rb

require 'spec_helper'

shared_examples_for 'sort [](empty)' do
  let(:ary) do
    ary = []
    ary.extend Sort
    ary
  end
  it { should == [] }
end

shared_examples_for 'ascending sort [2, 6, 1, 0, 5, 4, 3]' do
  let(:ary) do
    ary = [2, 6, 1, 0, 5, 4, 3]
    ary.extend Sort
    ary
  end
  it { should == [0, 1, 2, 3, 4, 5, 6] }
end

shared_examples_for "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
  let(:ary) do
    ary = ['C', 'B', 'A', 'F', 'E', 'D']
    ary.extend Sort
    ary
  end
  it { should == ['F', 'E', 'D', 'C', 'B', 'A'] }
end

describe Sort do
  context "#bubblesort" do
    subject { ary.bubblesort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.bubblesort { |a, b| a > b} }
    end
  end

  context "#selectionsort" do
    subject { ary.selectionsort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.selectionsort { |a, b| a > b} }
    end
  end

  context "#insertionsort" do
    subject { ary.insertionsort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.insertionsort { |a, b| a > b} }
    end
  end

  context "#mergesort" do
    subject { ary.mergesort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.mergesort { |a, b| a > b} }
    end
  end

  context "#quicksort" do
    subject { ary.quicksort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.quicksort { |a, b| a > b} }
    end
  end

  context "#heapsort" do
    subject { ary.heapsort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.heapsort { |a, b| a > b} }
    end
  end

end

describe Heap do
  context "#push" do
    context "when push '4' to [5, 8]" do
      subject do
        heap = Heap.new([5,8])
        heap.push 4
        heap.values
      end
      it { should == [4, 8, 5]}
    end
    context "when push '3' to [4, 9, 7, 8]" do
      subject do
        heap = Heap.new([4, 9, 7, 8])
        heap.push 3
        heap.values
      end
      it { should == [3, 4, 7, 8, 9]}
    end
  end
  context "#pop" do
    context "when pop from [4, 9, 7, 8, 10]" do
      let(:heap) { heap = Heap.new([4, 9, 7, 8, 10]) }
      it "returns 4" do
        heap.pop.should == 4
      end
      it "re-build heap as [7, 9, 10, 8]" do
        heap.pop
        heap.values.should == [7, 9, 10, 8]
      end
    end
    context "when pop from [](empty)" do
      let(:heap) { heap = Heap.new }
      it "returns nil" do
        heap.pop.should be_nil
      end
      it "no change heap as []" do
        heap.pop
        heap.values.should == []
      end
    end
  end
end

spec/spec_helper.rb

require 'pry'
require 'pry-nav'
require 'sort'

2012年6月6日水曜日

pry

半信半疑で入れたpryでしたが、思ったより重宝します。
色々なところで紹介されているので詳細は割愛しますが、個人的には

  • 外部エディタと連携
  • APIの検索やドキュメントの閲覧
  • デバッグの連携
あたりで使用しています。

pryはirbの代替というのが主な位置付けだと思いますが、今までirbでやることは大概がちょっとしたコードの試し書きみたいなもの。
ワンライナーで書ける程度ならよいのですが、1メソッド程度でも使い辛さを感じていました。
pryはラインエディタも備えていますが、使い慣れたエディタを使うのが一番かと思います。

2番目のドキュメント閲覧については、ウェブを検索することが多かったのですがちょっとだけ確認したい時などやはり面倒。
emacs使ってるので連動はできるのですが、あまりしっくりきていませんでした。
pryでお試しコード書く時も、そのままの状態でドキュメントを確認できるので作業がスムーズです。

3番目のデバッグですが、どうしてもデバッガはIDE付属のものをはじめ馴染めるものが少ない印象です。
その点、pryであればbinding.pryだけでブレイクポイント設定できるし、条件付きであれば"binding.pry if xxx"と書くだけ。
RSpec等でテストを自動にしていれば、「保存→テスト実行→ブレイクポイントで停止」となるので、非常にスムーズにデバッグがおこなえると思います。

インストールはgemで導入するのみ。

$ gem install pry pry-doc pry-nav
rvmでrubyをインストールした場合は
$ rvm docs generate
を実行しないと、ドキュメントが表示することができません。
これはpryに限ったことでなくriでも同様なのですが、最初これではまりました。
あとは、デバッグでステップ実行のためのエイリアスを設定しておきます。

~/.pryrc

Pry.commands.alias_command 'c', 'continue'
Pry.commands.alias_command 's', 'step'
Pry.commands.alias_command 'n', 'next'

2012年6月5日火曜日

モジュールのテスト

RSpecでモジュールをテストする方法です。
いくつか方法があると思いますがここでは3つ挙げます。

まず一つ目はRSpec.configureでincludeする方法。

lib/app.rb

module Hello
  module World
    def say
      "Hello World!"
    end
  end
end

spec/spec_helper.rb

require 'app'

RSpec.configure do |config|
  config.include Hello::World
end

spec/app_spec.rb

require 'spec_helper'

describe "Hello::World" do
  context "#say" do
    context "with including Module via RSpec.configure" do
      it { say.should == "Hello World!" }
    end
  end
end

2つ目はdescribeブロックでincludeする方法。こちらはspc_helper.rbのRSpec.configure記述が不要です。

spec/app_spec.rb

require 'spec_helper'

describe "Hello::World" do
  include Hello::World
  context "#say" do
    context "with including Module within the 'describe' block" do
      it { say.should == "Hello World!" }
    end
  end
end
これらはsinatraでRack::TESTを使用する方法と同様です。

別の方法としてオブジェクト拡張を使う方法があります。オブジェクト拡張についてはメタプログラミングRubyに記述があります。

spec/app_spec.rb

require 'spec_helper'

describe "Hello::World" do
  context "with anonymous inner class" do
    subject do
      obj = Object.new
      obj.extend Hello::World
      obj
    end
    its(:say) { should == "Hello World!" }
  end
end

1、2番目はRack::Testの他にもhelperのテストでも同様に使用できるでしょう。特定のクラスで使用することを想定してテストしたい場合や、局所的に記述したい時は3番目で書くとよいのではないでしょうか。

2012年5月29日火曜日

conky

定番の監視ツールconkyの導入です。
Xubuntu 12.04にしてから、conky起動後にデスクトップをクリックするとconkyの画面が消失する(プロセスは生きている)現象があったため
しばらく放置していたのですが、設定で解決できました。

まずはインストール。
$ sudo apt-get install conky-all lm-sensors

私のPCのマザーボードにはw83627ehfのドライバが必要で、以前は手動でインストールしていたのですが、
いつの間にか取り込まれていて不要になりました。

$ sudo sensors-detect

基本的にデフォルトでOKだと思いますが、最後に必要なドライバを/etc/modules追記するか聞かれるので、ここだけ追加するように答えます。
もちろん、手動で追加しても構いません。

設定についてはあまり詳しく知らないのですが、デフォルトの/etc/conky/conky.confとネットでの情報を元に記述しました。
ここで"own_window_type desktop"を設定した場合、"own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager"を設定しないと、
最初に述べた、デスクトップをクリックした時にconkyが消失する現象が発生してしまいます。

~/.conkyrc

alignment bottom_left
background no
border_width 1
cpu_avg_samples 6
default_color white
default_outline_color white
default_shade_color white
draw_borders no
draw_graph_borders yes
draw_outline no
draw_shades no
use_xft yes
xftfont ricty Mono:size=10
gap_x 80
gap_y 60
minimum_size 5 5
net_avg_samples 2
no_buffers yes
out_to_console no
out_to_stderr no
extra_newline no
own_window yes
own_window_class Conky
own_window_type desktop
own_window_argb_visual yes
own_window_transparent yes
own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager
double_buffer yes
stippled_borders 0
update_interval 1.0
uppercase no
use_spacer none
show_graph_scale no
show_graph_range no

TEXT
${color grey}Uptime:$color $uptime

${color orange}CPU$alignr $color${freq_g}GHz ${hwmon 0 temp 2 0.001 0}℃
$cpu%${color grey}${cpubar 6}
 ${color grey}CPU1 $color ${cpu cpu1}%
  ${color grey}${cpugraph cpu1 15,200}
 ${color grey}CPU2 $color ${cpu cpu2}%
  ${color grey}${cpugraph cpu2 15,200}
 ${color grey}CPU3 $color ${cpu cpu3}%
  ${color grey}${cpugraph cpu3 15,200}
 ${color grey}CPU4 $color ${cpu cpu4}%
  ${color grey}${cpugraph cpu4 15,200}
 ${color grey}CPU5 $color ${cpu cpu5}%
  ${color grey}${cpugraph cpu5 15,200}
 ${color grey}CPU6 $color ${cpu cpu6}%
  ${color grey}${cpugraph cpu6 15,200}

${color orange}GPU$alignr$color
 ${nvidia temp}℃

${color orange}Memory
 ${color grey}RAM Usage
  ${color}$mem${color grey}/$color$memmax${color grey} - ${color}$memperc%
   ${color grey}$membar
 ${color grey}Swap Usage
  $color $swap${color grey}/$color$swapmax${color grey} - $color$swapperc%
   ${color grey}${swapbar 6}

${color orange}DISK
 $color${fs_used}${color grey}/$color${fs_size}$alignr${fs_used_perc}%
  ${color grey}${fs_bar 6}
 ${color grey}Read $color$diskio_read${color grey} / Write $color$diskio_write

${color orange}Networking
 ${color grey}Up $color ${upspeed eth0}${color grey} / Down $color ${downspeed eth0}

${color orange}Processes$alignr${color grey}Total:${color}$processes  ${color grey}Running:$color $running_processes
${color grey} TOP CPU
 ${color grey}Name${alignr}PID   CPU%   MEM%
 ${color lightgrey}${top name 1}$alignr${top pid 1} ${top cpu 1} ${top mem 1}
 ${color lightgrey}${top name 2}$alignr${top pid 2} ${top cpu 2} ${top mem 2}
 ${color lightgrey}${top name 3}$alignr${top pid 3} ${top cpu 3} ${top mem 3}
 ${color lightgrey}${top name 4}$alignr${top pid 4} ${top cpu 4} ${top mem 4}

${color grey} TOP MEM
 ${color grey}Name${alignr}PID   CPU%   MEM%
 ${color lightgrey}${top_mem name 1}$alignr${top_mem pid 1} ${top_mem cpu 1} ${top_mem mem 1}
 ${color lightgrey}${top_mem name 2}$alignr${top_mem pid 2} ${top_mem cpu 2} ${top_mem mem 2}
 ${color lightgrey}${top_mem name 3}$alignr${top_mem pid 3} ${top_mem cpu 3} ${top_mem mem 3}
 ${color lightgrey}${top_mem name 4}$alignr${top_mem pid 4} ${top_mem cpu 4} ${top_mem mem 4}

あとはスタートアップに登録すればよいのですが、私の環境の場合、コマンドで"conky -d"の登録だけでも動作しました。
環境によってはsleepを入れて起動を遅らさなければいけないようなので、その場合はスクリプトを書いて登録しましょう。

2012年5月27日日曜日

HTTPメソッドとの対応

sinatraに触れると、だいたい最初の段階でCRUD操作がルーティングのpost/get/put/delete do ... endに対応する、ということを学びます。
しかしながら、多くのブラウザにはgetおよびpostの実装しかないということで、putとdeleteはもうひと工夫必要になります。
この辺りは予備知識があれば容易に分かるところですが、そうでないとなかなか辿り着くのに苦労します。
解決策としては

  • POSTによる代用
  • Javascriptによるput/deleteメソッドの生成
があります。get '/foo/delete' do ... end などとしてgetによる削除、更新処理をしている例もありますが、RESTfulなURI設計とは言い難いのでおすすめしません。
POSTによる代用は、フォームの隠しパラメータに_methodパラメータを用意しそこにメソッド名を入れます。sinatraで利用する場合は、:method_overrideを有効にすることで適切なルーティングがおこなわれます。例えば、
@@ post_delete
form method="POST"  action="/destroy_it"
  input type="hidden" name="_method" value="DELETE"
  button type="submit"
    | delete
のようなフォーム(Slim使用)あって、ボタンを押すとPOSTリクエストがサーバには届きますが、delete "/xxxx" do ... end へルーティングされます。
二番目の方法は読んでそのままですが、jQueryを使用すると下記のような形でリクエストを生成します。
$.ajax({
  type: "DELETE",
  url: "destroy_it",
  success: function(data) {
    $(".message").text(data)
  }
});
以下、両者のサンプルになります。"$ ruby app.rb"で実行できます。

app.rb

require 'sinatra/base'
require 'slim'

class MyApp < Sinatra::Base

  configure :development do
    require 'sinatra/reloader'
    register Sinatra::Reloader
  end

  configure do
    enable :inline_templates
    enable :method_override
  end

  get '/style.css' do
    scss :style
  end

  get '/post_delete' do
    slim :post_delete
  end

  get '/delete_method' do
    slim :delete_method
  end

  delete '/destroy_it' do
    content_type :text
    "delete done!!"
  end

  run! if app_file == $0
end

__END__

@@ layout
doctype html
html
  head
    title My App
    script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
    link rel="stylesheet" href="/style.css"
  body == yield

@@ post_delete
form method="POST"  action="/destroy_it"
  input type="hidden" name="_method" value="DELETE"
  button type="submit"
    | delete

@@ delete_method
span.delete__
  | delete
div.message

javascript:
  $(function(){
    $(".delete__").click(function(){
       $.ajax({
         type: "DELETE",
         url: "destroy_it",
         success: function(data) {
           $(".message").text(data)
         }
       });
    })
  })

@@ style
span.delete__ {
  cursor: pointer;
  background-color: #dddddd;
  border: #cccccc 2px outset;
  border-radius: 5px;
  padding: 1px 4px 2px 4px;
}

div.message {
  color: red;
  margin: 2px;
  padding: 2px;
}

2012年5月24日木曜日

Xfce Terminal Emulator

フォントや色の類はGUIでも設定できるのですが、起動時の大きさと位置は設定ファイルに記述しなければいけないようです。

~/.config/Terminal/terminalrc

MiscDefaultGeometry=132x43+0+0

デフォルトは80x24。
なお、このファイル、1度GUIで適当に設定しないと生成されないようです。インストール直後は注意。