Thanks Driven Life

日々是感謝

カンマ(,)による複数、及びハイフン(-)による連続した数字の取得

図書館とか本の所持冊数で良く見るような見ないようなやつで

  1-15,18,21-

というのがあると思います。
(こういうフォーマットて何か名前あるんでしょうか)
みたまんまと言いますか、これが何を表してるかっていうと

  1から15巻、18巻、21巻以降を持っている(まだ未完)

とかまあそんな感じだと思います。
こういった文字列を Ruby正規表現でチェックし、
実際にどの巻を持ってるか調べようかなとか思ったり。


決まりとして、

  • 文字列の最初と最後は必ず数字で
    • 今回は、「1-19,21-」みたいに、以降続刊とかいうのはなしで
  • 「--」と続くのは弾く
  • 「,」は連続でいくつ繋がっても無視する

てなことを決めると以下のような正規表現ができました。

  (?!^[^\d])   => 文字列の先頭は数字以外認めない
  (?!.*[^\d]$) => 文字列の末尾は数字以外認めない
  (?!.*\,-)    => 文中で「,-」と続くのは禁止
  (?!.*-\,)    => 文中で「-,」と続くのは禁止
  (?!.*--)     => 文中で「--」と続くのは禁止
  [-\,\d]*     => 以上の条件を踏まえた上で、
                  文字列は「-」「,」「数字」だけで構成される


(?!...)というのは否定先読み(negative lookahead)と呼ばれるものらしい。
こんな書き方初めてしった。奥が深いぜ正規表現


そんなこんなで、出来上がったのがこちら

  str = "1-15,17,19,21-23" # 今回調べる文字列とする

  str = str.gsub(/\s/, "")

  if /^(?!^[^\d])(?!.*[^\d]$)(?!.*\,-)(?!.*-\,)(?!.*--)[-\,\d]*$/ =~ str
    array = str.split(',')

    array.each do |s|
      next if s.empty? # 「,」が連続である場合にひっかかる

      a = s.split('-')
      if a.length > 1 # ex. a = "3-6" => (3..6).to_a
	vol.concat((a[0].to_i..a[1].to_i).to_a)
      else
        vol << a[0].to_i
      end

    end

    vol.uniq!
    vol.sort!

    print vol.join(' ')
  end

これを実行すると

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 19 21 22 23

なんとかできてるっぽい。
指定した文字列中で重複した文字があったり
順番とかばらばらでも、最後に uniq や sort をしてるので
別に問題ないかなと思ってます。


参考