マージソート(Ruby)

後記。以下の実装のほとんどはふつういうマージソートの実装ではありません。何かしら別物です。追記された merge_sort2.rb のみがふつういうマージソートの実装なので注意してください。というか、参考にしないで下さい。
 



アルゴリズムの基本

アルゴリズムの基本

マージソートアルゴリズムだけ勉強して、自力で Ruby で実装してみました。

merge_sort.rb

class Array
  def merge_sort
    return [] if empty?
    each_slice(1).to_a.merge
  end
  
  def merge
    #ar0, ar
    ar0 = []

    each_slice(2) do |x|
      ar = []
      if x.size == 1
        ar = x[0]
      else
        while x[0].size.nonzero? and x[1].size.nonzero?
          ar.push((x[0][0] < x[1][0]) ? x[0].shift : x[1].shift)
        end
        ar += x[0] + x[1] 
      end
      ar0 << ar
    end
    
    return ar0[0] if ar0.size == 1
    ar0.merge
  end
  protected :merge
end

a = (0..10).to_a.shuffle
a.merge_sort

Array#merge は分割されたソート済みの配列を2つずつ選んで結合します。結合をしているのは while 以下の 4行で、配列 x[0] と x[1] から値の小さい順に要素を選んで配列 ar に push します(値の小さい順に選んでいるので、配列 ar もソート済になります)。そしてそれを再帰的に繰り返し、すべて結合したら終了となります。
 
 
ここでの実装(これは本を参考にした)の方がすっきりしていますかねえ。でも、自力の実装もアルゴリズムに素直だという利点があると思います。


※追記(2018/2/9)
別様に実装してみました。
merge_sort1.rb

class Array
  def merge_sort
    join = lambda do |a, b|
      result = []
      while a.size.nonzero? and b.size.nonzero?
        result.push((a[0] < b[0]) ? a.shift : b.shift)
      end
      result + a + b
    end
    msort = lambda do |ar|
      return ar[0] if ar.size <= 1
      merged = []
      ar.each_slice(2) {|a, b| merged << join.call(a, (b or []))}
      msort.call(merged)
    end
    msort.call(map {|x| [x]})
  end
end

アルゴリズムどおりに素直に実装しました。まず与えられた配列をバラバラにしてそれぞれを配列化します。関数 msort は二つずつ配列を取り出して join します。関数 join[a, b] は、ソート済の二つの配列 a, b を、うまくソート済になるように結合します。


※再追記(2018/3/12)
さらに別様に実装してみました。これがふつういうマージソートの実装です。
merge_sort2.rb

class Array
  def merge_sort
    merge = ->(a, b) {
      result = []
      while a.size.nonzero? and b.size.nonzero?
        result.push((a[0] <= b[0]) ? a.shift : b.shift)
      end
      result + a + b
    }
    
    return self if length <= 1
    q = length / 2
    merge.(self[0...q].merge_sort, self[q..-1].merge_sort)
  end
end

Ruby でたらいまわし関数を遅延評価する

Haskell が遅延評価で「たらいまわし関数」を高速に実行できるなら、Ruby でも Proc で遅延評価できるので、Ruby でも「たらいまわし関数」を高速化できるのではないかと思った。でぐぐってみたら、そのものズバリの記事を発見。
 
おお、きちんとまとまったわかりやすい記事である。このリンクだけで充分なのだが、やはり自分でやってみないとということでやってみました。基本的に上記事のコードと同じ内容です(多少好みに書き換えました)。

tarai_lazy.rb

class Proc
  def -(lmd)
    ->{self.call - lmd.call}
  end
end


def tak_lazy(x, y, z)
  ->{
    xval = x.call
    yval = y.call
    if xval <= yval
      yval
    else
      tak_lazy(tak_lazy(x - ->{1}, y, z),
               tak_lazy(y - ->{1}, z, x),
               tak_lazy(z - ->{1}, x, y)).call
    end
  }
end

x, y, z = 12, 6, 0
tak = tak_lazy(->{x}, ->{y}, ->{z}).call
puts "tak(#{x}, #{y}, #{z}) = #{tak}"

数値まで lambda で包んでしまう。実行結果。

$ time ruby tarai_lazy.rb
tak(12, 6, 0) = 12

real	0m0.096s
user	0m0.036s
sys	0m0.020s

おお、圧倒的に速い!(Linux Mint 18.2 @ Core i5 4210U 1.70GHz; Ruby 2.3.3)

なんと、過去記事での C言語より速いではないか! 遅延評価を使わない Ruby 版と比べると、約20倍の高速化になっている(以上 user で比較)。なるほど、「たらいまわし関数」は遅延評価と相性がいいのだなと納得。まあ、Haskell 版にはさすがに敵いませんが。

Haskell でたらいまわし関数

まだ Haskell は全然わかりませんが、Haskell では「たらいまわし関数」がすごいということなのでやってみました。「たらいまわし関数」については、このブログにも簡単な記事があって、Ruby などでやってみております。簡単にいうと、これは関数呼び出しのベンチマークに使われる関数です。竹内関数とも呼ばれます。


で、Haskell のコードです。面倒な部分(つまり入出力)ははしょって、超初心者らしく書いております(他人のコードを参考にして、というかもろパクっています)。

tarai :: Int -> Int -> Int -> Int
tarai x y z
    | x <= y    = y
    | otherwise = tarai
                    (tarai (x - 1) y z)
                    (tarai (y - 1) z x)
                    (tarai (z - 1) x y)
                    
main = putStrLn $ show $ tarai 12 6 0

 
実行結果。

$ ghc tarai.hs
$ time ./tarai
12

real	0m0.003s
user	0m0.000s
sys	0m0.000s

なんというか…、言葉も出ません。

これはどういうことかというのですが、何なんでしょうね。詳しくは
で色いろ調べられております(自分にはよくわかりませんが)。遅延評価のためということなのでしょう。カリー化は? これも自分にはよくわかりませんが、もちろん関係しているのでしょうな。たらいまわし関数に関しては Haskell 最強という話であります。


※追記
インタプリタでやっても速いので、これはやはり遅延評価自体の威力なんだろうなあ。

$ time runghc tarai.hs
12

real	0m0.192s
user	0m0.148s
sys	0m0.036s

GTK+ で落書き 9(Ruby)

自作の Gem 'oekaki' で落書きしてみました。蚊取り線香? 何だか目が回ります。
 

 
コードは以下。何も考えずにテキトーにいきあたりばったりでコーディングしました。

require 'oekaki'

R = 7
Width, Height = 500, 500

Oekaki.app width: Width, height: Height do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, Width, Height)
  end
  
  p = Vector[0, 0]
  np = p.dup
  deg = 90
  r = 0
  step = 1.5
  
  b_line = lambda do |p1, p2|
    l = (p2 - p1).r
    pt = p1.dup
    0.upto(l) do
      color(0, 65535, 0)
      arc(true, Width / 2 + pt[0] - R, Height / 2 - pt[1] - R,
          R * 2, R * 2, 0, 64 * 360)
      pt += (p2 - p1) / l
    end
  end
  
  id = timer(50) do
    b_line.call(p, np)
    
    p = np
    θ = PI * deg / 180
    np = Vector[cos(θ), sin(θ)] * r
    r += step
    step_d = 20 - r / 35
    deg -= step_d
    Gtk.timeout_remove(id) if r > 220
    true
  end
end

関数 b_line は点 p1 から p2 へ太い線を引きます(円を動かしてやっています)。
お絵かきするのに標準添付ライブラリの Vector クラスや(ここでは使っていませんが)Matrix クラスは役立ちます。計算がだいぶ楽になりますよ。
 
Gen 'oekaki' については以下。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura

Haskell の Linux Mint(Ubuntu)へのインストール

Haskell の処理系は GHC(The Glasgow Haskell Compiler)が有名です。これを Linux にインストールしてみます。最新版はここからダウンロードしてインストールしますが、面倒なのでパッケージ・マネージャでインストールしました。
 

$ sudo apt-get install haskell-platform

だけでインストールできるので、とても簡単です。REPL(対話型実行環境)は

$ ghci
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
Prelude> 2 + 15
17
Prelude> :q
Leaving GHCi.
$

という感じです。このとおり、パッケージの最新バージョンは 7.10.3 でした。このバージョンで問題はたぶんないと思います。ちなみに現在の最新版は 8.0.2 です。

Linux Mint 18.1 で確認しました。

Hello, World!

おなじみの「Hello, World!」をやってみます。

hello.hs

main = putStrLn "Hello, World!"

 
インタプリタで実行。

$ runghc hello.hs
Hello, World!

 
コンパイルして実行。

$ ghc -o hello hello.hs
[1 of 1] Compiling Main             ( hello.hs, hello.o )
Linking hello ...
$ ls
hello  hello.hi  hello.hs  hello.o
$ ./hello
Hello, World!

 
OK ですね!

ドラゴン曲線を描く(Ruby)

自己相似図形であるドラゴン曲線を Ruby で描いてみました。
 
3次。

5次。

10次。これだと確かにドラゴンみたいですね。

 
描画には自作の Gem 'oekaki' を使っています。 

require 'oekaki'

Width, Height = 600, 400

class Point < Struct.new(:x, :y)
end

Oekaki.app width: Width, height: Height, title: "Dragon curve" do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, Width, Height)
    
    drawing = proc do |a, b, depth|
      x = b.x - a.x
      y = a.y - b.y
      
      c = Point.new
      c.x = a.x + (x + y) / 2
      c.y = b.y + (x + y) / 2
      
      if depth.zero?
        color(0, 65535, 0)
        line(a.x, a.y, c.x, c.y)
        line(b.x, b.y, c.x, c.y)
      else
        drawing[a, c, depth - 1]
        drawing[b, c, depth - 1]
      end
      true
    end
    
    a, b = Point.new, Point.new
    a.x, a.y = 150.0, 150.0 
    b.x, b.y = Width - 150.0, 150.0
    
    drawing[a, b, 5]
  end
end

 
Gem 'oekaki' については以下。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura

マンデルブロ集合を描いてみる(Ruby)


 
このサイトのそのままパクリです(ありがとうございます!)。やったのは Java から Ruby へ移植しただけ。
 

def mandelbrot_count(c)
  z = Complex(0)
  100.times do |i|
    z = z ** 2 + c
    return i if z.abs > 10
  end
  100
end

Diff = 0.001
io = open("mandelbrot_data.dat", "w+")

-2.step(1, Diff) do |r|
  -1.step(1, Diff) do |i|
    value = mandelbrot_count(Complex(r, i))
    next if value.zero?
    io.puts "#{r}\t#{i}\t#{value}"
  end
  io.print "\n"
end

io.close

自分の環境では 4分あまりかかりました。
 
 
gnuplot で描画します。これは上サイトそのまま。

set pm3d
set pm3d map
set palette defined(0"#000099",1"#ffffff",2"black")
set terminal png size 1024,768
set output 'mandelbrot-pm3d.png'
splot 'mandelbrot_data.dat' notitle

 



追記。こちらもどうぞ。(2019/7/28)
obelisk.hatenablog.com