第54回 Ruby/Rails 勉強会@関西で初級者向けレッスンやってきた
初級者向けレッスンを担当したので、以下スライドを解説。*1
スライドだけ欲しい人は直接どうぞ。
- まずは Array のリテラルを紹介。
- 最初のは、Fixnum を 4つ持つ Array.
- 次のは、Fixnum, String, Array, Float, Symbol の 5つの要素を持つ Array.
Array とは (2)
a = [1, "two", [3, "3"], 4.0, :five] a[0] # => 1 a[-1] # => :five a[1] = "2nd" a[3, 2] # => [4.0, :five] a[1..-2] # => ["2nd", [3, "3"], 4.0] a[5] # => nil
- 添字は 0 オリジン
- 添字が負数なら後ろから数える
- 代入できる
- サイズを指定して取り出せる
- 範囲を指定して取り出せる
- 値がなければ nil
Array オブジェクトの作り方
["a", "b", "c"] # => ["a", "b", "c"] ("a".."c").to_a # => ["a", "b", "c"] [*"a".."c"] # => ["a", "b", "c"] %w[a b c] # => ["a", "b", "c"]Array オブジェクトの作り方 (2)
"No Ruby, No Life.".scan(/\w+/) # => ["No", "Ruby", "No", "Life"] "1,1,2,3,5,8".split(/,/) # => ["1", "1", "2", "3", "5", "8"]Hash とは
- 連想配列クラス
- 任意のオブジェクトを持つことができる
- 任意のオブジェクトをキーにできる
{:AAPL=>566.71, :GOOG=>605.23} {AAPL: 566.71, GOOG: 605.23} # => {:AAPL=>566.71, :GOOG=>605.23}
- つづいて Hash のリテラルを紹介。
- キーと値は => で区切る。
- 1.9 からは下の表記を使える。(よりデータの羅列に見える)
Hash とは (2)
h = {AAPL: 566.71, GOOG: 605.23} h[:AAPL] # => 566.71 h[:MSFT] = 31.16 h[:FB] # => nil
- Hash もアクセスには [ 角かっこ ] を使う
- 代入できる (値の存在しないキーに対しても可)
- 値がなければ nil
Hash オブジェクトの作り方
a = [:AAPL, 566.71, :GOOG, 605.23] Hash[*a] # => {:AAPL=>566.71, :GOOG=>605.23}順序がおかしくても気にしない
- Array から Hash を作ることが (たまに) ある。
- キーと値を羅列した Array を展開して Hash.[] で Hash オブジェクトを生成。
- ファイルから読み込んだデータを String#split して Hash を生成するような用途。
Hash[:AAPL, 566.71, :GOOG, 605.23] # => {:AAPL=>566.71, :GOOG=>605.23} Hash[:AAPL, 566.71, 605.23, :GOOG] # => {:AAPL=>566.71, 605.23=>:GOOG}要素数が奇数なら、Hash[:AAPL, 566.71, :GOOG, 605.23, :FB] # ~> odd number of arguments for Hash (ArgumentError)あまり使わないけど、使うとハマる Array の初期化Array の初期化
Array.new(4, 0) # => [0, 0, 0, 0] a = Array.new(3, "ruby") # => ["ruby", "ruby", "ruby"] a[0].upcase! # => "RUBY" a # => ["RUBY", "RUBY", "RUBY"]a = [] a[0] += 1 # ~> undefined method `+' for nil:NilClass (NoMethodError)全ての要素が同一のオブジェクトを指している。 では、どうするか? そこでブロックですよ!
- mutable なオブジェクトで初期化する際は要注意
ブロックで初期値を指定すると、別のオブジェクトになる。 レッスンではサッと流したけど以下が重要。Array の初期化 (2)
a = Array.new(3){"ruby"} # => ["ruby", "ruby", "ruby"] a[0].upcase! # => "RUBY" a # => ["RUBY", "ruby", "ruby"]次の例だと、せっかくブロック使っても無意味。
- ブロックが要素数だけ繰り返される
- ブロック内でオブジェクトを生成している
s = "ruby" a = Array.new(3){s} # => ["ruby", "ruby", "ruby"] a[0].upcase! # => "RUBY" a # => ["RUBY", "RUBY", "RUBY"]また、ブロックでは値が受け取れる。a = Array.new(3){|i| i.to_s} # => ["0", "1", "2"]Hash についても同じことがいえる。Hash のデフォルト値
hash = Hash.new(0.0) # => {} hash[:AAPL] # => 0.0 hash = Hash.new{|h, k| h[k] = ""} # => {} hash[:GOOG] # => "" hash[:IBM] # => ""※ キーの破壊
- Hash はサイズを指定しない
- サイズだけ指定されても、どのキーなんだよってことになる
- ブロックには Hash と キーが渡される
- そこでデフォルト値を生成する
キーの破壊
なので、キーを破壊すると、破壊前の格納位置に到達できなくなる (たぶん) ただし、String をキーにしても、Hash のキーを破壊することはできない。 Ruby は String を特別に扱っている
- Hash はキーの hash メソッドで値の格納位置を決める (たぶん)
- 格納位置の値が確かにそのキーの値であるか eql? メソッドで確認する (たぶん)
- String オブジェクトをコピーして
- freeze する
key = "ruby" # [あとでこわす] h = {} h[key] = "関西" h # => {"ruby"=>"関西"} h.first # => ["ruby", "関西"] h.first.first # => "ruby" h.first.first.eql? key # => true # 文字列は同じ h.first.first.equal? key # => false # 別のオブジェクト h.first.first.frozen? # => true # freeze されている key.upcase! # => "RUBY" h # => {"ruby"=>"関西"} # 破壊できない繰り返し each
[0, 1, 2].each{|i| puts i} [0, 1, 2].each do |i| puts i end # >> 0 # >> 1 # >> 2
- 繰り返しには each メソッドを使う
- ブロックは { 波かっこ } または do end で囲む
- ブロック内を繰り返す
- 繰り返しのたびに i が順番に要素を指す
Enumerable という便利なモジュールがありまして、繰り返し Enumerable
Array.ancestors # => [Array, Enumerable, Object, Kernel, BasicObject] Hash.ancestors # => [Hash, Enumerable, Object, Kernel, BasicObject]
- Enumerable
- 繰り返しを行なうクラスのための Mix-in
- クラスには each メソッドが必要
それぞれのメソッドは「るりま」を見てね。きりがないし。*5 でも、この後、しょーもない紙芝居やるよりも inject の動きとか見た方が良かったんじゃないか、とスライド作りながら思ってた。*6 inject を教科書通りに呼ぶと、こんな感じ。繰り返し Enumerable (2)
a = [2, 3, 5, 7] # => [2, 3, 5, 7] a.map{|i| i * i} # => [4, 9, 25, 49] a.select{|i| i.odd?} # => [3, 5, 7] a.inject{|s, i| s += i} # => 17 a.all?{|n| n.prime?} # => truea = [2, 3, 5, 7] # => [2, 3, 5, 7] a.inject(0){|s, i| s += i} # => 17 a.inject(0) do |s, i| puts "s = #{s} + #{i}" s += i end # >> s = 0 + 2 # >> s = 2 + 3 # >> s = 5 + 5 # >> s = 10 + 7各ループで s と i は上記の値を指している。 つづいてスライドの例a.inject{|s, i| s += i} # => 17 a.inject do |s, i| puts "s = #{s} + #{i}" s += i end # >> s = 2 + 3 # >> s = 5 + 5 # >> s = 10 + 7s の初期値が省略されると、s = a[0]; i = a[1] から繰り返しが始まる。 しかし、上級者になると、こんな書き方をする。a.inject(&:+) # => 17ブロックの引数は、代入だと思ってみる。繰り返しと多重代入
a = [[:matz, 47], [:dhh, 32]] a.size # => 2 a.each{|i| puts "#{i[0]}(#{i[1]})"} a.each{|name, age|puts "#{name}(#{age})"} # >> matz(47) # >> dhh(32)i = [:matz, 47] i # => [:matz, 47] name, age = [:matz, 47] name # => :matz age # => 47さらに無理のある例、これも代入だと繰り返しと多重代入 (2)
a = [[1, [:matz, 47]], [2, [:dhh, 32]]] a.size # => 2 a.each do |id, (name, age)| puts "#{id}: #{name}(#{age})" end # >> 1: matz(47) # >> 2: dhh(32)i = [1, [:matz, 47]] i # => [1, [:matz, 47]] id, (name, age) = [1, [:matz, 47]] id # => 1 name # => :matz age # => 47かっこがないと、id, name, age = [1, [:matz, 47]] id # => 1 name # => [:matz, 47] age # => nilHash では、みんな自然と多重代入してた繰り返しと多重代入 (3)
h = {matz: 47, dhh: 32} h.each{|i| puts "#{i[0]}(#{i[1]})"} h.each{|name, age|puts "#{name}(#{age})"} # >> matz(47) # >> dhh(32)h = {matz: 47, dhh: 32} h.each do |i| puts i.class p i end # >> Array # >> [:matz, 47] # >> Array # >> [:dhh, 32]Hash#each すると、キーと値のペアが Array で渡される。 要素の数と引数の数が合わないと、どうなるのか?*7# 引数が多い場合 i, j, k = [1, 2] i # => 1 j # => 2 k # => nil # 引数が足りない場合 i, j = [1, 2, 3] i # => 1 j # => 2 # 引数が足りなくても…… i, *j = [1, 2, 3] i # => 1 j # => [2, 3]ちょっと蘊蓄Array のコピー
a = [1, 2, 3] b = a a[0] = 0 a # => [0, 2, 3] b # => [0, 2, 3]
- 代入はコピーじゃない
- a が指す Array オブジェクトを b も指すようになった
Array のコピー (2)
a = ["a", "b", "c"] b = a.dup a[0] = "A" a # => ["A", "b", "c"] b # => ["a", "b", "c"]
- 浅いコピーなので注意!
Array のコピー (3)
a = ["a", "b", "c"] b = a.dup a[1].upcase! a # => ["a", "B", "c"] b # => ["a", "B", "c"]
- 自分が書き換えるオブジェクトは何なのか意識すること
- Array オブジェクト
- Array 要素のオブジェクト
今回、話さなかったこと
- each はメソッド
- 制御構造ではない
[].respond_to? :each # => true
- ブロックは Proc
block = Proc.new{|i| i * 2} [*0..4].map &block # => [0, 2, 4, 6, 8] block[5] # => 10
- 外部イテレータ
a = [2, 3, 5, 7] i = a.each i.next # => 2 i.next # => 3 i.next # => 5 i.next # => 7 i.next # ~> `next': iteration reached an end (StopIteration)演習問題は [あとでかく]まとめ
- Array と Hash の作り方・使い方
- 初期化・デフォルト値はブロックで
- 繰り返しはブロックで
- 浅いコピー・破壊に注意