マージソート

Slashdot.jp で、初めて学ぶソートアルゴリズムは何がいい? という話題があった。

ちょくちょくマージソートが紹介されていたので作ってみた。

まずはマージ。

module Enumerable
  def merge(o)
    i = 0
    j = 0
    m = []
    while i < size && j < o.size
      if self[i] < o[j]
        m << self[i]
        i += 1
      else
        m << o[j]
        j += 1
      end
    end

    m += self[i..-1]
    m += o[j..-1]
  end
end

[1, 2, 3].merge [4, 5]          # => [1, 2, 3, 4, 5]
[4, 5].merge [1, 2, 3]          # => [1, 2, 3, 4, 5]
[1, 2, 3].merge [1, 2, 3]       # => [1, 1, 2, 2, 3, 3]
[1, 3, 5].merge [2, 4, 6]       # => [1, 2, 3, 4, 5, 6]
[1].merge [1]                   # => [1, 1]
[].merge []                     # => []

Enumerable::Enumerator で書けないか頑張ってみたけど、添字を使った方が簡単だった。

そしてソート。

module Enumerable
  def msort
    return self  if size <= 1
    n = size / 2
    self[0...n].msort.merge self[n..-1].msort
  end
end

a = (1..10).to_a.shuffle        # => [5, 3, 6, 8, 1, 4, 7, 2, 10, 9]
a.msort                         # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

動いた。

でも、Ruby の sort には、

  • sort
  • sort {|a, b| ... }
  • sort_by {|item| ... }

の 3 種類がある。

sort_by は sort {|a, b| ... } を使って作るのかな?

msort {|a, b| ... } を作るには merge {|a, b| ... } が必要。

module Enumerable
  def merge(o, &comp)
    comp = Proc.new{|a,b| a<=>b}  if comp.nil?
    i = 0
    j = 0
    m = []
    while i < size && j < o.size
      if comp [self[i], o[j]] < 0
        m << self[i]
        i += 1
      else
        m << o[j]
        j += 1
      end
    end

    m += self[i..-1]
    m += o[j..-1]
  end
end

[3, 2, 1].merge([5, 4]) {|a,b| b<=>a} # => [5, 4, 3, 2, 1]
[5, 4].merge([3, 2, 1]) {|a,b| b<=>a} # => [5, 4, 3, 2, 1]
["1", "5", "10"].merge(["2", "4", "8", "16"]){|a,b| a.to_i <=> b.to_i}
# => ["1", "2", "4", "5", "8", "10", "16"]

さて、これを msort {|a, b| ... } から使うには ... どうしたらいいんだ?

msort はブロックを使うわけじゃなくて、merge に渡したいだけなんだけど ...