おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

徒然Ruby(7)文字列と正規表現

文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。

文字列リテラル

文字列リテラルには

  • ダブルクォートで囲む(今まで使ってきた方法)
  • シングルクォート(')で囲む
  • %記法
  • ヒア・ドキュメント

があります。 ここでは最も使うダブルクォート、シングルクォートとヒアドキュメントを説明します。 その他の記法はRubyのドキュメントを参照してください。

ダブルクォートで囲む文字列リテラル

この記法はすでに出てきていますが、まだ説明が不十分でした。 ここでは、(1)この文字列リテラルの補足説明(2)バックスラッシュ記法(3)式展開を説明します。

ダブルクォートの文字列では

  • 空白を挟んで複数の文字列があれば、それらは結合されてひとつの文字列とみなされる
  • 文字列の途中に改行があれば、それは文字列中の改行コードになる(改行の代わりに\nがあるのと同じ)
  • 改行が文字列の途中でなければ(つまり複数の文字列の間であれば)改行前と後の2つの文字列に分かれる
a = "abc" "def" "ghi"
b = "abc
def"
c = "pqr" "stu"
"vwx"
print a, "\n"
print b, "\n"
print c, "\n"

これを実行すると

abcdefghi
abc
def
pqrstu

となります。

  • 変数aに代入された文字列は"abcdefghi"と同じ
  • 変数bに代入された文字列は"abc\ndef"と同じ
  • 変数cに代入された文字列は"pqrstu"と同じ
  • "vwx"は変数に代入されていないから、文字列として評価されるが、近いうちに消滅する(ガベージ・コレクション)

なお、ガベージ・コレクションというのは、どこからも参照されていない不用なオブジェクトを消滅させて、割り当てられていたメモリを解放することをいいます。 これをしないとどんどん不用なオブジェクトにメモリが割り当てられて、最悪の場合はメモリ不足になってしまいます。 ガベージ・コレクションはRubyが自動的に行ってくれます。

英数字やコンマ、ピリオドなどの表示できる文字以外に、改行やタブなどのコードを表すにはバックスラッシュ記法を用います。 最も使うのは改行\nです。 それ以外には次のようなものがあります(全部ではありません)

  • \t =>タブ
  • \文字 =>文字はtのような特殊な意味をもつもの(\tはタブになる)以外の文字。 その文字自身になる。 たとえば、\##自身を表す。 #は後ででてくる式展開でも使うので、#自身を表したい時にバックスラッシュをつける
  • \改行 =>改行を取り消す。複数行に文字列が渡るときに改行を抑止するために使う

なお、文字列でなくても改行直前にバックスラッシュがあるとその改行は抑止されます。 ですので、次の2つは同じ文字列を表します。

a = "abcd\
efg"
b = "abcd" \
"efg"
print a, "\n"
print b, "\n"

式展開は文字列中に#{式}の形で書き、式を文字列で表したものに置き換えます。 例えば、式の部分が整数の100であれば、それを文字列の"100"として文字列中に埋め込みます。

number = 100
print "数字は#{number}\n"

実行すると

数字は100

と表示されます。 式はもっと複雑なものでも構いません。 式展開は非常に便利で、文字列で最も良く使われる機能です。

シングルクォートで囲む文字列リテラル

シングルクォートで囲む文字列は、ダブルクォートのようなバックスラッシュ記法や式展開をしません。 シングルクォート自身を含めたい時にエスケープが必要なので、\'とバックスラッシュ記法を使います。 これにともない、バックスラッシュ自身もエスケープが必要な場合が出てくるので\\と表します。 行末のバックスラッシュはバックスラッシュ自身として解釈されます。

a = 'abc\ndef'
b = 'abc\\ndef'
c = '\abc
def'
d = 'abc\
def'
print a, "\n"
print b, "\n"
print c, "\n"
print d, "\n"

実行すると

abc\ndef
abc\ndef
\abc
def
abc\
def

となります。 シングル・クォートでは文字をその文字自身として表したい時に使いますが、バックスラッシュの扱いだけは注意が必要です。

ヒア・ドキュメント

ヒア・ドキュメントは<<EOSの次の行から行頭のEOSまでのすべての行からなる文字列です。 複数行に渡る長い文字列を表すためのものです。 EOSの代わりに任意の文字列を使っても構いません。

a = <<EOS
 春はあけぼの。やうやう白くなりゆく山ぎは、すこしあかりて、紫だちたる雲のほそくたなびきたる。
 夏は夜。月のころはさらなり。やみもなほ、蛍の多く飛びちがひたる。また、ただ一つ二つなど、ほのかにうち光りて行くもをかし。雨など降るもをかし。
 秋は夕暮れ。夕日のさして山の端いと近うなりたるに、烏の寝どころへ行くとて、三つ四つ、二つ三つなど、飛びいそぐさへあはれなり。まいて雁などのつらねたるが、いと小さく見ゆるはいとをかし。日入りはてて、風の音、虫の音など、はたいふべきにあらず。
 冬はつとめて。雪の降りたるはいふべきにもあらず、霜のいと白きも、またさらでもいと寒きに、火など急ぎおこして、炭もて渡るもいとつきづきし。昼になりて、ぬるくゆるびもていけば、火桶の火も白き灰がちになりてわろし。
EOS

print a

実行すると、枕草子の第一段が表示されます。 つまり、変数aの指す文字列は4行の長い文字列であるわけです。

EOSにダブルクォートをつけ<<"EOS"とするとダブルクォート文字列と同じように、バックスラッシュ記法と式展開を使えます。 同様にEOSにシングルクォートをつけ<<'EOS'とすると挟まれた文字列そのものになります。 このとき文中のシングルクォートにエスケープは必要ありません。 なお、クォートのない<<EOSはダブル・クォートと同じ扱いです。

インデントが可能ですがそれについてはドキュメントを参照してください。

パターンマッチ

文字列の検索には正規表現が用いられます。 正規表現の実装は一通りではありません。 現在のRuby(バージョン3.1)では「鬼雲」という正規表現ライブラリが使われています。 他の言語で正規表現を使ったことがあっても、細かい点ではRubyと異なる可能性があります。 また、Ruby正規表現は多岐にわたっているので、詳細はドキュメントを参照してください。 ここでは主な書き方に絞って説明します。

正規表現リテラル

正規表現はオブジェクトのひとつです。 正規表現は文字列の検索に用いられ、検索パターンの情報とメソッドを持っています。 また、正規表現は文字列、整数、配列と同じようにリテラルで表現することができます。 正規表現の場合はスラッシュ(/)で文字列を囲んで表します。 リテラルの規則は非常に多いので、ここですべてを解説することはできません。 詳細はRubyのドキュメントを参照してください。

例えば文字列"Hello"を検索することを考えてみます。 "Hello"という検索パターンを正規表現リテラルで表すと、

/Hello/

となります。 そして、そのパターンが文字列に含まれているかどうかを調べるには次のようにします。

a = "Hello world. Hello world.\n"
if /Hello/ =~ a
  print "マッチした\n"
else
  print "マッチしなかった\n"
end

print (/Hello/ =~ a), "\n"

=~は左辺の正規表現パターンが右辺の文字列の中に存在するとき(それを「マッチする」という)マッチした位置(インデックス)を返し、マッチしなかったときnilを返します。 このプログラムの文字列にはHello worldが2つありますが、はじめのHello worldにマッチするので、返される値は0です。 プログラムの最後の一行は0を表示します。 プログラムの実行結果は次のようになります。

マッチした
0

0はif文では真になることに注意してください。 if文で偽になるのはnilとfalseだけです。

Helloだけでなくhelloも検索対象にしたいときは

/[Hh]ello/

という正規表現を使います。 [ ]は文字クラスという仕組みで、その中にある文字のどれかに一致します。 更にすべて大文字のHELLOも入れたければ

/[Hh]ello|HELLO/

縦棒|はその左のパターンまたは右のパターンのどちらかにマッチすれば良い、という意味になります。 この他に次のようなものがあります。

  • メタ文字(正規表現で特別な意味を持つ文字)は( ) [ ] { } . ? + * | \で、これらの文字自身を表すにはバックスラッシュでエスケープする
  • #{ }で式展開できる(ダブルクォート文字列と同じ)
  • \nは改行にマッチ。その他にバックスラッシュで表す特別な文字(タブなど)がある
  • .(ドット)は「任意の1文字」にマッチ
  • [ ]で文字クラスを表す。 例えば[atc]はa、t、cのいずれにもマッチする。 [a-z](小文字全体)のようにハイフンで範囲を表せる
  • *は前の文字の0個以上の繰り返しにマッチ
  • +は前の文字の1個以上の繰り返しにマッチ
  • ^は行頭にマッチ
  • $は行末にマッチ

これ以外にも豊富なメタ文字がありますので、Rubyのドキュメントを参照してください。

Ruby版のgrep

Unixの文字列検索コマンドgrepRuby版を作ってみましょう。 名前をrubygrep.rbとすることにします。 コマンドラインにパターン、ファイル名の2つの引数を取り、パターンにマッチした行を標準出力に出力します。

$ ruby rubygrep.rb '^\#{1,6} ' 2022-9-10-Array.md

この例では2022-9-10-Array.mdという名前のファイルを検索します。 パターンは

  • ^は行頭に一致
  • \#はナンバー記号(#)。 正規表現リテラルでは式展開でこの記号(#)を使うので、バックスラッシュでエスケープしておく
  • {1,6}は1以上6以下の繰り返し
  • 半角空白は(文字通り)半角空白に一致

これらを総合すると「行頭からナンバー記号が1から6個並び、空白が続く行」となります。 すなわち、Markdownの見出しを検索することになります。

プログラムを作るために、ファイルの読み込みを説明しておきましょう。 ファイルを行ごとの配列に読み込むには

File.readlines(ファイル名)

というメソッドを使います。 rubygrep.rbは次のようになります。

r = Regexp.compile(ARGV[0])
File.readlines(ARGV[1]).each do |s|
  if r =~ s
    print s
  end
end
  • Regexp.compile(文字列)は文字列を正規表現に変換する。 コマンドラインでは正規表現リテラルとして書くことができないので、文字列としてRubyに伝わる。 Rubyではそれを正規表現オブジェクトに変換する必要がある
  • File.readlines(ARGV[1])でファイルを行ごとに読み、その配列を返す。 その配列に対してeachメソッドで各要素を取り出してパラメータsに代入しブロックを実行する
  • ブロックではsが正規表現rに一致すればsをprintで標準出力に書き出す

Ruby正規表現をサポートしているので、非常に短いプログラムで済みました。 実行してみます。

$ ruby rubygrep.rb '^\#{1,6} ' 2022-9-10-Array.md
## 配列のリテラル
## 2次元配列
## 配列のメソッド
## 配列の変更と複製
## ARGV

見出しの部分だけ表示されました。 期待通りの結果です。

なお、マッチの演算子=~は実は正規表現オブジェクトのメソッド.=~()の糖衣構文です。

r =~ s # => r.=~(s)

メソッド名は=~です。

したがって、演算子の左側は正規表現オブジェクトでなくてはならないのですが、実は左右を逆にしても使えます。

"Hello world. Hello world.\n" =~ /Hello/

これを糖衣構文でメソッドに直すと.=~()は文字列のメソッドになってしまいます。 実は文字列オブジェクトでも.=~()メソッドを定義していて、それは演算子の左右を入れ替え、正規表現.=~()メソッドの値を返すことになっています。 事情は複雑ですが、要するに正規表現と文字列を左右どちらに置いてもOKということです。

マッチデータオブジェクト

検索時あるいは検索後にマッチした文字列を取り出したいことがあります。

例えば「Markdown文書の目次を自動生成する」ことを考えてみましょう。 目次の対象になるのは見出しです。 見出しはATXタイプ(ナンバー記号が行頭に来るタイプ)だけを対象にします。

見出し検索の正規表現パターンを次のようにします。

/^\#{1,6} +(.+)$/
  • 行頭から1から6個のナンバー記号
  • 1個以上の半角空白
  • 任意の文字が行末まで

括弧( )には2つの機能があります。

  • グループを表す。数式の括弧と同じ
  • マッチした文字列のその部分を取り出す。

マッチには=~またはmatchメソッドを使います。

=~でマッチを行った場合は、その直後に前から1番目の( )でマッチした文字列が$1に、以下n番目の( )にマッチした文字列が$n$に代入されます。

a = "## 配列のリテラル"
if /^\#{1,6} +(.+)$/ =~ a
  print $1, "\n"
end

このプログラムでは( )がひとつしかありません。 if文のマッチは成立し、括弧とマッチする文字列は「配列のリテラル」ですので、それが$1に代入されています。 プログラムを実行すると、

配列のリテラル

と表示されます。 注意が必要なのは、$1は次のパターンマッチが行われると内容が変わってしまうことです。 ですから、マッチ文字列をとっておきたいときは、s = $1など、別の変数に$1の内容を代入しておくことです。 これをうっかりしたバグが結構あります。

matchメソッドを使うと、マッチが実行され、その結果がMatchDataオブジェクトとして返ってきます。 MatchDataオブジェクトはマッチの情報を保持するオブジェクトです。 $1に相当するデータは.[1]メソッドで取得できます。

m = /^\#{1,6} +(.+)$/.match("## 配列のリテラル")
print m[1], "\n"

mには正規表現で文字列に対してマッチを行った結果のMatchDataオブジェクトが代入されます。 m[1]で1番めの括弧に対応するマッチ文字列が得られますので、結果は

配列のリテラル

が表示されます。 文字列検索でマッチした文字列を得るにはmatchメソッドが便利で、私もよく使います。 このときの注意点としては

  • 文字列と正規表現の左右を入れ替えても同じMatchDataオブジェクトが得られる。 このへんの事情は=~メソッドと同様
  • 文字列が正規表現とマッチしないときはmatchメソッドはnilを返す。 このとき[ ]を使うと、nilにはそのメソッドがないのでエラーになってしまう。 そこで、.to_aメソッドで配列に変更しておくとエラーを避けることができる

2番めの注意点はもう少し説明が必要です。

  • MatchDataオブジェクトのto_aメソッドは、マッチした全体の文字列が0番目の要素、以下n番目の( )にマッチする文字列がn番目の要素となる配列を返す
  • nil.to_aは空の配列[ ]を返す

これを具体的なプログラムで説明しましょう。

m = /^\#{1,6} +(.+)$/.match("## 配列のリテラル")
print m[1], "\n"
print m.to_a[1], "\n"
m = /^\#{1,6} +(.+)$/.match("配列のリテラル")
print m.class, "\n"
print m.to_a[1]
print m[1]

これを実行すると

$ ruby example7.rb
配列のリテラル
配列のリテラル
NilClass

example7.rb:6:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)

print m[1]
       ^^^

となります。 行ごとに説明します。

  • 1: マッチが成立し、mにはMatchDataオブジェクトが代入される
  • 2: MatchDataオブジェクトmには[ ]メソッドがあり、1番目のマッチ文字列が表示される=>「配列のリテラル」が表示される
  • 3: MatchDataオブジェクトが配列["## 配列のリテラル", "配列のリテラル"]to_aメソッドで変換される。 更に[1]で配列の1番めの要素が取り出され、表示される=>「配列のリテラル」が表示される
  • 4: マッチが成立せず、mにはnilが代入される
  • 5: m.classはmのクラス名を返す=>「NilClass」が表示される
  • 6: m.to_anilから空の配列になる。 空の配列の1番めの要素が無いので、nilが返される。 printはnilを空文字列として出力する=>空行が出力される
  • 7: nilには[ ]メソッドがないのでエラーになる=>NoMethodErrorが表示される

長い説明になりましたが、まとめると、

「MatchDataオブジェクトに[ ]メソッドを使うときはその前にto_aメソッドを使おう」

ということです。

文字列のメソッド

文字列には便利なメソッドがいっぱいありますが、ここでは検索と置換に関係するメソッドを取り上げます。

subとgsub

subとgsubは置換メソッドです。 その違いは、subが最初にマッチした部分の置換だけをするのに対して、gsubは繰り返しマッチと置換を行います。 そして置換によってできた新規文字列を返します。 元の文字列オブジェクト自身は変更されません。

a = "abcdefg"
b = a.sub(/def/, "DEF")
print "a = ", a, "\n"
print "b = ", b, "\n"

実行してみます。

a = abcdefg
b = abcDEFg

メソッドsubによってdefがDEFに置換され、変数bに代入されました。 元の文字列と置換された文字列は別のオブジェクトですから、表示されたaとbは異なります。

元の文字列自身を破壊的に置換したいときはsub!メソッドを使います。 エクスクラメーションマークのついたメソッドは「破壊的」であることが多いです。 さきほどのプログラムをsub!に置き換えて実行してみましょう。

a = "abcdefg"
b = a.sub!(/def/, "DEF")
print "a = ", a, "\n"
print "b = ", b, "\n"
a = abcDEFg
b = abcDEFg

sub!はマッチしたときは元の文字列に置換をほどこし、その文字列を返します。 したがって、変数bはaと同じ文字列を指しています。 その結果aとbが両方とも置換後の文字列になって表示されます。

注意しなければならないのはsubとsub!の返り値の違いです。

  • subはパターンがマッチすればそこを置換した新しい文字列オブジェクトを返し、マッチしなければ元と同じ内容の文字列を新規に生成して返します。 いずれの場合も元の文字列オブジェクトとは別の文字列オブジェクトを返します。
  • sub!はパターンがマッチすれば、元の文字列の中で置換してそれを返し、マッチしなければnilを返します。

特にマッチしないときの振る舞いが全然違うので、返し値を利用する場合は注意が必要です。

print "abcdef\n".sub(/ddd/,"DEF").sub(/ab/,"AB")
print "abcdef\n".sub!(/ddd/,"DEF").sub!(/ab/,"AB")

これを実行すると

ABcdef
example7.rb:2:in `<main>': undefined method `sub!' for nil:NilClass (NoMethodError)

print "abcdef\\n".sub!(/ddd/,"DEF").sub!(/ab/,"AB")
                                  ^^^^^

1行目は、最初のsubメソッドでマッチが起こらなかったので元と同じ内容の新規文字列を返し、その文字列に対して2番めのsubメソッドを実行した結果が表示されます。

2行目では、最初のsub!メソッドでマッチが起こらなかったのでnilが返され、nilに対してsub!メソッドを実行しようとしましたが、nilにはそんなメソッドが無いのでエラーになります。

さて、subの2番めのパラメータには、マッチした文字列を埋め込むことができます。

  • \0はマッチした文字列全体
  • \1\2、・・・は( )の1番目、2番目・・・のマッチした文字列
  • バックスラッシュ自身を表したいときはエスケープする(\\とする)

例えば

print "abcdefg\n".sub(/d(..)g/,'\1')

これを実行すると

abcef

と表示されます。 パターンにdefgがマッチし、( )に相当する部分がefなので、置換文字列の\1にはefが代入されます。 その結果、defgがefに置換されます。

この例では文字列にシングルクォート文字列を使いました。 ダブルクォート文字列を使うこともできますが、その場合バックスラッシュはエスケープしなければなりません。 したがって、

print "abcdefg\n".sub(/d(..)g/,"\\1")

このように\\1と書かなければならないことに注意してください。 なお、シングルクォートでもバックスラッシュをエスケープ可能ですので\\1はOKです。

置き換え文字列の中に\1などを埋め込めるのは便利に思うかもしれませんが、バグを生みやすいことにも注意が必要です。 置換文字列がリテラルで与えられるなら問題はないのですが、外部から取り込んだ文字列(例えばファイルを読み込んで得られた文字列)などの場合に\1などが入っていると意図しない動作になるおそれがあります。

このような問題を避けるには、ブロック付きのsubを使う方法があります。 このsubはマッチが起こった時にブロックを実行し、その値への置換を行います。 ブロックの中では、$0$1・・・の形でマッチした文字列、( )に対応する部分文字列を参照できます。 これらは以前述べたマッチが起こった直後のグローバル変数$0$1・・・です。

a = '\1'
print "abcdefg\n".sub(/d(..)g/){ a+$1 }

ブロックの中では\1に対する置き換えはないので、

abc\1ef

と表示されます。 以上のことから、単純ではない置換や置換先の文字列が不確定の場合はブロック付きのsubが推奨されています。 なお、ブロックにはパラメータをひとつ置くことができ、パラメータはマッチした文字列全体が引数として代入されます。

以上subを見てきましたが、実用的には繰り返し置換するgsubの方がよく使われます。 gsubは「繰り返し」置換する点だけがsubとの違いなので、subでの注意点はそのままgsubにも通用します。

split

splitメソッドは、区切りを表す文字列を引数にとり、区切られた文字列の配列を返します。 例えば

print "a,b,c".split(",")
print "\n"

を実行すると

["a", "b", "c"]

と表示されます。

引数は正規表現も可能です。 例えば、コンマ区切りデータを配列に変換するには、区切りにコンマと改行の文字クラスを指定すれば良いです。

c = <<EOS
2,3,5,7,11
13,17,19,23,29
31,37,41,43,47
53,59,61,67,71
73,79,83,89,97
EOS
c.split(/[,\n]/).each do |i|
  print i, "\n"
end

このプログラムを実行すると2から97までが表示されます。

コンマ区切りデータをきちんと定義したものをCSV(Comma separated value)といいます。 RubyではCSVを扱うライブラリがあるので、そちらを使うほうがより実用的です。

scan

scanは繰り返しマッチを行い、マッチした文字列を配列にして返すメソッドです。 コンマ区切りデータから数字を取り出す例をscanで書き直してみましょう。

c = <<EOS
2,3,5,7,11
13,17,19,23,29
31,37,41,43,47
53,59,61,67,71
73,79,83,89,97
EOS
c.scan(/\d+/).each do |i|
  print i, "\n"
end

\d[0-9]と同じで、十進数クラスを表します。 scanは十進数の連続を探して取り出しています。 splitを使ったプログラムと違い、区切りは十進数以外であれば何でもOKです。

今回splitとscanはデータの取り出しに使いましたが、更に広い応用もあります。 特にドキュメントを加工、変換するようなプログラムでは活用が期待できます。

徒然Ruby(6)配列

配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。

配列のリテラル

配列を表すには角括弧を用います。

a = [1, 2, 3]

変数aに配列が代入されています。 右辺は配列を表していて、この配列には1,2,3の3つの要素があり

  • 0番目の要素が1
  • 1番目の要素が2
  • 2番目の要素が3

です。 「〜番目」の「〜」の数字をその要素のインデックスといいます。 インデックスは0から始まることに注意してください。 また、インデックスには負数を用いることもできます。

  • -1番目の要素が3
  • -2番目の要素が2
  • -3番目の要素が1

つまり、負数の場合はおしりから前に戻ってきます。

配列を表すのに角括弧をつかいました。 この配列を表す記法を「リテラル」といいます。 同様にダブル・クォートで囲んだ記法は文字列のリテラルです。 整数のリテラルは、その整数自身を書けばよく、整数オブジェクトの10は単に「10」と書きます。 このようにリテラルとはそのオブジェクトを直接表現する記法のことです。

変数が配列を表すとき、要素を参照するには[ ]を用います。

a = [1, 2, 3]
print a[1], "\n"

printの引数がコンマ区切りで複数渡されています。 このとき、printは各引数に対して画面表示を行います。 ひとつひとつに分けてprintするのと同じ動作になります。

a[1]は配列の1番めの要素を表します。 インデックスは0から始まるのでa[1]は2になります。

a[5]のように、配列に要素がないときはnilが返されます。 nilは「何もない」ということを表すオブジェクトだと考えてほぼ間違いありません。

配列の要素に代入するには[ ]=を使います。

a = [1, 2, 3]
a[1] = 5
print a, "\n"

実行してみると

[1, 5, 3]

と表示されます。 1番目の要素が2から5に変わり、print aで配列を表示します。 printは配列をこのように表示してくれるので便利ですね。

a[6] = 10のように、何もないところに要素が代入されると、その前の空いていたところにはnilが代入されます。

[1, 5, 3, nil, nil, nil, 10]

配列はRubyのオブジェクトのひとつです。 ですので、配列はメソッドを持ちます。 実は要素の参照と代入もメソッドです。 [ ][ ]=は糖衣構文で、それぞれメソッド.[].[]=に直して実行されます。

a[2] => a.[](2)
a[3] = 5 => a.[]=(3, 5)

ひとつめは、「[]」がメソッド名で2が引数、ふたつめは「[]=」がメソッド名で3と5が引数です。

配列の要素には任意のオブジェクトを代入できます。 配列自身には型がなく、いろいろな種類のオブジェクトがひとつの配列に混在しても構いません。

文字列の配列を表すには[ ]とダブルクォートを用いて

[ "a", "b", "c" ]

と書けますが、%記法というのを使うと少し労力を減らすことができます。

abc = %w!a b c!
print abc, "\n"

実行すると

["a", "b", "c"]

と表示されます。 %記法では、「%w」が「文字列の配列を表す」という意味で、「!」(区切り)で挟まれた部分の空白で区切られた各文字列が要素になります。 文字列に!を入れたいときは、他の文字(非英字)を区切り文字に使います。

abc = %w|! !! !!!|
print abc, "\n"

実行すると

["!", "!!", "!!!"]

となります。 括弧類を区切り文字にするときは左括弧と右括弧を組み合わせます。

ab = %w(睦月 如月 弥生)
cd = %w[卯月 皐月 水無月]
ef = %w{文月 葉月 長月}
gh = %w<神無月 霜月 師走>
print ab, "\n"
print cd, "\n"
print ef, "\n"
print gh, "\n"

実行すると

["睦月", "如月", "弥生"]
["卯月", "皐月", "水無月"]
["文月", "葉月", "長月"]
["神無月", "霜月", "師走"]

%記法ではダブルクォートを書かなくて良いのが、意外に楽になった感じがします。

1から10までの整数を要素に持つ配列を作るにはRangeオブジェクトを使うと簡単です。 Rangeオブジェクトは整数の範囲を表すオブジェクトです。 例えば(1..10)は1から10までの整数を表します。 このRangeオブジェクトにはto_aというメソッドがあります。 to_aはその範囲の数字からなる配列を作ります。

a = (1 .. 10).to_a
print a, "\n"

実行すると

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

となります。 同様に1から1000までの配列も

a = (1..1000).to_a

で簡単に作れますね。 [ ]リテラルで書くと気が遠くなるでしょう。 C言語では、forループを使って作るのが最も合理的だと思いますが、それでもRubyの短いコードで解決できるのには敵いません。

2次元配列

たてよこに広がりを持つデータを配列で表すことができます。 例えばカレンダーを考えてみましょう。

カレンダー

2次元データは縦に1行目、2行目・・・、横に1列目、2列目・・・と数えます(なので、数学では2次元データを「行列」というわけです)。 1行目は7つのマスがあり、それを配列で表すと

[nil, nil, nil, nil, nil, 1, 2]

nilは「そこに何もない」ということを表します。 2行目以下も同様に配列で表せます。 それを配列で表すと

calendar = [
  [nil, nil, nil, nil, nil, 1, 2],
  [3, 4, 5, 6, 7, 8, 9],
  [10, 11, 12, 13, 14, 15, 16],
  [17, 18, 19, 20, 21, 22, 23],
  [24, 25, 26, 27, 28, 29, 30]
]

となります。 配列は1行で書いてもよいのですが、長くなってわかりにくいので改行して行ごとに書きました。 カレンダーの3行目は配列で表せます。

calendar[2] #=> [10, 11, 12, 13, 14, 15, 16]

更に、左から4番めのマスは

calendar[2][3] #=> 13

となります。 [ ]は糖衣構文で、Rubyはメソッドに直してから実行するのでした。

calendar[2] => calendar.[](2)

[]がメソッド名で、2はメソッドの引数。このメソッドは[10, 11, 12, 13, 14, 15, 16]を返す。 返されたものが配列なので、再び[]メソッドが使え、

calendar[2][3] => (calendar.[](2)).[](3) => [10, 11, 12, 13, 14, 15, 16].[](3) => 13

となります。 以上から、上から3行目、左から4列めの要素はcalendat[2][3]と表せます。 数字が1だけずれるのは、1から数え始めるか0から数え始めるかの違いから起こります。 どちらも0から始めることに直せば、

上から2行目、左から3列めの要素はcalendat[2][3]

ということになります。 一般に

上からm行目、左からn列めの要素はcalendat[m][n]

です。

このように、たてよこに広がりのあるデータは配列を二重に使うことによって表せます。 この二重の配列を2次元配列といいます。

注意したいのは、2次元配列という新たなオブジェクトが作られたわけではなく、もともとあった(1次元)配列とそのメソッドを繰り返し使っただけだということです。 ですから、3回繰り返せば3次元配列、4回繰り返せば4次元配列・・・・というように拡張できます。

配列のメソッド

配列には多くの有用なメソッドがありますが、ここではその一部を紹介します。 まず、eachとeach_indexはよく使われるメソッドです。 eachは配列要素を順に取り出しブロックを実行します。

[1,2,3,4,5].each do |i|
  print i, "\n"
end
  • 最初の要素1をパラメータiに代入してブロックを実行=>1が表示される
  • 次の要素2をパラメータiに代入してブロックを実行=>2が表示される
  • 以下、順に5までパラメータiに代入して実行

実行結果は次のようになります

1
2
3
4
5

each_indexは要素のインデックス(0からはじめて何番目か)をパラメータにします。

a = ["", "", ""]
a.each_index do |i|
  print i
  print " => "
  print a[i]
  print "\n"
end

実行してみます。

0 => 青
1 => 黃
2 => 赤

この2つに似たメソッドにeach_with_indexがあり、これは要素とインデックスの2つをパラメータに渡します。

["青", "黃", "赤"].each_with_index do |x, i|
  print i
  print " => "
  print x
  print "\n"
end

出力結果はeach_indexの例と同じになります。

Rubyでは、eachイテレータを使うことが多いです。 for文やwhile文もRubyにはあるのですが、イテレータのほうが読みやすく分かりやすくなることが多いです。

配列の変更と複製

配列は変更可能なオブジェクトです。 それは、[ ]=で要素を代入できることからも分かります。 その他に配列自身を変更するメソッドには次のようなものがあります。

  • << => 配列の最後に要素を加え、できた配列を値として返す。 糖衣構文によって、演算子のように記述できる。 例えば、[1,2] << 3によって、配列が[1, 2, 3]に変更され、その配列がメソッドの値として返る。 メソッドで書き直すと[1,2].<<(3)となる。
  • appendまたはpush。 この2つは同じ動作のメソッドで、引数(複数可)を配列の後ろに加える。 [1, 2].append(3, 4) => [1, 2, 3, 4]。 引数は配列[3, 4]にしても同じ結果になる
  • pop => 配列の最後の要素を取り除き、その取り除いたオブジェクトを値として返す。 引数が正の整数であれば、その数だけ取り除く
  • clear => 配列の要素をすべて取り去り、空の配列にする。 このとき、配列の要素だったオブジェクトは残る。 配列がそれらのオブジェクトを要素として指さなくなったということ
  • delete => 引数と同じ要素をすべて取り除く
  • delete_at => 引数はインデックスを表す。そのインデックスの要素を取り除く

この他にも配列自身を変更するメソッドがありますが、詳細はRubyのドキュメントを参照してください。 なお、自身を変更するメソッドを「破壊的」メソッドといいます。

clear、delete、delete_atなどで説明したように、配列から削除することはそのオブジェクトを消滅させることではありません。 配列は変数に似ていて、各オブジェクトを「指している」だけです。

この点はCの配列とは異なります。 Cの配列は型があり、その型の「配列の要素数分のメモリが確保されます。 そして配列のメモリの中に値が書き込まれていきます。 したがって、配列のある要素を変更すると、以前の値は消えてしまいます。 Rubyの場合は指しているオブジェクトが変わるだけで以前のオブジェクトは消滅しません。

a = "abc"
b = "def"
c = [a, b]
print c, "\n"
c[0] = "ABC"
print a, "\n"
print c, "\n"

これを実行すると次のようになります。

["abc", "def"]
abc
["ABC", "def"]

配列cの0番目の要素は文字列オブジェクト"abc"から"ABC"に変わりましたが、元のオブジェクト"abc"は残っていて、変数aを表示した時にabcが表示されています。

配列はオブジェクトを指しているだけなので、配列を複製(コピー)して新たな同じ内容の配列をオブジェクトとして作成するとき、各オブジェクトは複製されないことに注意してください。 複製はdupメソッドまたはcloneメソッドで行います。 この2つは細かい点で振る舞いが違いますが、ここではその説明は省略します。

Dupのイメージ

このようなコピーをシャロー・コピー(shallow copy)といいます。

シャロー・コピーをすると同じオブジェクトを異なる配列から参照しているので、一方でオブジェクトを変更(破壊的メソッドを使う)と他方の配列にも影響します。 そのためバグを生じやすいので注意しなければなりません。 これを避けるには

  • 破壊的メソッドを使わない。 例えばa[0]=="abc"のとき、a[0].replace("ABC")は破壊的メソッドで文字列オブジェクトが変更される。 それに対してa[0]="ABC"は新しい文字列"ABC"を生成して0番目の要素を取りかえるだけで元のオブジェクトは変更されずに残る
  • オブジェクトの複製も行う。 eachメソッドを使って各オブジェクトの複製を作るなどの方法があります。 このとき元の配列と複製された配列は違うオブジェクトを指すので、破壊的メソッドを使っても他の配列に影響はありません
  • オブジェクトを変更禁止にしておく。 freezeメソッドを使うとオブジェクトは変更禁止になります。 すべての破壊的メソッドは効かなくなります(エラーになる)

のようなことが考えられます。 一番良いのは安易に配列の複製をしないことだと思います。

ARGV

コマンドラインからRubyプログラムを実行する時に引数を渡すことができます。 引数は配列ARGVに渡されます。 大文字から始まる識別子は定数を表します。 ARGVは定数なのでこれに代入することはできません。

ARGV.each do |x|
  print x, "\n"
end

これをexample6.rbに保存して、実行してみましょう。

$ ruby example6.rb Hello world
Hello
world
$

引数は半角の空白で区切られてARGVに格納されます。 eachメソッドで引数をひとつずつ取り出して表示しています。

引数が何個あるかを調べたいときがあります。 そのときはsizeメソッドを使います。

徒然Ruby(5)ローカル変数、文字列、インスタンス

今回の目標はインスタンスです。 インスタンスを説明するために、ローカル変数と文字列オブジェクトを事前に扱います。

ローカル変数

変数にはローカル変数、インスタンス変数、クラス変数がありますが、今回はローカル変数のみ説明します。

ローカル変数は(1)英小文字またはアンダースコア(_)で始まり(2)英文字、数字、アンダースコアが続きます。 例えばabc_d55などは変数を表すことができます。 これらはメソッド名にもなりうるので、それが変数なのかメソッド名なのかはプログラムの中で判断します。

ローカル変数はその変数に対する代入文がはじめて現れた時に、代入と同時に定義されます。 一度定義されると、そこからその変数が宣言されたブロック、メソッド定義、またはクラス/モジュール定義(次回以降に説明)の終りまで有効です。 この有効範囲をスコープといいます。 メソッド定義の外側で定義されたローカル変数はメソッド定義の中では参照できません。

abc = 10
print abc
print "\n"

def abcdefg
  print "Hello world.\n"
  print abc
  print "\n"
end

abcdefg

このプログラムexample5.rbとして保存し、実行するとエラーになります。

$ ruby example5.rb
10
Hello world.
example5.rb:7:in `abcdefg': undefined local variable or method `abc' for main:Object (NameError)

  print abc
        ^^^
        from example5.rb:11:in `<main>'
$

プログラムを順を追って見ていきましょう

  • ローカル変数abcに10を代入する
  • print abcで10が画面に表示される=>実行画面の最初の10
  • メソッドabcdefgを定義する。 メソッドは
    • Hello world.を画面に表示し
    • ローカル変数abcを表示し、改行を表示する
  • メソッド定義では実行はしないので、エラーにはならない。
  • メソッドabcdefgを呼び出し、実行する
    • Hello world.を画面表示する=>実行画面2行目のHello world.
    • ローカル変数abcの値を画面表示しようとするが、abcが未定義なのでエラーになる=>3行目以降のエラーメッセージ
  • エラーになったのは1行目で定義したローカル変数abcはメソッド内部では参照できないから。 つまり、メソッド内部はスコープではないからです。

abcをメソッドの外に移せば、スコープになるのでエラーにならずに10が表示されます。

abc = 10
print abc
print "\n"

def abcdefg
  print "Hello world.\n"
end

abcdefg
print abc
print "\n"

実行すると

10
Hello world.
10

また、メソッドの中でローカル変数を定義すると、その変数はメソッドの中だけで有効で、外はスコープ外になります。

文字列と変数

変数には文字列を代入することもできます。

a = "Hello world.\n"
b = a
a = "Good by.\n"

print b

ちょっとややこしいプログラムなんですが、これを実行すると画面には何が現れるでしょうか

  • 1 Hello world. が表示される
  • 2 Good by. が表示される

実行してみましょう

Hello world.

1が正解でした。 プログラムを順に見ていきます。

  • 変数aに文字列"Hello world.\n"が代入された=>aは文字列"Hello world.\n"を指している
  • 変数bに変数aの指している文字列を代入した=>bは文字列"Hello world.\n"を指している
  • 変数aに文字列"Good by.\n"が代入された=>aは文字列"Goodby.\n"を指している

最後の代入は変数'b'には関係ありませんから、bは相変わらず文字列"Hello world.\n"を指しています。

Rubyの文字列は可変です。 これはRubyの特徴で、良い場合と困る場合があります。 可変であるためにバグを生みやすいということは言えると思います。

例えば、文字列は[]=を使って文字を書き換えできます。

a = "free\n"
a[0] = "t"
print a

このプログラムを実行するとtreeが表示されます。

  • 変数aに文字列"free\n"が代入された
  • aの指している文字列の0番目の文字(文字を数えるとき最初の文字は0番目です)を"t"に変える=>"free\n"が"tree\n"になる
  • aの指す文字列"tree\n"を画面に表示

この[]=というのは配列のn番目の要素を代入するのに似ています。 文字列は文字の配列だと考えれば自然な演算です。

さて、次のプログラムを実行すると何が表示されるでしょうか

a = "free\n"
b = a
a[0] = "t"

print b
  • 1 freeが表示される
  • 2 treeが表示される

実行すると、"tree"が表示されます。 2が正解です。 なぜでしょうか?

  • 変数aに文字列"free\n"が代入された
  • 変数bに変数aの指している文字列を代入した=>bは文字列"free.\n"を指している=>aとbは同じ文字列を指していることに注意
  • aの指している文字列の0番目の文字を"t"に変える=>"free\n"が"tree\n"になる =>bもaと同じ文字列を指しているので、bは"tree\n"を指している
  • bの指す文字列"tree\n"を画面に表示

何となくわかったでしょうか? 正しく理解するためには、インスタンスを理解することが必要です。

インスタンス

Rubyでは、文字列も数字もプログラムの対象になるものはすべてオブジェクトです。 オブジェクトはその中に状態を保ち続けることができる変数とメソッドを持ったもので、そのオブジェクトがどういう変数やメソッドを持っているかを定義しているものをクラスといいます。 クラスに基づいて、メモリ上にオブジェクトを実現したものをインスタンスといいます。 ひとつのクラスに対して通常は複数のインスタンスが可能です。

なお、「オブジェクト」と「インスタンス」という言葉はそれぞれのプログラミング言語により、使い分けられています。 Rubyの場合は両者は同じと考えて差し支えありません。 ですが、「インスタンス」というときは、「クラス」をメモリ上に実体化したというニュアンスが強くなります。 また、オブジェクトは「オブジェクト指向」という概念的な意味で使うこともあります。

例えば文字列のクラスはStringといいます。 "Hello world\n"というダブルクォートで囲まれた文字列がプログラム中に現れると、RubyはStringクラスのインスタンスを作ります。 インスタンスはメモリ上に作られ、その文字列がHello world\nであるという情報や、String固有のメソッドがあるという情報を保持します。

a = "Hello world.\n"

RubyはまずHello world\nという文字列のインスタンスをメモリ上に作成し、その場所を変数aに代入します。 変数aはそのインスタンスを指しているだけです。

a = "Hello world.\n"
a = "Good by.\n"
a = "Hello world.\n"
b = "Hello world.\n"

この場合、Hello world.\nという文字列インスタンスが2つ作られます。 aとbが指しているインスタンスは別のインスタンスです(文字列としては同じですが)。 インスタンスにはオブジェクトidという番号が振られ、区別できるようになっています。 オブジェクトidはobject_idメソッドで知ることができます。

print "Hello world.\n".object_id
print "\n"
print "Hello world.\n".object_id
print "\n"

これを実行すると

60
80

のようになります。 (あなたが実行するとき60や80とは違う数字になるかもしれません)。 同じ文字列だが、インスタンスとしては別だということがidの違いからわかります。

もう理解できたとは思いますが、最後に1問。

a = "free\n"
b = "free\n"
a[0] = "t"
print b

このとき、

  • 1 freeが表示される
  • 2 treeが表示される

のどちらですか?

答えは1です。 aとbは別のインスタンスを指しているので、aの指している文字列が変わってもbの指している文字列は変わっていません。

「同じ」ということ

a = "free\n"
b = "free\n"

このとき、aとb(正しくはaの指している文字列とbの指している文字列)は同じでしょうか? これはちょっとむずかしい問題です。

そこで、どちらを基準に考えているかによって2種類の「同じ」を判断する計算が必要になります。

  • a == b => aとbが同じ文字列ならtrue、違う文字列ならばfalse
  • a.eql?(b) => aとbが同じ文字列ならtrue、違う文字列ならばfalse
  • a.equal?(b) => aとbが同じインスタンスならtrue、違うインスタンスならば(文字列として同じであっても)false

eql?equal?は文字列のメソッドです。 実は==も文字列のメソッドで、a == ba.==(b)というメソッドだとして評価されます。 Rubyではメソッド名にアルファベットや数字だけでなく==?のような記号も使えるのが面白いところで、長所です。 この==メソッドとeql?は、ほぼ同じです。 詳しくはRubyのドキュメントを見てください。

糖衣構文

このように==という論理演算子Rubyではメソッド.==( )に直されて評価されるのはなぜでしょうか(==のようなメソッドと別形式が用意されているときその別形式を糖衣構文またはシンタックス・シュガーといいます)。 それは「同じ」ということの意味がオブジェクトごとに違うからです。 また、実装上も==をオブジェクトごとに定義するのが楽です。 (むしろ、一般的な==演算子を作り、どのオブジェクトにも通用するようにするのは事実上無理です)

同じことは+という演算子についてもいえます。 実はこれも糖衣構文で

10 + 5 => 10.+(5)

このように整数のメソッドに直して評価されます(メソッド名が+)。 整数の場合は算術的な加算としてメソッドが定義されていますが、文字列では連結としてメソッドが定義されています。

"abc" + "def" => "abc.+("def") => "abcdef"

他のオブジェクトでも+メソッドを定義することができます。 加算がオブジェクトごとに意味づけられるというのは面白い考えです。

整数のインスタンス

さて、整数の場合は文字列と違って可変ではありません。 このとき同じ整数を表すインスタンスを複数作るのは非効率です。 そこで整数の場合はあるひとつの整数を表すインスタンスは一つしか存在しません。

print 100.object_id
print "\n"
print 100.object_id
print "\n"

これを実行すると

201
201

となります。 1行目の整数100と3行目の整数100は同じオブジェクトidなので、インスタンスとしても同じとわかります。

Rubyの変数とCの変数の違い

Rubyの変数はオブジェクトを指しているだけで、変数自体に型はありません。

Cの場合は変数に型がかならず付けられます。

int n;

n = 10;

変数nはint型(整数型)として定義されます。 このとき、Cではnに対して整数を格納できるサイズのメモリを割り当てます。 n=10;の代入文で、そのメモリに10が格納されます。

このようにCでは型とメモリが変数に結びついています。

int a, b;

a = b = 100;

このとき、aとbはそれぞれメモリを割り当てられて、100はそれぞれのメモリに代入されます。 このあと、aのメモリを変更してもbの値は変わりません。

Rubyの場合は

a = b = "free\n"

a[0] = "t"

aとbは同じオブジェクトを指しているので、aがオブジェクトの内容を変更すると、bも同じものを参照しているので内容が変わります。

C言語習得者がRubyを習うと、このあたりで躓くことが多いと思います。 Rubyの変数はCのポインタのようなものだと思うとわかりやすいと思います。

さて、最後に「オブジェクト」と「インスタンス」についてもう一度。 今回は「インスタンス」を多用しましたが、Rubyのドキュメントでは「オブジェクト」の方が多く用いられています。 今後もそのときの文脈でどちらかを選択して使いますが、意味は同じだと考えてください。

徒然Ruby(4)トップレベルのメソッド定義

今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。

トップレベルに限ればメソッド定義はCの関数定義とほとんど変わりません。

メソッド定義と実行

メソッドはdefからendまでで定義をすることができます。

def example
  print "Hello world.\n"
end

example

このプログラムを説明しましょう。

  • defの後にメソッド名を書く。 このメソッドはexampleという名前のメソッド。
  • endの前までがメソッドの本体。 ここでは「Hello world.と表示する」ことを定義。 定義中は実行はしない。 定義と実行は別
  • メソッドを呼び出すにはメソッド名を書く。 メソッドは呼び出されると、自身を実行する

このプログラムexample4.rbというファイルの保存し、実行すると次のようになります。

$ ruby example4.rb
Hello world.
$

定義したメソッドは何回でも呼び出せます。

def example
  print "Hello world.\n"
end

example
example
example

これを実行すると

$ ruby example4.rb
Hello world.
Hello world.
Hello world.
$

となります。 また、前回学んだイテレータを使うこともできます。

def example
  print "Hello world.\n"
end

3.times do
  example
end

実行結果は前と同じです。

$ ruby example4.rb
Hello world.
Hello world.
Hello world.
$

Cを学んだ人ならば、メソッド定義はCの関数定義に似ていると思うでしょう。 現時点ではそのような理解で良いと思います。

パラメータと引数

メソッドにはパラメータを付け加えることができます。 パラメータは括弧の中にコンマで区切って書きます。

def sum(a, b)
  a + b
end

print sum(5, 10)
print "\n"

実行すると15が表示されます。

  • sumメソッドには2つのパラメータabがある
  • メソッドsuma + bを計算する
  • メソッド定義で最後に計算された値が、そのメソッドが実行されたときの「値」になる
  • print の引数はsum(5, 10)である。sum(5, 10)はメソッドsumに引数5と10を与えて実行したときの「値」である。 5と10はパラメータのaとbに代入され、メソッドにより5+10=15が計算され、15が値として返ってくる。 したがって、printは15を表示する
  • 最後にprintは改行を画面に出力する

「パラメータ(parameter)」と「引数(argument)」という言葉の区別ですが、

  • メソッド定義でメソッド名の後ろの括弧にある文字(aやb)はパラメータ
  • メソッド呼び出し(メソッドを実行すること)のときにメソッド名の後ろの括弧につけるオブジェクト(5や10)が引数

です。

Rubyでは括弧を省略することができます。 そのとき必要に応じて空白を入れて区切りを明確にします。

def sum a, b
... ... ...
print sum 5, 10

括弧は演算の順序を示すときにも使われます。

(2+3)*(4+5)

この括弧は省略できません。 省略すると

2+3 * 4+5

これは、2+12+5=19になってしまうからです。 *の両側に空白があったからといって、2+34+5を先に計算してはくれません。

Rubyではメソッド名と左括弧の間に空白を入れないことが大事です。 一般にRubyではメソッドの括弧を省略しそこに代わりの空白を入れることが可能なので、構文の解釈に曖昧さが生じます。

def double x
  2*x
end

print double (2) + 3
print "\n"

print double(2) + 3
print "\n"

これを実行すると

10
7

と表示されます。 最初のdoubleの呼び出しではdouble(の間に空白があるので、メソッドの括弧が省略され、(2)が計算の順序を表す括弧だと解釈されたのです。 それで、まず(2)+3=5を計算して、その5をメソッドdoubleに引数として渡し、2倍され、10が表示されたのです。 つまり、

double((2)+3)

と解釈されたのです。

2番めのdoubleの呼び出しではdoubleの直後に(があるので、括弧が引数を表す括弧だと解釈され、2が倍になって4、そのあと3が加えられて7が表示されました。

(double(2))+3

このようなことが生じるのは、Rubyが括弧の省略を許しているためです。 とにかく、メソッドの括弧はメソッド名の直後に書くようにしてください。

if文

多くの言語と同じようにRubyにもif文があります。

def even_or_odd(n)
  if n.even?
    print "偶数です\n"
  else
    print "奇数です\n"
  end
end

even_or_odd(3)
even_or_odd(6)

実行すると

奇数です
偶数です

となります。

  • メソッド名にアンダースコア(_)も使える
  • パラメータnは整数だと仮定する。整数のメソッドeven?を呼び出す。 このメソッドはnが偶数ならtrue(真)、そうでなければfalse(偽)を返す。
  • if文では、ifの後ろが真であれば直後を実行し、偽であればelse以下を実行する。 なおif文の「真」とは、「falseでもnilでもない」こと。 falseやnilもオブジェクトの一種。 したがって、整数の0や空文字列""は真とみなされる。

ifの右側を「条件」といいますが、ここにはいろいろな演算子が使えます。 良く用いられるのは

  • == 等しい
  • > 左が大きい。>=左が大きいかイコール
  • < 右が大きい。<=右が大きいかイコール

などです。

  • else以下は省略できる
  • ifとelseの間にelsifを入れることができる。 前のif(またはelsif)の条件が偽でelsifの条件が真のとき、直後の文が実行される

再帰呼出し、パラメータのスコープ

メソッドのパラメータはそのメソッドの中でのみ有効です。 これはメソッド定義でもメソッド呼び出しでもそうです。 これをパラメータのスコープといいます。 スコープは有効範囲ともいいます。

メソッドが複数回呼び出されるとき、それぞれの呼び出しに対応するパラメータはすべて別変数になります。 このことは再帰呼出しをするときに重要になります。

def fact(n)
  if n<1
    nil
  elsif n==1
    1
  else
    n*fact(n-1)
  end
end

print fact(5)
print "\n" 

これを実行すると

120

と表示されます。

  • ここでは引数nは整数であると想定する
  • 引数nが1より小さければnilを返す
  • 引数nが1ならば1を返す
  • それ以外(1より大きな整数)のときは、nにn-1のときのfactの値を掛けて返す

最後が複雑ですが、順に考えればわかりやすいです。

  • n=2のときは「n=1のときのfactは1なので、2*1=2」
  • n=3のときは「n=2のときのfactは2なので、3*2=6」
  • n=4のときは「n=3のときのfactは6なので、4*6=24」
  • n=5のときは「n=4のときのfactは24なので、5*24=120」

このメソッドは定義の中で自分自身を呼んでいます。 これを「再帰呼出し」といいます。 再帰呼出しのとき、そのどの呼び出しにおいてもパラメータは別物なので、このような計算結果になります。

再帰呼出しは、この例の階乗(factorial)計算に限らず、使えるケースが少なくありません。

まとめ

  • トップレベルのメソッドはCの関数(あるいは他の言語の関数や手続き)のように使うことができる
  • メソッドにはパラメータをつけることができる。 パラメータはそのメソッドの中でのみ有効
  • パラメータはメソッド名直後(から次の)まで。 括弧は略すことができる。 そのときは左括弧の代わりに半角空白を入れる
  • メソッドを再帰呼出しすることができる
  • if - elsif - else - end が使える

徒然Ruby(3)ブロックとイテレータ

前回の整数の後半の内容はあまりプログラミングで使われない内容になっていました。 すみません。 今回は極めて有用なことを書きます。 ブロックとイテレーションです。

繰り返し

同じことを何回もやるときのブログラムについて述べます。例えばHello worldを3回表示したければ、

print "Hello world.\n"
print "Hello world.\n"
print "Hello world.\n"

でも良いのですが、もっと良い方法があります。 整数のメソッドにtimesというのがあります。 これはそのメソッドに付属するブロックをその整数の回数だけ繰り返すというメソッドです。

3.times do
  print "Hello world.\n"
end

doからendまでを「ブロック」といいます。 ブロックにはRubyの任意のプログラムを書けます。 timesメソッドはその整数の回数だけ繰り返すので、10回繰り返したければ10のtimesメソッドを使えば良いのです。

10.times do
  print "Hello world.\n"
end

他の言語にもブロックがあるのかどうかは知りませんが、少なくともCにはありません。 Cで同じようなことをするには関数へのポインタを使いますが、Rubyのようにスマートにしかも短く書くことはできません。

timesのように繰り返しをするメソッドでループを表現することをイテレーションといいます。

uptoメソッド

整数のメソッドで有用なもう一つはuptoです。 このメソッドには引数があります。

1.upto 5 do |i|
  print i
  print "\n"
end

整数1のuptoメソッドは1から始めて引数の5までをブロックのパラメータのiに代入してループします。 パラメータの文字はiでなくても、例えばnumberのような6文字でも構いません。 この文字は変数というものの一種です。 変数は文字列ではないのでダブルクォートで囲むことはしません。 変数については別の回で詳しく説明します。

パラメーターはそのブロックの中でのみ有効です。iには最後に5が代入されますが、ブロックが終了すると、iは消えてしまいます。ブロックの外でiを参照しても5にはなりません。

ブロックのパラメータは縦棒|で囲むことになっています。 それに対してメソッドの引数はカッコ()で囲むか、半角空白でメソッドと引数を離すのでした。 メソッドとブロックは似ていますが、本質的に違うものです。

実行すると

1
2
3
4
5

と表示されます。

uptoに似たメソッドでdowntoがあります。 これはその整数から1ずつ小さくなって、引数まで繰り返します。

10.downto(6) do |i|
  print i
  print "\n"
end

ここではメソッドdowntoの引数にカッコをつけました。 半角空白で離してカッコを省略する書き方もできます。 実行すると

10
9
8
7
6

と表示されます。

ブロックの2つの書き方

ブロックは、do, endで囲みましたが、これを波カッコにすることもできます。

1.upto(5) {|i| print i; print "\n"}

波カッコでは、ブロックが短く、1行で書ける時に使う人が多いと思います。 注意すべきは

  • 引数にはカッコをつけなければいけない。 メソッドとカッコの間に半角空白を入れない。 この理由は、Rubyがdo-endよりも{ }の方がその前の引数と強く結びつくためです(構文解析上の問題)。
  • 1行に2つの文を入れるときはセミコロンで区切る

ということです。

私は以前はdo-endを多く使っていましたが、最近は波カッコが多くなってきました。 というのは、Rubyのメソッドは優れていて、一行でブロックが収まることが多いからです。 どちらを使うかはメソッドによって9割くらい決まるように思います。 今回のtimes、upto、downtoはdo-endを使うことが多いかもしれません。

まとめ

今回はRubyの最も優れた点のひとつであるブロックを紹介しました。 ブロックは基本的にメソッドの引数の最後に現れます。 ブロック単独で現れることもあるのですが、それはかなり高度な話になります。 通常はブロックはメソッドと一緒に使われると考えて差し支えありません。

次回はメソッドをトップレベルで定義することを扱おうと思います。 この場合のメソッドは今回のブロックにちょっと似ています。

徒然Ruby(2)整数

徒然なるままにRuby(2)整数

整数

Rubyで整数を使うことができます。

print 10
print "\n"

example2.rbに保存して実行すると、

$ ruby example2.rb
10
$

計算

整数は計算できます。

print 1+2
print "\n"
print 10-3
print "\n"
print 2*3
print "\n"
print 10/2
print "\n"
print 20/3
print "\n"

アスタリスク('*')は掛け算の記号です。 実行すると(以下コマンド行は省略します)、

3
7
6
5
6
  • printは計算結果を表示します。 計算の式自体(例えば「1+2」)は表示されません。
  • 20/3では、あまりは捨てられます。

カッコを使った複雑な計算も可能です。

print (2-4)*(3+5)
print "\n"

実行すると-16が表示されます。

(2-4)*(3+5) = (-2)*8 = -16

きちんと計算できていたことがわかります。

大きさの上限はない

多くのプログラミング言語では整数の上限がありますが、Rubyにはありません。

print 123456789123456789*123456789123456789
print "\n"

実行すると

15241578780673678515622620750190521

計算が合っているかどうか確かめるのは大変なのでやりませんが、エラーにならずに実行されたのは分かると思います。

桁数の多い数字にはコンマをつけますが、Rubyではコンマのかわりにアンダースコア(_)を使うことができます。

print 1_000_000_000_000

一兆が表示されます。

1000000000000

表示される方にはアンダースコア(あるいはコンマ)が出ないので分かりにくいですね。 表示でコンマをつける方法は今回の内容の範囲を超えるので、本文では扱わず、最後にオマケとして説明します。

なお、アンダースコアは単に無視されているだけなので、数字の先頭以外であればどこに書いても構いません。

メソッド

Rubyでは整数はオブジェクトの一種です。 オブジェクトはメソッドを持っています。 メソッドはオブジェクト自身に対して、なんらかの操作をします。

例えば、その整数の絶対値をとるメソッドはabsです。 絶対値absolute valueの最初の3文字です。 使い方はドットとabsを整数の後ろにつけます。

print 20.abs
print "\n"
print -30.abs
print "\n"

実行すると

20
30

が表示されます。

gcdは最大公約数(greatest common divisor)を計算するメソッドです。 最大公約数には2つの整数が必要です。 もうひとつの整数はgcdのあとにカッコとともに付け加えます。 カッコを省略して半角スペースで区切ることもできます。

print 12.gcd(18)
print "\n"
print 20.gcd 30
print "\n"

実行すると、

6
10

と表示されます。

いくつかのメソッドを紹介すると

メソッド 説明
lcm(整数) 最大公約数を計算
even? 偶数であればtrue奇数であればfalse
odd? 偶数であればfalse奇数であればtrue

コンマ区切りをつけて数字を表示

この項目の内容は記事のカバーできる範囲を越えているので、参考程度に見てください。

yukichi gemをインストール

$ gem install yukichi

例えば1234567890をコンマ区切りをつけて表示するには

require "yukichi"

print Yukichi.new(1234567890).jpy_comma()
print "\n"

実行すると

1,234,567,890

yukichi gemの中では次のようなメソッドを使っています。

1234567890.to_s.gsub(/(\d)(?=(\d{3})+(?!\d))/, '\1,')

これを使えばgemのインストールは不要です。

また、別の方法としては

1234567890.digits(1000).reverse.inject{|a,b| a.to_s+','+sprintf("%03d",b)}.to_s

でもできます。 最後の方法は私自身の考案です。

徒然なるままにRuby(1)Hello world

「徒然なるままに」をネットで調べてみると、「することもなく、手持無沙汰なのにまかせてという意味」とありました。 まさに、自分の現状を言い当てた言葉。 しかも、ブログに書くネタもなかなか思いつかない日々。

今回はそんな自分が、思いつくままにRubyの話を書いてみることにしました。 チュートリアルではありませんが、Rubyをはじめて学ぶ人向けに書くことにします。 (どうせ難しいことは書けない・・・)

インストール

自分の環境がLinuxなので、WindowsMacについては基本的にサポートできません。 Rubyのウェブサイトにインストール方法があるので見てください。

Linuxでは最も簡単なのはディストリビューションのパッケージをインストールすることです。 UBUNTUであれば、

$ sudo apt install ruby

です。

最新版をインストールする場合はRbenvを使います。 使い方についてはこちらを参照してください。 結構面倒ですが、書かれてあるとおりにやればインストールはできます。

Hello world

どんな言語でも最初に習う定番は「Hello world.」です。 画面にその文字列を表示する例のアレです。

print "Hello world.\n"

これをエディタで書き、example1.rbのファイル名で保存します。

  • printと"Hello world.\n"の間には半角空白がある。
  • printには、半角の後に「何か」が続く。その「何か」をprintの「引数(ひきすう)」という。 printは引数を画面上に表示する
  • "Hello world.\n"のように、ダブルクォート(")で囲まれたものをRubyの「文字列」という。 「\n」は改行を表す

実行するとHello world.が表示され、改行されて次のプロンプトが現れます。

$ ruby example1.rb
Hello world.
$

このようにプログラムを実行するには「ruby プログラム名」とタイプすればOK。 ここまでで、プログラムを書いて「.rb」拡張子で保存し、それを実行することができました。

このときRubyはおおまかにいって次のようなことをしています。

  • コマンドラインから引数example1.rbをつけてRubyが起動された
  • Rubyは引数のexample1.rbをオープンして読み込む
  • print、半角空白、"Hello world.\n"がプログラムであることを理解=>半角空白は区切りの意味なので、Ruby的にはprintと"Hello world.\n"と認識
  • 「print=>画面出力」「"Hello world.\n"=>文字列」とプログラムを解釈=>画面にHello worldと改行を出力

つまり、Rubyはプログラムを読み、解釈し、実行するのです。 プログラムすることによって、Rubyに様々な仕事をさせることができる、これがプログラム言語です。

文字列は何でも良い

文字列は何でも良いです。 英語でなく、日本語でも大丈夫です。 例えば、Hello worldの代わりに「おはよございます」にするには

print "おはようございます\n"

とすれば良いのです。

$ ruby example1.rb
おはようございます
$

要するにダブルクォートで囲んでやれば文字列になるということです。 本当はもっとたくさんのルールがあるのですが、それはおいおい説明することにします。

次回は整数を見ていきます。