Rubyにある程度触れた状態で、現在改めてRubyのチェリー本に目を通しています。
とても勉強になることが多いので、その学びをここに残しておきます。
4章では特にRubyの配列に関する機能を紹介されています。
配列の特徴とその他
delete_at
配列内の特定の位置にある要素を削除
この時、削除した値が戻り値になる。
a = [1, 2, 3]
a.delete_at(2) # => 3
a = [1, 2]
配列を使った多重代入
a, b = 1, 2
a, b = [1, 2]
条件演算子
条件式 ? 真の場合の処理 : 偽の場合の処理
numbers = [1, 2, 3, 4]
numbers.each do |n|
sum_value = n.even? ? n*10 : n
end
この時条件式にeven?やinclude?などの、?を扱ったメソッドも利用できる。(割と当然だけど。
select
各要素に対して、ブロックを評価し、その戻り値が真の要素を集めた配列を返すメソッド
num = [*1..6]
evens = num.select(&:even?)
evens = [2, 4, 6]
範囲 Range
Rubyには「1から5まで」「文字‘a’から文字‘e’まで」のように、値の範囲を表すオブジェクトがあります。これを範囲オブジェクトと言います。
範囲オブジェクトは次のように..または...を使って作成します。
最初の値..最後の値(最後の値を含む
最初の値...最後の値(最後の値を含まない)
配列より範囲オブジェクトを扱った方が利便性が高い例
1. n以上m以下、n以上m未満の判定
<や>=のような不等号よりも、範囲オブジェクトを使ったほうがシンプルになる
2. case文
case age
#0歳から5歳までの場合
when 0..5
0
#6歳から12歳までの場合
when 6..12
300
#13歳から18歳までの場合
when 13..18
600
#それ以外の場合
else
1000
end
3. 値が連続する配列を作成する(splat展開)
[*1..5] #=>[1,2,3,4,5]
[*1...5] #=>[1,2,3,4]
改めてテスト駆動開発
TDDを扱った方ががいいと思われる場合の条件
- プログラムのインプットとアウトプットが明確である
- テストコードの書き方が最初からイメージできる
この2つの条件が揃っている場合は、TDDが向いている場合が多い。
TDD手順
① テストコードを書く。
② テストが失敗することを確認する。
③ 1つのテストをパスさせるための仮実装を書く。("Fake It")
④ テストがパスすることを確認する。
⑤ 別のテストパターンを書く。("三角測量")
⑥ テストが失敗することを確認する。
⑦ 仮実装ではなく、ちゃんとしたロジックを書く。
⑧ テストがパスすることを確認する。
⑨ ロジックをリファクタリングする。
⑩ テストがパスすることを確認する。
配列についてもっと詳しく
要素の取得方法
a = [1,2,3,4,5]
a[1,3] #=> [2,3,4] # 2つ目の要素から、3つ分の要素を取り出す
a.values_at(1,2,3) #=> [2,3,4] # 要素を指定
a.first(2) #=> [1,2]
a.last(2) #=> [4,5]
配列の連結
2つの配列を連結したい場合はconcatメソッドか、+演算子を使う
違いは元の配列を変更するかどうか。
a = [1,2]
b = [3,4]
========================
a.concat(b) #=> [1,2,3,4]
a #=> [1,2,3,4] aは変更されている(破壊的)
b #=> [3,4] bは変更されていない
========================
a + b #=> [1,2,3,4]
a #=> [1,2] aもbも変更されていない(非破壊的)
b #=> [3,4]
配列と集合
配列は|、-、&を使って、和集合、差集合、積集合を求めることができます
いずれも、元の配列は変更しません
a=[1,2,3]
b=[3,4,5]
a|b #=>[1,2,3,4,5]
a-b #=>[1,2]
a&b #=>[3]
多重代入で残りの全要素を配列として受け取る
変数に*をつけると、残りの全要素を配列として受け取ることができる。
e,*f = 100, 200, 300
e #=> 100
f #=> [200,300]
引数として配列を渡す場合に、配列から各要素に展開する
a = [1,2]
b = [3,4]
a.push(3,4) #=> [1,2,3,4]
a.push([3.4]) #=> [1,2,[3,4]] # a.push(b)と同義
a.push(*[3.4]) #=> [1,2,3,4] # a.push(*b)と同義
メソッドの可変長引数
上記のpushメソッドにおける、個数に制限のない引数のことを、可変長引数と言います。
自分で定義したメソッドで可変長引数を使いたい場合は、引数の手前に*をつける。
splat展開の一種
def メソッド名(引数1,引数2,*可変長引数)
#メソッドの処理
end
def greeting(*names)"
#{names.join('と')}、こんにちは!"
end
greeting('田中さん') #=>"田中さん、こんにちは!"
greeting('田中さん','鈴木さん') #=>"田中さんと鈴木さん、こんにちは!"
greeting('田中さん','鈴木さん','佐藤さん') #=>"田中さんと鈴木さんと佐藤さん、こんにちは!"
%記法で文字列の配列を作る
#[]で文字列の配列を作成する
['apple','melon','orange'] #=>["apple","melon","orange"]
#%wで文字列の配列を作成する(!で囲む場合)
%w!applemelonorange! #=>["apple","melon","orange"]
#%wで文字列の配列を作成する(丸カッコで囲む場合)
%w(applemelonorange) #=>["apple","melon","orange"]
式展開や改行文字(\n)、タブ文字(\t)などを含めたい場合は、%W(大文字のW)を使います。
配列の初期値
指定なしの場合は、初期値はnil
# a = [] と同じ
a = Array.new
a = Array.new(5)
#=> [nil,nil,nil,nil,nil]
a = Array.new(5, "default") # 初期値をdefaultと設定
#=> ["default","default","default","default","default"]
a=Array.new(10){|n|n%3+1} # 初期値をブロックで指定
#=> [1,2,3,1,2,3,1,2,3,1]
ただし、第2引数を使って初期値を指定する場合は注意が必要。これは配列の全要素が同じオブジェクトを参照する事になる**。**
なので、上記の例の場合で試すとこんな感じ。
a = Array.new(5, "default") # 初期値をdefaultと設定
#=> ["default","default","default","default","default"]
a[0].upcase! #=> "DEFAULT" (破壊的変更)
a
#=> ["DEFAULT","DEFAULT","DEFAULT","DEFAULT","DEFAULT"]
ブロックについてもっと詳しく
with_indexメソッド
eachやmapなどの繰り返し処理に対して、ブロック引数の第二引数に添字を渡してくれるメソッド。
fruits=['apple','orange','melon']
fruits.each_with_index{|fruit,i|puts"#{i}:#{fruit}"}
#=>0:apple#1:orange#2:melon
fruits.map.with_index do |fruit,i|
"#{i}:#{fruit}"
end
#=>["0:apple","1:orange","2:melon"]
さらにwith_indexについて
技術的にはこのメソッドは、Enumeratorクラスのインスタンスメソッドである。
そしてeachやmapなど繰り返し処理を行うメソッドの大半は、ブロックを省略して呼び出すと、Enumeratorオブジェクトを返すようになっている。
この性質により、あたかも繰り返し処理と組み合わせて実行しているように見える。
配列がブロック引数に渡される場合
dimensions = [
[10,20],
[30,40],
[50,60],
]
areas=[]
========================
# ブロック引数が1個であれば、ブロック引数の値が配列になる
dimensions.each do |dimension|
length = dimension[0]
width = dimension[1]
areas << length*width
end
areas #=> [200,1200,3000]
========================
# 配列の要素分だけブロック引数を用意すると、各要素の値が別々の変数に格納される
dimensions.each do |length,width|
areas << length*width
end
areas#=>[200,1200,3000]
========================
# 3つの値をブロック引数に渡そうとするが、2つしかないので3つめの値は捨てられる
dimensions = [
[10,20,100],
[30,40,200],
[50,60,300],
]
dimensions.each do |length,width|
p [length,width]
end
#=> [10,20]
# [30,40]
# [50,60]
========================
# ブロック引数を()で囲んで、配列の要素を別々の引数として受け取る
dimensions=[
[10,20],
[30,40],
[50,60],
]
dimensions.each_with_index do |(length,width),i|
puts "length:#{length},width:#{width},i:#{i}"
end
#=> length:10,width:20,i:0
# length:30,width:40,i:1
# length:50,width:60,i:2
配列をもっと上手に使いこなすために
配列には様々なAPIが揃っている。自分で頑張ってコードを描かなくても、用意されている場合の方が多いかもしれない。
その場合、配列については、Arrayクラス自身に定義されてるものと、Enumerableモジュールに定義されているもの、の2つに大別できるので、そこを確認すると良いとのこと。
繰り返し処理とEnumerableモジュール
maoメソッドやselectメソッドはArrayクラスではなく、Enumerableモジュールに定義されている。
配列のArrayクラスや、
範囲オブジェクトのRangeクラス、
uptoメソッドの戻り値であるEnumeratorクラスは、
全てこのEnumerableモジュールをincludeしている。
そのため、どのクラスもEnumerableモジュールのメソッドが扱える。
余談
その他にも個別のメソッドなどについて、詳細に説明されている章であった。
とてもボリューミーである。
ただこの章の最後の部分である、"Enumerableモジュール"などについての説明を読み、まだクラスやモジュールに関する理解は半端ではあるが、これまで何も考えず行っていた作業の違いを認識できるようになった。
また引数や戻り値など、各メソッドの要素やその結果などについて、これまで深く考えてこなかったが、そこを理解できることができれば、よりRubyを使いこなせるようになりそう。