パーフェクトRubyお勉強フェスティバル 第5章 その1

oftonkingdom.hatenablog.com

5章「主な組み込みクラス/モジュール」です.大ボリュームで大変でした.

長いため5-5 Enumerableまででその1とします.

5-1 Numeric

Numeric#step

整数型だけでなく,数値オブジェクト全てから呼び出せる.第一引数の数値を上限に,第二引数で指定した数値を足し合わせる. Integer#timesでは初期値や足し合わせる数値を指定できなかったので,そこが違いでしょうか.

ちなみに細かいですが第二引数で指定した数値を足していった結果,第一引数の上限の値と等しくならない場合について書籍に乗っていなかったのでやってみました.上限を超えないところまで足し込まれるということが確認されました.

1.5 step 9.0, 3.0 do |num|
  puts num
end

出力

1.5
4.5
7.5

Integer#nextとInteger#succの違い

以下を見ると機能的な違いはないみたい.内部の実装が違う?

class Integer (Ruby 2.5.0)

整数型,浮動小数点型の算術演算

算術演算の戻り値は、レシーバと引数どちらかが浮動小数点であれば浮動小数点となります。

例えば整数型同士の除算は結果が整数型(切り捨て)だが,いずれかが浮動小数点なら結果は浮動小数点となる.

整数の0除算の場合はZeroDivisionError,浮動小数点の場合はNaNまたはInfinityが返る.

NaNってなんなんですか?というずっと思っていた疑問. 「Not a Number」なんですね…なん…ですね

constant Float::NAN (Ruby 2.5.0)

NaN - Wikipedia

5-2 String

String#slice(部分文字列取得)

何かと使う場面が多そうなので.ショートハンドはString#[].

あとで出てくる配列でも,部分要素取得は同じくsliceというメソッドなんですよね.

str = 'ofton2018'

puts str.slice(3) #=> 'o'
puts str[3] #=> 'o'

puts str.slice(2,3) #=> 'ton'
puts str[2, 3] #=> 'ton'

puts str.slice(2..5) #=> 'ton2'
puts str[2..5] #=> 'ton2'

puts str.slice(/[0-9]+/) #=> '2018'
puts str[/[0-9]+/] #=> '2018'

よく使いそうなメソッド

  • String#strip:文字列の前後の空白文字を取り除いた文字列を返す
  • String#downcase,String#upcase,String#capitalize:それぞれ,全てを小文字にした文字列,全てを大文字にした文字列,先頭の文字だけ大文字にした文字列を返す
  • String#sub,String#gsub:文字列置換.第一引数に正規表現で置換対象の文字列を指定,第二引数には置き換える文字列を指定.
  • String#split:文字列を指定文字で分割した配列を返す.

5-3 Regexp

Regexp#match

マッチした場合にMatchDataオブジェクトというのを返す. MatchDataオブジェクトはマッチ結果に関する様々な情報を持っている.

class MatchData (Ruby 2.5.0)

String#scan

マッチした部分文字列を配列に格納して返す.

str = "kuroneko shironeko debuneko shibainu"

p str.scan(/\w+neko/) # => ["kuroneko", "shironeko", "debuneko"]

先読みと後読み

いまいちわかっていなかったのでこれを機会に勉強しました.

#MatchData[0]から戦闘力の数字だけを抜き出せる.
pattern = /(?<=私の戦闘力は)(\d+)(?=万です)/
p pattern.match('私は永遠の17歳です.私の戦闘力は53万です') # => #<MatchData "53" 1:"53">

しかしこの例だけだといまいち先読み・後読みを使うメリットがよくわからない.以下のようにしてMatchData[1]から取り出せるではないか…

#MatchData[1]で戦闘力の数字だけを抜き出せる.
pattern = /私の戦闘力は(\d+)万です/
p pattern.match('私は永遠の17歳です.私の戦闘力は53万です')[1] #=> 53

調べました.先読み・後読みは位置を示すものなのですね.

abicky.net

qiita.com

secondlife.hatenablog.jp

戦闘力の数字だけ変換したい場合なんかは,先読み・後読みを使わないとダメそう.

str = '私は永遠の17歳です.私の戦闘力は53万です'
p str.gsub(/(?<=私の戦闘力は)(\d+)(?=万です)/, '0') # => "私は永遠の17歳です.私の戦闘力は0万です"

5-5 Enumerable

Enumerable:数え上げられる

Array,Hash,Rangeなどオブジェクトを集めたものを扱うクラスがincludeしているモジュール,それがEnumerable. ものすごく大ボリュームな節で心が折れそうですがその分きっと大切なはず!

eachとeach_with_index

一番使いそう.ちなみにRange#eachは,各要素のsuccメソッド(先述)を呼び出して繰り返し処理を行っている.

# インデックスと要素の両方をブロック引数として受け取る
%w(ミミズ オケラ アメンボ).each_with_index do |bug, idx|
  puts "#{idx}: #{bug}だって"
end

puts 'みんなみんな生きているんだ友達なんだ'

Enumerable#mapとEnumerable#collectの違い

本当にmapはcollectの別名なだけらしい(実装が違うとかでもない). ref.xaio.jp

to_aはArrayに変換するメソッドで,is_a?は引数に渡したクラスのインスタンスならtrueを返す

間違えそう…

Enumerable 使えそうなメソッド

  • 検索系 Enumerable#grep,Enumerable#detect(Enumerable#find),Enumerable#select(Enumerable#find_all)
  • 畳み込み Enumerable#inject(Enumerable#reduce)
  • 繰り返しながらオブジェクトを更新していくEnumerable#each_with_object

Array 使えそうなメソッド

  • Array#sample:配列の中からランダムに要素を返す

指定した位置に要素を追加 Array#[]=

破壊的メソッドである(指定した位置に要素がある場合は上書き). 指定した位置が,配列のサイズ以上なら,その指定した位置までnilが入った配列となる.

要素追加・削除

  • Array#push,Array#pop 末尾に対する操作
  • Array#shift,Array#unshift 先頭に対する操作

他にもArrayには細かいメソッドはたくさんあるが,使いながら覚えていこう…

Hash#each

hash = {apple: 5, orange: 3}
# ハッシュのキー,値をブロック引数で受け取る
hash.each do |key, val|
  puts "#{key}#{val}"
end

出力結果

apple:5個
orange:3個

Enumerator

むずかしかったです…

Enumeratorオブジェクトはいつ返されるか

Enumeratorオブジェクトとは,繰り返し処理を行うメソッド(eachなど)を,ブロックを渡さず実行した場合に返されるもの.

ちなみにEnumerableをincludeしていないクラスでも,Enumeratorオブジェクトを返すメソッドを持っている(String#each_line,String#each_charなど).

Enumeratorオブジェクトは何ができるか,何がうれしいか

Enumeratorオブジェクトを生成したメソッドを使って,Enumerableのメソッドを呼び出せるようになる.

これにより例えば,以下のようにEnumerableをincludeしていないクラス(Stringなど)からでもEnumerableのメソッドを使えるようにできる.

word = 'ofton'

#Enumeratorオブジェクトenumを生成
enum = word.each_char
#Enumerator#mapは渡されたブロック内の処理を繰り返し,その結果を配列にして返す
p enum.map {|word| word.upcase} # => ["O", "F", "T", "O", "N"]

他には,mapやselectといった要素に対する繰り返ししかしないメソッドであっても,Enumerator#with_indexと組み合わせることで柔軟な処理が可能となる.

Enumeratorクラスの詳細は以下の通り.

class Enumerator (Ruby 2.5.0)

mapとwith_indexを組み合わせてみた.

%w(oden yakitori karaage).map.with_index do |food, idx|
  "#{idx}: #{food}"
end # => ["0: oden", "1: yakitori", "2: karaage"]

外部イテレータ

Array#eachなどにブロックを渡して繰り返し処理を行うのは「内部イテレータ」という.内部イテレータは,特定のオブジェクトに対してのみ繰り返し処理を行う場合はよいのだが,繰り返し処理を途中で中断して他の処理を行ったり,複数のオブジェクトに対して繰り返しを行いたい場合はうまくできない.

一方,オブジェクトに対してEnumeratorオブジェクトを生成する(to_enumメソッドなどで)と,繰り返しを行うための機能をオブジェクトの外部に独立させることができる.それが「外部イテレータ」である.以下は複数のオブジェクトに対する繰り返しを同時に行う例.

#Enumeratorオブジェクトを2つ生成
foods = %w(kanimiso ebimayo hotatebutter).to_enum
prices = [500, 400, 600].to_enum

#食べ物と値段を同時に表示する
loop do
  food = foods.next
  price = prices.next
  
  puts "#{food} #{price}"
end

出力結果

kanimiso 500円
ebimayo 400円
hotatebutter 600円

Enumerator::Lazy

繰り返し処理の実行を遅延させることができる.非常に大きな配列や,無限の要素を持つオブジェクトの集まりを手軽に扱える.

自分で考えるの限界だったので以下の記事を読みました.

qiita.com

その2に続く…