Ruby TIPS

Ruby TIPS

範囲式や正規表現を使うには? ― 穴埋め問題と株価診断プログラムを作る

2017年1月27日

範囲式は条件式の中で使うとちょっと面白い動作をする。範囲式を使って簡単な穴埋め問題や株価診断プログラムを作ってみよう。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

 Rubyでは、..演算子や...演算子を使えば範囲式が記述できる。一般に、範囲式は繰り返し処理などで使う範囲オブジェクトとして利用するが、条件式の中で使うとちょっと面白い動作をする。今回は範囲式を使って、簡単な穴埋め問題や株価診断プログラムを作ってみよう。また、正規表現を使ってより簡単に書く方法も紹介する。

範囲式を条件式の中で使う

 範囲式が条件式の中で使われると、左辺の評価と右辺の評価を交互に行うような動きをする。最初は、左辺がtrueになるまではfalseを返し、左辺がtrueになると次からは右辺を評価する。そして、右辺がtrueになるまでtrueを返す。右辺がtrueになると、次は左辺を評価し、左辺がtrueになるまではfalseを返す、という具合である。大まかなイメージとしては、左右に壁があって、一方の壁にタッチしたら反対の壁に向かう、といった感じだが、それだけでは実感が湧きにくいし、正確な動きも分からないので、簡単な例で動作を確認するところから始めてみよう(リスト1.1)。なお、..演算子を使った場合と...演算子を使った場合で動きが異なる場合があるが、それについては後述する。

sample001.rb
x =[5,12,10,12,17,15,12,10]
x.each{|n|
  if n==10...n==15
    puts n.to_s + ":" + "true"
  else
    puts n.to_s + ":" + "false"
  end
}
リスト1.1 範囲式を条件式の中で使う簡単な例

..演算子の左辺はn==10、右辺はn==15である。配列xの要素は順にnに代入される。最初はn10になるまではfalseを返し、n10になるとtrueを返す。その後、nが15になるまでtrueを返し続ける。n15になったら、次はn10になるまでfalseを返し続ける。

 実行結果がどうなるか想像が付くだろうか。実際にやってみると、以下のようになる。

コンソール
$ ruby sample001.rb
5:false
12:false
10:true
12:true
17:true
15:true
12:false
10:true
$
実行例1.1 範囲式を条件式の中で使ったプログラムの実行例

nの値が10になるとtrueが返され、その後、nの値が15になるまではtrueが返され続ける。nの値が15になった時点ではtrueが返され、その後は、nの値が10と等しいかを調べる。nの値が10になるまではfalseが返され続ける。

 念のため、ステップごとに処理を追いかけた表を以下に示す。

n 評価される式 結果 評価の方法
5 n==10...n==15 false 左辺がtrueになるまでfalseを返す
12 n==10...n==15 false
10 n==10...==15 true 左辺がtrueになったのでこの次からは右辺を評価
12 n==10...n==15 true 右辺がtrueになるまでtrueを返す
17 n==10...n==15 true
15 n==10...n==15 true 右辺がtrueになったのでこの次からは左辺を評価
12 n==10...n==15 false 左辺がtrueになるまでfalseを返す
10 n==10...n==15 true 左辺がtrueになったのでこの次からは右辺を評価
表1.1 範囲式を条件式の中で使ったプログラムの動作

マーカーを付けた式が評価される。左辺の10にタッチするとtrueが返され、右辺の15にタッチするとfalseが返されるイメージで捉えるといい。

範囲式を使って穴埋め問題を作る

 範囲式と条件式を使えば、文字がある記号で囲まれた範囲内にあるかどうかが判定できる。その働きを利用して、問題文のうち<>で囲んだ部分をスペースに置き換えて穴埋め問題を作ってみよう。最初の<が現れるまでは文字を変更せず、次の>が現れるまで文字をスペースに変えればいい(リスト1.2)。

sample002.rb
answer = "This <is> a <pen>."
question = ""
answer.each_char{|c|
  if c=="<"...c==">"
    question += " "
  else
    question += c
  end
}
puts question
リスト1.2 穴埋め問題を作るプログラム

穴埋めにしたい部分を<>で囲んでおく。each_charメソッドにより変数answerで示される文字列を1文字ずつ調べ、最初は変数questionに文字が1つずつ連結されていく。文字が<に一致すればtrueが返されるので、変数questionにスペースが連結されるようになる。その後、文字が>に一致すれば、それ以降はfalseが返されるので、再び変数questionに文字が連結されるようになる。

 実行例は以下の通り。

コンソール
$ ruby sample002.rb 
This      a      .
$ 
実行例1.2 穴埋め問題を表示する

<>で囲まれた部分がスペースに置き換えられた。

 これまでの例では、..演算子を使っても...演算子を使っても結果は同じであるが、演算子の左辺と右辺が同時にtrueになることがある場合には、結果が異なるので注意が必要だ。例えば、<>ではなく、引用符で囲まれた部分をスペースに置き換えるのであれば、..演算子ではなく...演算子を使う必要がある。..演算子の場合は、左辺がtrueになったとき、右辺もtrueであれば、初期状態に戻る。従って、次からはfalseが返されてしまう。つまり、最初の引用符に出会った時点で引用符の中に入ったと見なされるが、次は引用符の外にいると見なされる。一方、...演算子では、左辺がtrueになった時点では右辺は評価されないので、次に右辺がtrueになるまでずっとtrueが返される。つまり、最初の引用符に出会った後は、次の引用符が現れるまで、ずっと引用符の中にいるものと見なされる。以下にプログラムと実行例を示すので、違いを確認してほしい。

sample003.rb
answer = "This \"is\" a \"pen\"."
question = ""
answer.each_char{|c|
  if c=="\""..c=="\""
    question += " "
  else
    question += c
  end
}
puts question
リスト1.3 穴埋め部分が引用符で囲まれている場合のプログラム(うまくいかない例)

穴埋めにしたい部分を引用符で囲んでおく。引用符そのものを文字として扱う場合には\でエスケープしておく必要があることに注意。

 実行例は以下の通り。引用符がスペースに置き換えられただけで、穴埋め問題になっていない。

コンソール
$ ruby sample003.rb
This  is  a  pen .
実行例1.3 穴埋め問題がうまく作れない例

引用符の中に入った後、すぐに引用符の外に出たと見なされる。つまり、引用符を見つけたときだけtrueになるので、引用符だけがスペースに置き換えられた。つまり、引用符だけを別に文字に置き換える場合には..演算子を使った方がいい。

 正しい結果を得るためには、..演算子ではなく...演算子を使うとよい。...演算子を使ってリスト1.3を書き換えて実行すれば、実行例1.2と同じ結果が得られる。違いを確認するため、ステップごとに処理を追いかけた表を以下に示す。

c 評価される式 結果 評価の方法
c=="\""..c=="\"" false 左辺がtrueになるまでfalseを返す
" c=="\""..c=="\"" true 左辺がtrue、右辺もtrue
i c=="\""..c=="\"" false 上で右辺がtrueだったので、初期状態に戻った
s c=="\""..c=="\"" false
" c=="\""..c=="\"" true 左辺がtrue、右辺もtrue
c=="\""..c=="\"" false 上で右辺がtrueだったので、初期状態に戻った
a c=="\""..c=="\"" false
c 評価される式 結果 評価の方法
c=="\""...c=="\"" false 左辺がtrueになるまでfalseを返す
" c=="\""...c=="\"" true 左辺がtrueになったのでこの次からは右辺を評価
i c=="\""...c=="\"" true 右辺がtrueになるまでtrueを返す
s c=="\""...c=="\"" true
" c=="\""...c=="\"" true 右辺がtrueになったのでこの次からは左辺を評価
c=="\""...c=="\"" false 左辺がtrueになるまでfalseを返す
a c=="\""...c=="\"" false
表1.2 ..演算子を使った場合(上)と...演算子を使った場合(下)の動作の違い

..演算子を使った場合、左辺と右辺が同時にtrueになることがあると、...演算子と動作が異なる。左辺がtrueのときに右辺がfalseであれば、..演算子でも...演算子でも同じ動作となる。

【コラム】株価診断プログラムを作る

 実のところ、上記のような例であれば正規表現を使った方が簡単にできる(その方法は後述する)。そこで、正規表現でなく範囲式を使わないとできない例として、株価の動きによって、売りシグナルと買いシグナルを表示する簡単なプログラムを紹介しておこう。

 このプログラムでは、株価が500円を下回ったら「買い」というシグナルを表示し、540円を上回ったら「売り」というシグナルを表示する。540円を上回っても、次に500円を下回るまでは「売り」と表示されることに注意しよう。次に500円を下回ったら、今度は540円を上回るまでずっと「買い」となる

sample005.rb
v=[514,498,512,496,544,534,548,534,504,487]
for i in 0..v.length-1
  if v[i]<500...v[i]>540
    puts "買い"
  else
    puts "売り"
  end
end
リスト1.4 簡単な株価診断プログラム

ブロックを使ってもできるが、ここでは繰り返し処理で書いてみた。配列vに株価が入力されている。株価が500円を下回ったら、540円を超えるまではずっと「買い」となり、540円を超えたら、500円を下回るまではずっと「売り」となる。

 実行結果は以下の通り。

コンソール
$ ruby sample004.rb
514:売り
498:買い
512:買い
496:買い
544:買い
534:売り
548:売り
534:売り
504:売り
487:買い
$ 
実行例1.4 株価診断プログラムの実行例

500円を下回ったら「買い」になり、540円を上回ったら「売り」になることが分かる。ただし、540円を超えた時点では「買い」になり、その次から「売り」になることに注意。また、540円を超えないと、いくら株価が下がってもずっと「買い」になることにも注意(ロスカットの指標にはならない)。

 もちろん、これだけで完璧な株価予測ができるわけではないが、下限値と上限値を上手く設定すると、その範囲内で株価が上昇傾向にあるか下降傾向にあるかが分かる。ただし、設定した範囲を株価が大きく逸脱すると役に立たないことに注意が必要だ。

正規表現を使って穴埋め問題を作る

 穴埋め問題の作成など、文字列を操作する多くの処理は正規表現を使うと簡単にできる。例えば、穴埋め問題を作るときに、<>をそのまま出力したいことも多いだろう。範囲式を使う方法では、falseからtrueに変わった時点を見つけて<を出力しないといけないので、直前の状態と比較する必要がある。そのためプログラムが複雑になってしまう。しかし、正規表現を使えば簡単にできる。以下に、正規表現を使った例を示しておこう。

sample005.rb
answer = "This <is> a <pen>."
question = answer.gsub(/<.*?>/){"<" + " "*($&.length-2) + ">"}
puts question
リスト1.5 正規表現を使って穴埋め問題を作る

Stringクラスのgsubメソッドは文字列を置換するためのメソッドである。ブロック付きにすれば、正規表現に一致した文字列を順に渡せる。一致した文字列は$&で参照できるので、<>の中に、一致した文字列の文字数-2個のスペースを入れたものに置換する。

 正規表現の/<.*?>/は、<>で囲まれた文字列を表している。.任意の文字*0文字以上の繰り返しを表す。その後の?最短一致の量指定子と呼ばれるもので、一致する文字列のうち最も短いものと一致することを表す。?がなければ最長一致となるので、<で始まり、任意の文字が続き、>で終わる最長の文字列は<is> a <pen>となってしまう。ここでは、<is><pen>に一致させたいので、?を指定している。

 なお、ブロック内に書かれた$&正規表現に一致した文字列全体を表す。lengthメソッドを使って長さを求め、2を引いているのは<>の中に含まれる文字列そのものの字数を求めるためである。*演算子は文字列の繰り返しに使われる。ここではスペースを繰り返すために使っている。図解してみると以下のようになる。

図1.1 gsubメソッドの引数とブロックの図解

gsubメソッドの引数に指定した正規表現は、「<で始まって0文字以上の任意の文字列が続き、>で終わる最短の文字列」という意味。ブロックの中では、見つけた文字列を<で始まり、見つけた文字列より2文字分少ないスペースが続き、>で終わる文字列に置き換える。

 実行例は以下の通り。

コンソール
$ ruby sample005.rb
This <  > a <   >.
$ 
実行例1.5 正規表現を使って作った穴埋め問題を表示する

範囲式を使った場合よりも、正規表現を使った方が、簡単に穴埋め問題ができる。

 今回は穴埋め問題を作る処理を例に、範囲式を条件式の中で使う方法を見た。ただし、多くの場合、文字列の処理には正規表現を使った方が簡単になる。両方の機能をよく知って、適切に使い分けるといい。

まとめ

 範囲式を条件式の中で使うと、ある範囲内にある文字列を検索したり、いくつかの数値が下限値から上限値に向かうときと、上限値から下限値に向かうときとで異なる処理を実行したりできる。ただし、文字列の処理には正規表現を使う方が簡単な場合が多い。

処理対象:..演算子|...演算子 カテゴリ:文法 > 演算子式 >範囲式

※以下では、本稿の前後を合わせて5回分(第13回~第17回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

13. クラスを継承するには? メソッドの呼び出しをprivate/protectedで制限するには?

オブジェクト指向言語の特長である「クラスの継承」をRubyで実現する方法を解説。スーパークラスのメソッドの呼び出し制限で、Ruby言語特有の内容についても紹介する。

14. クラスのメソッドをオーバーライドするには?

継承先クラスの新メソッドで元クラスの既存メソッドをオーバーライドして異なる機能に置き換える方法と、新メソッド内から既存メソッドを呼び出すことで既存機能に新機能を追加する方法を説明する。

15. 【現在、表示中】≫ 範囲式や正規表現を使うには? ― 穴埋め問題と株価診断プログラムを作る

範囲式は条件式の中で使うとちょっと面白い動作をする。範囲式を使って簡単な穴埋め問題や株価診断プログラムを作ってみよう。

16. RSSを扱うには? ― 標準rssライブラリ利用して天気予報を表示する

Rubyに標準搭載されているrssライブラリを使って、Webサイトで提供されているRSS/Atomフィードを処理する方法を説明する。例として天気予報情報のRSSフィードを使う。

17. 数値/文字列/配列/範囲式/正規表現の比較を行うには?

Rubyプログラミングでは「等しいかどうか」を調べるための比較はどう行うのか? 比較を行える演算子やメソッドを使って、さまざまな比較を試してみる。

サイトからのお知らせ

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • 日本マイクロソフト株式会社
  • グレープシティ株式会社