読者です 読者をやめる 読者になる 読者になる

「Rubyでどう書く?:連続した数列を範囲形式にまとめたい」をやってみる

Rubyでどう書く?:連続した数列を範囲形式にまとめたい
http://builder.japan.zdnet.com/sp/ruby-doukaku-panel/story/0,3800086254,20369264,00.htm

・数値は、半角スペースで区切られた文字列で渡されます。
・続いている部分は、最初の数値と最後の数値を-(ハイフン)で繋いだ表記にします。
・連続が1回の場合(前の数も後ろの数も連続でない)は、-(ハイフン)では繋ぎません。
・出力は、「,」(カンマ)と半角スペースで区切られた文字列でなければなりません。


"1 2 3" => "1-3."
"1 2 3 5 7 8" => "1-3, 5, 7-8."
"1 3 4 5 7" => "1, 3-5, 7."

という問題があったのでやってみる。
何も考えずJava脳で、調べ物もせずありあわせのRuby知識で書いたコードがこちら。

def renzoku(input)
  ins = input.split(" ")
  result = []
  buff = []
  ins.each do |i|
    i = i.to_i
    last = buff.last
    if last
      if i == (last + 1)
        buff << i
      else
        result << buff_to_s(buff)
        buff = []
        buff << i
      end
    else
      buff << i
    end
  end
  result << buff_to_s(buff)
  result.join(",")
end

def buff_to_s(buff)
  if buff.size == 1
    buff[0].to_s
  else
    buff.first.to_s + "-" + buff.last.to_s
  end
end

def eq(result, expected)
  puts "#{result} - #{expected}"
  if result == expected
    puts "ok"
  else
    puts "NG"
  end
end

eq(renzoku("1 2 3"), "1-3")
eq(renzoku("1 2 3 5 7 8"), "1-3,5,7-8")
eq(renzoku("1 3 4 5 7"), "1,3-5,7")

長いですね。assertっぽいメソッドも自分で作っちゃったけど、なんかあった気が。メインの部分だけで30行くらい?
とりあえずこれをリファクタリングしてみる。

require 'test/unit'
include Test::Unit::Assertions

def renzoku(input)
  buff, result = [],[]
  input = input.split(" ").map {|i|i.to_i}
  input.each do |i|
    if buff.last && (buff.last + 1) < i
      result << buff_to_s(buff)
      buff.clear
    end
    buff << i
  end
  result << buff_to_s(buff)
  result.join(",")
end

def buff_to_s(buff)
  if buff.size == 1
    buff[0].to_s
  else
    buff.first.to_s + "-" + buff.last.to_s
  end
end

class TestRenzoku < Test::Unit::TestCase
  def test_renzoku
    assert_equal("1-3",renzoku("1 2 3"))
    assert_equal("1-3,5,7-8",renzoku("1 2 3 5 7 8"))
    assert_equal("1,3-5,7",renzoku("1 3 4 5 7"))
  end
end

いまいちだな。。。
ちなみに元記事の回答例はこちら。

a = $*[0].split(' ').map{|i|i.to_i}+[nil]
i = a[0]
p a.inject([a[0].to_s]){|r, v|
  if i != v
   r << r.pop + "-#{i-1}" if 2 <= i - r.last.to_i
   break r unless v
   i = v
   r << i.to_s
  end
 i +=1
 r
}.join(', ')+'.'

回答例ではテストデータの投入部分がないですが、それでもかなり短くまとまっている。しかし、injectのあたりでムムッとなってしまうのは俺が知能指数が足りないからだろうか・・。かといって俺のコードのほうが分かりやすく美しい、というわけでもないけど・・。あ、最後にピリオドつけなきゃいけないのか。

def renzoku(line)
  b, r = [],[]
  line.split(' ').map{|j|j.to_i}.each do |i|
    if b.last && (b.last + 1) < i
      r << b2s(b)
      b.clear
    end
    b << i
  end
  r << b2s(b)
  r.join(',')+'.'
end
def b2s(b)
  r = b.first.to_s
  if b.size > 1
    r += '-' + b.last.to_s
  end
  r
end

じゃこんなかんじで。。。おやすみ。