>
== なにこれ ==
* 2013年度新入生向けRuby講習会の第2回向け資料です。
* まず第1回向け資料をお読みください。
* [[alstamber/2013FreshmanRubySeminar]]
== 読みやすいプログラムを書こう ==
=== スペースを有効活用 ===
演算子や代入のイコールの前後などにはスペースをあけるようにしましょう。
{{{#!highlight ruby
str="Love Live!"
str2="Tachibana Arisu"
arr=["hoge","fuga","piyo"]
}}}
{{{#!highlight ruby
str = "Love Live!"
str2 = "Tachibana Arisu"
arr = ["hoge", "fuga", "piyo"]
}}}
=== 名前が8割 ===
この講習会で書いているような小さなプログラムであれば変数の名前は適当でも別に困りませんが、大きなプログラムを書くようになると<
>
変数がどのようなデータと紐付けられているのか、名前を見るだけでわかるようにしておくとはかどります。
{{{#!highlight ruby
a = 1011203
}}}
{{{#!highlight ruby
student_number = 1011203 #=>学籍番号だということが一発でわかる
}}}
単語の区切りをアンダーバーで表す方法をスネークケース、単語の区切りを単語の頭を大文字にすることで表す方法をキャメルケースと言います。
{{{#!highlight ruby
student_number = 1011203
studentNumber = 1011203
}}}
キャメルケースやスネークケースで長々と単語をつなげるとやはり読みにくくなるので、できるだけ短い文字数で的確に変数の性質を説明できるような名前をつけられるようにしたいものです。
=== インデント ===
行の頭をある一定の文字数分下げることをインデントと呼びます。
{{{#!highlight ruby
if str == str2
puts str #=>インデントされている
end
}}}
インデントのないプログラムは一般に読みにくくなることが多いです。
{{{#!highlight ruby
if str == str2
puts str
puts str2
f(str)
g(str)
end
}}}
インデントは普通4文字分することが多いようです。メジャーなエディタならスペースを律儀に押さなくてもTabキーを押せば最適なインデントをしてくれます。<
>
tabは便利ですが、開くエディタによって何文字分ずれるかが変わってしまう可能性があるので、tabを自動的にスペースに置き換えてくれるようにエディタで設定しておくと良いです。<
>
vimの場合~/.vimrcに
{{{
set expandtab
}}}
と書くと良いです。
== 変数のスコープ ==
演習問題を解いてもらう過程で割と質問が多かったので'''変数のスコープ'''と呼ばれるものについて説明しておきたいと思います。
例えば次のプログラムを考えます。
{{{#!highlight ruby
5.times do |i|
a = i + 1
puts a
end
puts a
}}}
このプログラムは実行時にエラーが出ます。 エラーの原因となっているのは最後の行です。<
>
最後の行ではaという変数の内容を表示するように記述していますが、Rubyが「aなんて変数はないよ!」と言ってエラーを出してしまうのです。
aという変数はtimesメソッドの中で登場します。<
>
しかしRubyの規則として'''「ブロックの中で初めて登場した変数はブロックの中でしか有効にならない」'''というものがあります。<
>
ブロックというのは例えばdo endで囲まれているもの、for endで囲まれているものなどがあります。if式では
{{{#!highlight ruby
if 条件1
#ここが一つの
#ブロック
elsif 条件2
#ここが一つの
#ブロック
else
#ここが一つの
#ブロック
end
}}}
となります。
例えば次のようにプログラムを書き換えればエラーを消すことはできます。
{{{#!highlight ruby
a = 0
5.times do |i|
a = i + 1
puts a
end
puts a
}}}
このプログラムではaという変数がブロックの外で初めて登場しているので、aはブロックの中だけではなくプログラム全体で有効になっているからです。
=== 考えてみよう ===
* 上のプログラムを実行するとどのような出力が得られるでしょうか。
== Tips ==
=== forとRubyの文化 ===
僕自身がRubyを始めてまだ1年も経っていないのでなんとも言えないのですが、Rubyではあまりforを使わない傾向にあるようです。<
>
第1回で述べましたが、Rubyにおいて
{{{#!highlight ruby
for hoge in fuga
...
end
}}}
は
{{{#!highlight ruby
fuga.each do |hoge|
...
end
}}}
に完全に等価です。そこでどちらかの書き方を使うことにすれば、もう一方の書き方をあえて使う必要はなくなるということになります。
Rubyではeachメソッドのように後ろに処理の塊がくっついたメソッドのことを「ブロック付きメソッド」と呼びます。<
>
Rubyではブロック付きメソッドはeachメソッドのようなイテレータだけではなく色々な所で活用されますし、またプログラムを書く際に自分でブロック付きメソッドを作ることも多くあります。<
>
(ブロック付きメソッドの作り方については第2回では触れません)
このようなある意味での「文化」が根付いているのでRubyではeachメソッドを使うことがほとんどです。
for式は他の言語(たとえばC言語)のように「forなんとかかんとか」という書き方でループを表現するのが一般的な言語との互換性のために用意されているといった感じです。
{{{#!highlight c
//C言語でのループの例(気持ちで読んでね!)
int n = 5;
int a[n] = {1, 2, 3, 4, 5};
int i;
for(i=0; i>
Rubyはオブジェクト指向言語と呼ばれる言語の一種です。字の通り、オブジェクトというものを中心に考えたプログラミングの考え方を取り入れた言語です。<
>
オブジェクト指向は現代のプログラミングの考え方の主流の一つとなっている考え方なので、この際その考え方に触れておくことにしましょう。
オブジェクト指向はRubyだけではなくJavaやC++といった現在主流となっている言語に採用されている考え方です。
=== オブジェクトって何 ===
オブジェクトというのはデータとそれに関連する処理をひとまとめにした概念といえます。<
>
と言われてもわかりにくいと思うので、実際の例で考えてみることにしましょう。
文字列を考えます。Rubyでは文字列は次のように書きますね。
{{{#!highlight ruby
"Love Live!"
}}}
プログラムはデータに対して加工や処理を行なっていくことの繰り返しだということを第1回で述べました。<
>
なのでデータに対してよく行われるであろう処理や加工をデータそのものに紐付けておくと何かと便利なのではないかという気がします。<
>
データとそれに関連する手続きをバラバラにもっておくのではなく、一体のものとして扱う。これがオブジェクト指向の考え方の1つの基本です。
たとえば文字列に対して行う処理として文字列の長さを求めるというものが考えられます。オブジェクト指向ではない言語とRubyを比較してオブジェクト指向的な<
>
考え方というのはどういうものなのかをみていきましょう。
オブジェクト指向ではない言語の例としてCを挙げることにします。Cで文字列の長さを求めるにはstrlenというものを使います。
{{{#!highlight c
char[] a = "Love Live!";
strlen(a); //strlenという処理はaという文字列に属しているわけではなく、独立して存在している
}}}
対してRubyの例です。Rubyで文字列の長さを求めるにはlengthメソッドを使います。
{{{#!highlight ruby
a = "Love Live!"
a.length #aという文字列に関連付けられているlengthメソッドを使っているという雰囲気
}}}
=== データと処理を一体として扱うメリット ===
文字列の長さのような例だとあまりこのような手法のメリットは感じられないかもしれません。<
>
巨大なプログラムにおいてこの手法は威力を発揮します。
Cのような非オブジェクト指向言語ではデータと処理が別のものとして扱われるため、すべてのデータと処理が一緒くたになってプログラムに記述されることになります。<
>
これはプログラムが読みにくくなり、開発しにくくなるという結果を招きやすくなります。
オブジェクト指向を取り入れると、あるデータとそれに紐付けられた処理を一つの塊として切り出すことができるので、プログラムをその塊の組み合わせとして記述しやすくなります。<
>
つまり大きなプログラムでもひとつの大きな塊ではなく、もう少し小さな単位の集合として作ることができ、プログラムが書きやすくなるというわけです。
=== 文字列はオブジェクト ===
もう一度先の例に戻ってみてみましょう。
{{{#!highlight ruby
a = "Love Live!"
a.length
}}}
この例でaは"Love Live!"という文字列であり、かつlengthという文字列の長さを求める手続きも持っている存在であるということができます。<
>
つまりRubyでは文字列は文字列そのもののデータとそれに関連する手続きをもった'''オブジェクト'''です。
文字列オブジェクトは何もlengthメソッドしか持っていないわけではありません。例えばreverseというメソッドがあります。
{{{#!highlight ruby
"Love Live!".reverse #=> "!eviL evoL"になる
}}}
どんなメソッドがあるのかはググれば色々出てくると思います。
=== 配列もオブジェクト ===
Rubyではなにも文字列だけがオブジェクトとして扱われているわけではありません。配列も立派なオブジェクトです。例えば配列オブジェクトにはlengthというメソッドがあります。
{{{#!highlight ruby
["Kyoko", "Toshino"].length #=> 2になる
}}}
reverseメソッドもあります。
{{{#!highlight ruby
["Kyoko", "Toshino"].reverse #=> ["Toshino", "Kyoko"]になる
}}}
=== Rubyでは全てがオブジェクト ===
Rubyは文字列や配列にかぎらずすべてのデータあるいはデータ構造がオブジェクトとして扱われます。<
>
(一方例えばJavaではオブジェクトととして扱われないデータが存在します)
例えば整数もオブジェクトです。timesメソッドを思い出してみましょう。
{{{#!highlight ruby
5.times do ......
}}}
見て分かる通り、timesメソッドは整数オブジェクトに属するメソッドなわけです。
== オブジェクトとメソッド ==
=== メソッドの呼び出し方 ===
もうわかっている人も多いと思いますが、オブジェクトに属しているメソッドを呼び出すには次のように表記します。
{{{#!highlight ruby
(オブジェクト).(メソッド名)
}}}
例えば
{{{#!highlight ruby
"hoge".length
}}}
や
{{{#!highlight ruby
a = ["love", "live"]
a.sort
}}}
などです。
「.」は「+」や「-」よりも強い結びつきの力を持っています。なので例えば
{{{#!highlight ruby
n-1.times
}}}
などと書くと、nから1.timesの結果を引いたもの、というふうに解釈されます。これを防いで、n-1という数値オブジェクトにtimesメソッドを適用するには
{{{#!highlight ruby
(n - 1).times
}}}
と書きます。
=== 引数と返り値 ===
putsメソッドを思い出してみましょう。putsメソッドは与えられた文字列を画面に出力するというメソッドでした。
{{{#!highlight ruby
puts "Love Live!"
}}}
メソッドに与えられるデータのことを引数(ひきすう)といいます。与えられる引数の種類(文字列か数値かなど)とその数はメソッドごとに決まっています。<
>
たとえばlengthメソッドは引数の個数が0個です。
メソッドを呼び出したあとの実行結果のことを戻り値または返り値といいます。lengthメソッドの戻り値は文字列の長さになります。
引数が複数あるようなメソッドについて文字列オブジェクトに属しているinsertメソッドを例に見ていくことにしましょう。
insertメソッドは整数と文字列の値をとって、引数の文字列を引数の整数番目の直前に挿入するというメソッドです。
{{{#!highlight ruby
a = "Live!"
puts a.insert(0, "Love ")
}}}
==== 考えてみよう ====
* putsメソッドの引数はなんですか?
* insertメソッドの引数はなんですか?
* insertメソッドの戻り値はなんですか?
* 次のプログラムは実行できるでしょうか?
{{{#!highlight ruby
a = "Live!"
puts a.insert("Love ", 0)
}}}
* 次のプログラムは実行できるでしょうか?
{{{#!highlight ruby
b = 1
puts b.insert(0, "Love ")
}}}
* 次のプログラムは実行できるでしょうか?
{{{#!highlight ruby
c = ["live"]
puts c.insert(0, "love")
}}}
* (上級)putsメソッドの戻り値はなんですか?
=== 真偽値 ===
今までデータの種類として数値や文字列を扱って来ましたが、真偽値というものについても紹介しておきます。<
>
真偽値というのは、真か偽のいずれかの値をとるようなものです。
以前if式を紹介した時にif式には条件を与える、という話をしました。<
>
この条件が成立している時が真に相当して、成立していない時が偽に相当します。
つまりif式というのは
{{{#!highlight ruby
if 条件
条件が真の時に実行される処理
else
条件が偽の時に実行される処理
end
}}}
となっています。<
>
条件の部分には調べると必ず真偽値のどちらかになる式を書いておいて、判定結果によって処理が分岐するという形になっているわけです。
真偽値は数値や文字列と同じ扱いを受けるので、if式だけではなくメソッドの引数や戻り値として使うこともできます。<
>
たとえば次のようなメソッドを定義できます。numは整数値だとします。
{{{#!highlight ruby
def hoge(num)
num % 2 == 0
end
}}}
%というのは割り算の余りを求めるものなので、numが整数値だとすれば、num%2は必ず1か0になります。<
>
それが0と等しいかどうかを判定しているので、全体ではnumが2で割り切れれば戻り値がtrueになって、割り切れなければ戻り値がfalseになります。<
>
つまりこのメソッドは与えられた引数の整数が偶数かどうかを判定するメソッドになっています。
=== オブジェクトの種類 ===
すでに気づいている人も多いとは思いますが、オブジェクトには幾らかの種類があります。<
>
整数であったり、文字列であったり、配列であったりです。<
>
オブジェクトの種類の違いはオブジェクトに属しているメソッドの違いにも現れています。<
>
たとえば整数のオブジェクトはreverseメソッドを持ちません。<
>
文字列のオブジェクトはtimesメソッドを持ちません。
このように呼び出せるオブジェクトに違いがあるので、Rubyの仕様側で陽にオブジェクトの種類の違いを区別できるような仕組みが必要になります。
この仕組の一つとしてクラスというものが存在します。Rubyではオブジェクトの種類ごとにクラスと呼ばれるものを1つ用意しています。<
>
クラスにはそのオブジェクトが持っているデータやメソッドが定義されています。
例えば
{{{#!highlight ruby
"Love Live!"
}}}
はStringクラスのオブジェクトである、といいます。Stringは文字列という意味です。
また
{{{#!highlight ruby
0
1
-99
934387
}}}
などはFixNumクラスのオブジェクトである、といいます。FixNumはFix Numberの略です。Fixの意味についてはコンピュータの詳しい仕組みを知っていないと理解できないので省略しますが、<
>
要するに整数を表しているということです。
{{{#!highlight ruby
["hoge", "fuga"]
}}}
はArrayクラスのオブジェクトです。Arrayは配列という意味です。
つまり
{{{#!highlight ruby
["Love", "Live!"].reverse
}}}
というふうにかけるのは、Arrayクラスにreverseメソッドが定義されているからだといえます。
=== 特定のオブジェクトに属さないメソッド ===
今まである種類のオブジェクトにのみ属するメソッドについて考えて来ましたが、そのような限定がないメソッドというのも存在します。
今まで皆さんが頻繁に使ってきたputsメソッドはその一つです。今まで皆さんがputsメソッドを実行するときに「.」を使ってオブジェクトを指定することはなかったと思います。
{{{#!highlight ruby
puts "Love Live!"
}}}
=== 特定のオブジェクトに属さないメソッドを自分で作る ===
特定のオブジェクトに属さないメソッドについては自分で独自のものを作ることがわりかし簡単に出来ます。
どのように書けばメソッドを作ることができるのかを示します。
{{{#!highlight ruby
def メソッド名 (引数)
メソッドで行いたい処理
end
}}}
メソッドを作ることを、'''メソッドを定義する'''、といいます。
例えば3つの数値を引数にとって足し算を行った結果を返すメソッドmyadd3は次のように書けます。
{{{#!highlight ruby
def myadd3 (a, b, c)
a + b + c
end
}}}
Rubyではメソッドの処理の最後の結果が自動的に戻り値になります。上のmyadd3を例にすれば、a + b + cの結果が戻り値になります。
作ったメソッドを呼び出すにはたとえば次のように書きます。
{{{#!highlight ruby
a = myadd3(1, 2, 3)
puts a
}}}
==== 考えてみよう ====
* 上のプログラムを実行すると何が出力されますか?
上のプログラムは1行にまとめて書いてしまうこともできます。
{{{#!highlight ruby
puts myadd3(1, 2, 3)
}}}
myadd3メソッドの定義と呼び出しを一つにまとめると次のようになります。
{{{#!highlight ruby
def myadd3 (a, b, c)
a + b + c
end
a = myadd3(1, 2, 3)
puts a
}}}
上のプログラムでmyadd3の戻り値を代入しているaとmyadd3の中で出てくるaは違うものになっている、という点に注意しましょう。
さて、Rubyの戻り値の仕様上、どんなメソッドも必ず戻り値を持つことになります。しかしメソッドの中には特に戻り値が必要ないものもあるでしょう。<
>
putsメソッドはそのいい例で、画面に出力さえしてしまえばいいので、メソッド自体の戻り値は別になんでもあってもいいわけです。<
>
このような場合は「何もない」ことを意味するnilというものを返すようにすることが多いです。<
>
putsメソッドもnilを返します。
{{{#!highlight ruby
def myintroduce(name)
puts "I am " + name + "."
end
}}}
上のメソッドはputsメソッドがメソッドの中で最後に処理されるものです。putsメソッドは戻り値としてnilを返すので、このメソッドの戻り値もnilです。
=== いずれにも当てはまらないメソッド ===
今まで見てきたどれにも当てはまらないメソッドというものも存在します。例としては数学の関数に相当するメソッドがあります。<
>
数学の関数に相当するメソッドはMathと呼ばれるものに属しています。
{{{#!highlight ruby
Math::sin(1) #「::」は「.」と同じ意味だと考えて良い
}}}
上の例を見ると、Mathのオブジェクトを作ってそれのメソッドを呼び出しているのではなくて、直接Mathに対してメソッドを呼び出していることがわかります。<
>
このようなメソッドについて完全に理解するためにはクラスというものについてもっと深く学ぶ必要があるので、今回はこういうものもあるんだ、という認識でいてもらえばいいです。
== 課題 ==
=== 課題1 ===
2つの数を引数にとって大きい方を返すメソッドを定義してみましょう。
=== 課題2 ===
数値の配列を引数にとって平均を求めて返すメソッドを定義してみましょう。
=== 課題3 ===
文字列の配列を引数にとって最も長い文字列を探して返すメソッドを定義してみましょう。
=== 課題4 ===
引数として与えられた整数を上限として、宿題の問9のような挙動をするメソッドを定義してみましょう。
=== 課題5 ===
極座標で点P(r, θ)が与えられた時、点Pの直交座標でのxの値を返すメソッドrectXとyの値を返すメソッドrectYを定義してみましょう。
=== 課題6 ===
円の直径d[cm]が与えられた時、円の面積S[cm^2]を返すメソッドを定義してみましょう。
=== 課題7 ===
次のプログラムで最後のputsメソッドが実行された時、何が出力されますか?<
>
(実際に実行して確かめるのは反則ですよ!)
{{{#!highlight ruby
a = 1
if a == 1
a = 2
end
5.times do |a|
a = a + 1
end
puts a
}}}
=== 課題(ちょっと上級) ===
alstamberさんはある闇金から10万円の借金をしました。この闇金は1日毎に5%の利息を借金に加え、かつ借金の1,000円以下を切り上げます。<
>
alstamberさんが借金をしてからn日後にどれだけの借金を抱えることになるのかを求めてください。
* nを引数にとってn日後の借金の額を返すメソッドを作ってください。
* メソッドを作るだけでなくその結果を出力するようにしてください。
* 余力があれば借金が2倍を超えるまでにかかる日数を求めるように改良してください。
=== 課題(上級) ===
次のメソッドは何を求めるメソッドでしょう。
{{{#!highlight ruby
def nazo(n, m)
if m.zero? #zero?メソッドは0なら真になるメソッド
n
else
nazo(m, n%m)
end
end
}}}