第44回 Ruby/Rails勉強会@関西に行ってきた

第44回 Ruby/Rails勉強会@関西

初級者向けレッスンの演習問題

Arrayクラスの次のメソッドを Stringクラスにも作ってください。 文字列はアスキー文字 (1バイト文字) だけからなるもとのします。 sort については、非破壊的なメソッドと破壊的なメソッドを作ってください。

ruby1.9 を使えばマルチバイト文字でもそのまま動く。

アプローチとして String# を使う方法と Array にして処理してから join する方法がある。(テキスト §2.3 および §2.4((テキストは第44回 Ruby/Rails勉強会@関西のページ参照。)) )

String# の方で素直に書いてみた。

#! /usr/local/bin/ruby19
# -*- coding: utf-8; -*-

class String
  def push(o)
    self[size] = o
    self
  end

  def pop
    val = self[-1]
    self[-1] = ''  unless empty?
    val
  end

  def shift
    val = self[0]
    self[0] = ''
    val
  end

  def unshift(o)
    self[0,0] = o
    self
  end

  def first
    self[0]
  end

  def last
    self[-1]
  end

  def sort!(p = 0, q = size - 1, &comp)
    return self  if p >= q
    comp = :<=>  unless comp
    i = partition!(p, q, &comp)
    sort!(p, i-1, &comp)
    sort!(i+1, q, &comp)
    self
  end

  def sort(p = 0, q = size - 1, &comp)
    s = dup
    s.sort!(p, q, &comp)
  end

  private
  def partition!(p, q, &comp)
    i = p
    (p+1 .. q).each do |j|
      if comp[self[p], self[j] ] > 0
        i += 1
        self[i], self[j] = self[j], self[i]
      end
    end
    self[p], self[i] = self[i], self[p]
    i
  end
end


if __FILE__ == $0
  require 'test/unit'

  class TestArrayMethods <Test::Unit::TestCase
    def test_push
      s = ''
      assert_equal('a', s.push('a'),
                   '空文字列に push すると与えた文字が返ること')
      assert_equal('a', s,
                   'push は破壊的であること')
      assert_equal('ab', s.push('b'),
                   'push すると文字を末尾に追加した文字列か返ること')
      assert_equal('ab', s,
                   'push は破壊的であること')
      assert_equal('abcd', s.push('cd'),
                   '2文字以上 push できること')
      assert_equal('abcd', s,
                   'push は破壊的であること')
      assert_equal('abcd', s.push(''),
                   '空文字を push しても変化しないこと')
      assert_equal('abcd', s,
                   '変化していないこと')
    end

    def test_pop
      s = 'abcd'
      assert_equal('d', s.pop,
                   'pop すると最後の文字が返ること')
      assert_equal('abc', s,
                   'pop は破壊的であること')
      s.pop
      s.pop
      assert_equal('a', s.pop,
                   '最後の 1 文字が返ること')
      assert_equal('', s, '空であること')
      assert_equal(nil, s.pop,
                   '空文字列に pop すると nil が返ること')
      assert_equal('', s, '空であること')
    end

    def test_shift
      s = 'abcd'
      assert_equal('a', s.shift,
                   'shift すると最初の文字が返ること')
      assert_equal('bcd', s,
                   'shift は破壊的であること')
      s.shift
      s.shift
      assert_equal('d', s.shift,
                   '最後の 1 文字が返ること')
      assert_equal('', s, '空であること')
      assert_equal(nil, s.shift,
                   '空文字列に shift すると nil が返ること')
      assert_equal('', s, '空であること')
    end

    def test_unshift
      s = ''
      assert_equal('d', s.unshift('d'),
                   '空文字列に unshift すると与えた文字が返ること')
      assert_equal('d', s,
                   'unshift は破壊的であること')
      assert_equal('cd', s.unshift('c'),
                   'unshift すると文字を先頭に挿入した文字列が返ること')
      assert_equal('cd', s,
                   'unshift は破壊的であること')
      assert_equal('abcd', s.unshift('ab'),
                   '2文字以上 unshift できること')
      assert_equal('abcd', s,
                   'unshift は破壊的であること')
      assert_equal('abcd', s.unshift(''),
                   '空文字を unshift しても変化しないこと')
      assert_equal('abcd', s,
                   '変化していないこと')
    end

    def test_first
      s = 'abcd'
      assert_equal('a', s.first,
                   'first は先頭の文字を返すこと')
      assert_equal('abcd', s,
                   'first 非破壊的であること')
      assert_equal(nil, ''.first,
                   '空文字列に first すると nil を返すこと')
    end

    def test_last
      s = 'abcd'
      assert_equal('d', s.last,
                   'last は末尾の文字を返すこと')
      assert_equal('abcd', s,
                   'last は非破壊的であること')
      assert_equal(nil, ''.last,
                   '空文字列に last すると nil を返すこと')
    end

    def test_sort
      s = 'higaki'
      assert_equal(s.split('').sort.join, s.sort,
                   'Array#sort と同じ結果であること')
      assert_equal('higaki', s,
                   'sort は非破壊的であること')
      assert_equal(s.split('').sort{|a,b|b<=>a}.join, s.sort{|a,b|b<=>a},
                   'Array#sort と同じ結果であること')
      assert_equal('higaki', s,
                   'sort は非破壊的であること')
      assert_equal('', ''.sort,
                   '空文字列に sort しても死なないこと')

      assert_equal(s.split('').sort.join, s.sort!,
                   'Array#sort と同じ結果であること')
      assert_equal('higaki'.split('').sort.join, s,
                   'sort! は破壊的であること')
      assert_equal(s.split('').sort{|a,b|b<=>a}.join, s.sort!{|a,b|b<=>a},
                   'Array#sort と同じ結果であること')
      assert_equal('higaki'.split('').sort{|a,b|b<=>a}.join, s,
                   'sort! は破壊的であること')
    end
  end
end