パーフェクトRubyお勉強フェスティバル 第10章 「Rubyでのリフレクションプログラミング」
多忙につき,ひさびさの更新です.
メタプログラミング最後の章「Rubyでのリフレクションプログラミング」見ていきます.
リフレクションとは
プログラムの実行中に、そのプログラムの情報を取得したり操作することをリフレクションと呼びます。
とあります.
オブジェクトの情報取得や操作
インスタンス変数の確認
Object#instance_variablesメソッドで,オブジェクトに定義されているインスタンス変数名の配列を得ることができるのはわかったが,そこの例にあったコード(以下のような感じ)
class Ofton def initialize @material = '羽毛' @temperature = 38 end end ofton = Ofton.new ofton.instance_variables.any? do |instance_val_name| instance_val_name =~ /status/ end
Array#any?で絞り込んでいるが,絞り込み条件はRegexp#=~を使った結果を用いている.「=~」ってマッチした位置を返すんじゃなかったか…?と思ったが,マッチしなかった場合「nil」を返すから,偽として扱われてマッチしたかどうかの判定に使われるんでした.(復習した)
getterやsetterがなくても,オブジェクトのインスタンス変数のgetやsetができる.
Object#instance_variable_getとObject#instance_variable_setで可能. どんなところで使われるのか気になって調べましたが,なるほど,テストなどで使われるのですね.(以下のような感じで)
オブジェクトに定義されているメソッドを調べる
Object#methodsでは引数なしまたは引数にtrueを渡すと,プライベートメソッド以外のメソッドを返す.引数にfalseを渡すとオブジェクトの特異メソッドのみを返す.
Object#send
任意のメソッドを実行できるメソッド.上に貼ったブログにも登場しております. メソッドの可視性にかかわらず呼び出しができるので,privateメソッドを呼びたいときなどに使える.
Object#__send__,前に書いた記事でなんだ?って思ったものですがここで解決.「send」というメソッド名が汎用的な名前のため,もしオーバーライドされてもよいように「__」をつけた別名のメソッドを用意しているということでした.
クラスの情報取得・操作
クラス変数への値のセット
サンプルコードで,Module#instance_variable_getではインスタンス変数名としてシンボル(例:「:@val」)を渡していたがModule#class_variable_setではクラス変数名として文字列(例:「'@@val'」)を渡している.クラス変数名はシンボルを使って書かないんだろうか?と思ったけど「:@@val」のような指定もするみたい(サンプルコードがたまたま文字列になってただけ)
インスタンスメソッド 削除と未定義の違い
Module#remove_methodでメソッドを削除,Module#undef_methodでメソッドを未定義にする. これらの違いについて.削除された場合は,同名メソッドが親クラスでも定義されているなら,親クラスのメソッドが呼び出されるようになる.一方,未定義にすると,同名メソッドが親クラスで定義されていたとしても,NameErrorが発生する.
イベントのフック
「||=」という演算子
モジュールがincludeされたら実行されるメソッド「Module#included」「Module#extended」のサンプルコードで出てきた「||=」という演算子がよくわからなかった.けどこれよくよくみたら以下のようなことだな.
#どっちもおなじ!
ary ||= []
ary || ary = []
aryが「nilである」場合にだけaryに空配列が代入されるというわけか…ショートサーキット演算子の性質を上手く用いていてスマートである.
Kernel.#set_trace_funcとTracePoint
これらによってRubyインタプリタのフックができる.細かいことはたくさんあるが,Procオブジェクトかブロックの処理をハンドリングできる.Ruby標準添付のdebugやprofileはKernel.#set_trace_funcによって作られている.
普通にアプリを書くぐらいだったらなかなか使うことはないのかな.
パーフェクトRubyお勉強フェスティバル 第9章 「Methodクラス」
600ページくらいあるこの本もやっと折り返し地点…
「Methodクラス」はさらっと行けそうです.サラッと書きます.
Methodオブジェクト
オブジェクトの持つメソッドをオブジェクトとして扱うのが「Methodオブジェクト」であり,取得はObject#methodにより行う.
なおここで取得されたMethodオブジェクトは「メソッドの処理そのもの」を保持しているものなので,オープンクラスや特異メソッドでオーバーライドしても影響は受けない.
Methodオブジェクトからは引数の情報,メソッドの定義されたクラスまたはモジュール,メソッド名,レシーバ,メソッドがオーバーライドしたメソッドなどの情報を得ることができる.
UnboundMethod
Methodオブジェクトからレシーバの情報を除いたのがUnboundMethodオブジェクトである.Module#instance_methodかModule#unbindを使う.
実行するときはUnboundMethod#bindにレシーバにしたいオブジェクトを引数として渡す.
Methodオブジェクトの使い道
Procオブジェクトは前回記事(パーフェクトRubyお勉強フェスティバル 第8章 「Procオブジェクト」 - おふとん王国)でクロージャとして使うことがわかったがMethodオブジェクトの使い道とはなんだろう.
Methodオブジェクトが実際に使われている例としてメソッドに渡された引数の数に応じて処理を変更する,というのが挙げられていた.こういうのはJavaだったらオーバーロードで解決できるけど,Rubyにはオーバーロードは存在しないんですよね…なるほど.
ProcやMethodの違いを深く理解しておく.
パーフェクトRubyお勉強フェスティバル 第8章 「Procオブジェクト」
8章,Procオブジェクトです.
関数型プログラミングっぽい話が多くて面白かった!
Procクラス
ブロックを扱いやすくするのがProcオブジェクト,というイメージをなんとなく持ちました.
Procオブジェクトのいいところは,ブロックをオブジェクトの形にして扱うことができるし,メソッドに対して複数ブロックを渡すということも可能になるというところですかね.
Proc#callに引数を渡す場合は別の書き方もできる.(忘れそう)
proc_obj = Proc.new do |name| puts "こんにちは#{name}さん" end #以下は全部同じこと proc_obj.call("太郎") proc_obj["太郎"] proc_obj.("太郎")
覚えておきたい,メソッド呼び出し時のブロックとProcオブジェクト
「&」を頭につけることで,以下が可能となる.
- メソッド呼び出し時にブロックを指定した場合に,メソッド側ではProcオブジェクトとして扱う
- メソッド呼び出し時にProcオブジェクトを指定した場合に,メソッド側ではブロックとして扱う
#引数名に「&」をつけた仮引数により,渡されたブロックをProcオブジェクトとして受け取れる def ofton(&block) block #Procオブジェクトを返却 end proc_obj = ofton{puts "Hello"} #ブロックを渡す proc_obj.call #=> "Hello" #yieldで呼び出したいブロックを,メソッド呼び出し時にProcオブジェクトとして渡す def ofton2 yield end proc_obj2 = Proc.new {puts "Hello"} ofton2 &proc_obj2 #=> "Hello"
カリー化
関数型プログラミングについて勉強してくると出て来るカリー化ですね.
複数の引数を持つメソッドに対し,例えば第一引数だけ渡してメソッド呼び出しを行うと,残りの引数をとるメソッドを返してくれるというやつです.
ofton = Proc.new {|name, status| "#{name}は#{status}"} p curried_ofton = ofton.curry.("おふとん国王") #「name」にのみ値を渡しカリー化する p curried_ofton.("眠い") #「status」に値を渡す
結果
#<Proc:0x00000000e8a378> "おふとん国王は眠い"
一つ疑問に思ったのが,上の例だとname,statusという順番で引数が定義されているが,statusにのみ値を渡しカリー化することはできるのか?
ちょこっと調べた限りだとできなそう(何か方法はあるのかもしれないが).というかそれはもう「カリー化」とは言わないのか.
ところでカリー化よく出てくるけど,何がありがたいんだろう…という疑問.
ラムダ
Kernel.#lambdaでProcオブジェクトを作ることも可能.なお,これにはシンタックスシュガー「->」がある.なぜ「->」かというとこれが「λ」に似ているから…らしい(似ているかどうかは知らない).
とは言え他言語でも出て来るラムダ式の由来を知ることができてよかった.
#Kernel.#lambdaを使う lambda_obj = lambda { |name| puts "こんにちは#{name}さん"} #簡潔に書けるシンタックスシュガー lambda_obj2 = ->(name) {puts "こんにちは#{name}さん"} lambda_obj.("太郎") lambda_obj2.("太郎")
結果
こんにちは太郎さん こんにちは太郎さん
Proc.newとKernel.#procとKernel.lambda
これらはいずれもProcオブジェクトを作り出せるが,これらによって作られたProcオブジェクト違いとして
- returnとbreakの挙動の違い(return時,Kernel.lambdaは制御を抜ける,Proc.newとKernel.#procではメソッドを抜ける.break時,Kernel.lambdaは制御を抜ける,Proc.newとKernel.#procでは例外が発生する.)
- 引数の違い(Kernel.lambdaは引数の数が一致しないとArgumentErrorを発生させる.Proc.newとKernel.#procでは,多く渡されたらその分を無視,少なかったらnilを渡す,などの対応をする.)
がある.基本的にKernel.lambdaはメソッドと同じ挙動となる.
クロージャ
これも関数型プログラミングの勉強をしているとよく出てくる.が,未だにピンと来ていなかったりした.
そこでまず,クロージャの利点みたいなものを調べてみる.
JavaScriptの記事ですが,前よりはなんとなく使い所がわかった気がする.
そして,Rubyにおけるクロージャへ.クロージャのようにProcオブジェクトを使う例.Proc.newしたコンテキストでの「status」を保持していることがわかる.
def ofton_proc status = 'ofton' Proc.new { status } end proc_obj = ofton_proc status = 'toplevel' p proc_obj.call
結果
"ofton"
いろいろ調べてみるとRubyでクロージャをやろうとするときにProcオブジェクトを使う理由がわかってくる.
JavaScriptだったら関数の中に関数を定義することで実現しているけど,Rubyでメソッドの中にメソッドを定義したとしても内側のメソッドからは外側のメソッドのスコープの変数は参照できないわけですもんね.
パーフェクトRubyお勉強フェスティバル 第7章「動的なプログラミング」
久々におふとん王国を全力で統治しています!楽しい!
パーフェクトRubyの勉強記録,更新していきます.
「静的」と「動的」
Rubyは動的なプログラミング言語ですが,ここで静的と動的について深く考えたことがなかったので,調べてみます.
テスト駆動開発が動的なプログラミング言語の弱点をカバーすることができる,というあたりの話は面白いですね. 「テスト駆動開発」も常々読んでみたいと思っています.(給料が出たら…!)
- 作者: Kent Beck,和田卓人
- 出版社/メーカー: オーム社
- 発売日: 2017/10/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
オープンクラス
Rubyでは既存のクラスの拡張が可能.定義後にメソッドを追加することができる.
ただ注意しなければならないのは,メソッドを上書きできてしまうので,オープンクラスによる拡張を行っているライブラリを使う場合などは想定外の動作となる可能性がある.
Refinements
オープンクラスの影響範囲を限定するための機能がRefinementsである.
まずModule#refineの引数に拡張したいクラスを指定し,ブロック内でメソッドを定義する. そしてModule#usingで取り込む.
refinements.rb
#Numericクラスに対し,オブジェクトを布団の枚数として表示するメソッドを追加 module RefineOfton refine Numeric do def count_ofton puts "布団が#{self}枚" end end end #Classのコンテキストでusing class Ofton using RefineOfton def ofton_number 5.count_ofton end end Ofton.new.ofton_number
結果
布団が5枚
別ファイル「using_refineofton.rb」からトップレベルでModule#usingし,読み込み
using_refineofton.rb
require_relative 'refinements' using RefineOfton 3.count_ofton
結果
布団が3枚
さらに別ファイル「require_using_refineofton.rb」からModule#requireして,「count_ofton」を呼び出した場合は,NoMethodErrorが発生する.
require './using_refineofton' 5.count_ofton
結果
布団が3枚 require_using_refineofton.rb:3:in `<main>': undefined method `count_ofton' for 5:Integer (NoMethodError)
その他いろいろと細かい話があるのですが割愛...
はて,自分が使うことはあるのだろうかと思い調べる.
クラス拡張のメリットを享受しつつも,拡張した影響範囲をコントロールしたいというニーズを満たすということですかねえ. Ruby on Railsでも使われているみたいだし,Railsチュートリアルをやりながら見られたらいいかなと思いますね.
BasicObject#method_missing
BasicObject#method_missingは「NoMethodError」をraiseするメソッドである.
メソッドの探索順序通りにメソッドを探していくと,最上位の親クラス(BasicObjectクラス)まで行き着く.そこでもメソッドがないというときにこのBasicObject#method_missingが呼ばれ,「NoMethodError」となるわけですね.
method_missingをオーバーライドして,メソッドが見つからなかった場合の処理を自分で定義することで,NoMethodErrorの発生を回避できる.
サンプルコードで出てきた「Object#send」って?
この謎は10章で解けるのでまた詳しく.
注意点
- method_missing内でさらに存在していないメソッドを呼び出すとループしてしまう
- 継承時,継承ツリーに存在するクラスでmethod_missingがオーバーライドされている場合がある
eval(イーバル)
この章で個人的に一番面白いと思ったのがこれです.evaluate,式を評価するということです.
Kernel.#eval
引数に渡された「文字列」を式として評価し,実行する.
何が嬉しいかというと
- 似たようなメソッド定義を簡潔に書ける
- メソッドを作るメソッドが定義できる
やってみました.似たようなメソッド定義を簡潔に書いてみる.
class Ofton animals = %w(cat dog pig) animals.each do |animal| eval <<-END_OF_DEF def #{animal} puts "#{animal}" end END_OF_DEF end end obj = Ofton.new obj.cat obj.dog obj.pig
結果
cat dog pig
メソッドを作るメソッドについては,動的にsetterやgetterを追加するメソッドの例が書籍に載っていました.
evalとBindingオブジェクト
Kernel.#bindingでBindingオブジェクトは得られる.
Bindingオブジェクトを使うことでそのコンテキストの内容を他のコンテキストに持ち込める.
これの何がよいかというとevalで式を評価するコンテキストを任意のコンテキストにできる.
class Ofton #Bindingオブジェクトを返すメソッド def show_king @king = 'ofton king' binding end end ofton_obj = Ofton.new ofton_binding_obj = ofton_obj.show_king p eval "@king" #ここからは「@king」は見ることができないが p eval "@king", ofton_binding_obj #Bindingオブジェクトによってコンテキストを指定しているので@kingが見える
結果
nil "ofton king"
Bindingオブジェクトを使わなくてもモジュール,クラス,インスタンスのコンテキストを指定してevalがしたい場合
module_eval,class_eval,instance_evalを使う. これらを使うと文字列だけでなく渡したブロックも式として評価し実行できる.
class Ofton #クラスインスタンス変数 @class_instance_val class << self def class_instance_val @class_instance_val end end #インスタンス変数 attr_reader :instance_val end #class_evalによるクラスインスタンス変数の再設定 Ofton.class_eval do @class_instance_val = 'new class instance val' end p Ofton.class_instance_val #instance_evalによるインスタンス変数の再設定 obj = Ofton.new obj.instance_eval do @instance_val = 'new instance val' end p obj.instance_val #Oftonクラスの中からは呼び出せない変数(ここではval)も参照できる val = 'ofton' #class_evalによるクラスインスタンス変数の再設定 Ofton.class_eval do @class_instance_val = val end p Ofton.class_instance_val #instance_evalによるインスタンス変数の再設定 obj.instance_eval do @instance_val = val end p obj.instance_val
結果
"new class instance val" "new instance val" "ofton" "ofton"
パーフェクトRubyお勉強フェスティバル 第6章 「Rubyのクラスオブジェクト」
前回の記事
oftonkingdom.hatenablog.com
前回(5章)の続きを書こうかと思ったのですが間違えて下書きを消してしまいました…
5章の残り,内容がファイルやディレクトリの操作,ThreadやFiber,プロセスについてであり,このあたりは初めて読む人は無理に読まなくてもいいよみたいな意見も見たりしたので,がっつり使う機会が来たら戻ってこようかとも思います.
というわけで「6章 Rubyのクラスオブジェクト」です.メタプログラミングの内容に入っていきます.
クラスもオブジェクト??
今までオブジェクト指向言語(Javaなど)書く中でクラスはクラス宣言することでしか書いたことがないが,クラスですらClassクラスのオブジェクトなのである.
クラスの継承ツリーは次のようになっている.
p Class.ancestors
出力
[Class, Module, Object, Kernel, BasicObject]
Class.newで作成したクラスオブジェクトを定数に代入することで,クラスを定義できる.
無名クラス
Class.newで作成したクラスオブジェクトを変数に代入すると「無名クラス」となる.
これ,どんなときに使えるのかよくわからなかったのでちょっとググりました.この記事だと,引数内でクラスを定義するときに使っていますね.
JavaScriptで無名関数が便利なときって,そこでしか行わない処理(使い捨てみたいな)を書くときに,離れたところに書かなくてもいいから便利みたいなイメージがあるのですが,それと近い?
class定義式とClass.newの違い
class定義式だとclass定義式外のスコープを参照できないが,Class.newだとブロックで定義するのでブロック外のスコープも参照できる.
これが動的なプログラミングたるゆえん,なのでしょうか.それまでの処理の流れを活かした上でクラスを定義したい場合などはClass.newに軍配が挙がるということになるのでしょうか.
また,class定義式内,Class.newに渡すブロック内における,「self」が指すものはいずれも以下の特徴を持つ * メソッド定義内のselfはクラスのインスタンスを指す * メソッド定義以外でのselfはクラスオブジェクトを指す
なお,Class.newに渡されるブロックのブロック引数は「クラスオブジェクト」を指す.
ややこしい「インスタンス変数」「クラスインスタンス変数」「クラス変数」
ややこしいので1つのコードにまとめてみました.
「クラスインスタンス変数」はクラスオブジェクトのインスタンス変数ということなので,上述の「self」が「クラスオブジェクト」を指すような場所で「インスタンス変数」を定義すればよい.
class Ofton #「クラスインスタンス変数」定義パターン1:インスタンスメソッド定義外で定義 @class_instance_val1 = :class_instance_val1 #これは「クラス変数」 @@class_val = :class_val #クラスメソッド「count_ofton」 def self.count_ofton #「クラスインスタンス変数」定義パターン2:クラスメソッド定義外で定義 @class_instance_val2 = :class_instance_val2 #class_instance_val1を参照もできる @class_instance_val1 #=> :class_instance_val1 end #インスタンスメソッド「sleep」 def sleep #これは「インスタンス変数」 @instance_val = :instance_val #インスタンスメソッドにはクラスインスタンス変数は「見せられないよ!!!!!」であるため, #以下については「class_instance_val1」という名前の「インスタンス変数」を探そうとする. #そんなものはないためnilが返る. @class_instance_val1 #=> nil #クラス変数は見せられるよ!!!!!!!! @@class_val #=> :class_val end end #Oftonクラスを継承した「UmoButon」クラスから親クラスの変数を参照する class UmoButon < Ofton #クラスインスタンス変数は子クラスには「見せられないよ!!!!!」であるため, #インスタンスメソッドからのクラスインスタンス変数の参照と同様の理由でnilが返る. @class_instance_val1 #=> nil #クラス変数は見せられるよ!!!!!!!! @@class_val #=> :class_val end
結局,クラス変数とクラスインスタンス変数は何が違うのかというと…クラス変数は継承したクラスからも見える特徴があるため,継承したクラスから見せたくないような場合(特定のクラスに限定した処理を行いたい場合など)にクラスインスタンス変数を使う,ということで納得しました.
特異クラス,特異メソッド
特異メソッドは特定のオブジェクトにのみ定義されるメソッドなので,直接のクラスには定義されていない.オブジェクトに対する「特異クラス」と呼ばれる場所で定義されるのである.
なお,クラスメソッドも,Classオブジェクトの特異クラスに定義されているのである.
class Ofton end ofton_obj = Ofton.new #特異メソッドを定義 def ofton_obj.fukafuka 'ふかふか' end #Oftonクラスに上記の特異メソッドは定義されていない p ofton_obj.class.method_defined? :fukafuka #=> false #Oftonクラスの特異クラスに上記の特異メソッドは定義されている p ofton_obj.singleton_class.method_defined? :fukafuka #=> true #特異クラスの親クラスがOftonクラスになっている p ofton_obj.singleton_class.superclass #=> Ofton
特異クラスの使い道とは?シングルトンオブジェクト
シングルトンオブジェクト,いまいち何がうれしいのかあまりよくわかっていなかったです.デザインパターンの一種なんですよね.
Module#prepend
そもそもモジュールのインクルードを行う「include」もModule#includeというメソッドなんですね.
prependとは「先頭に追加する」という意味.継承ツリーでは,Module#prependの呼び出し元であるクラス/モジュールよりも手前に,Module#prependに引数で渡したモジュールをラップしたクラスが追加される.つまり,同名のメソッドがあった場合,Module#prependで定義されたメソッドが呼び出される.以下の例では,OftonModule#helloはOfton#helloをオーバーライドしており,OftonModule#helloが呼び出される.
module OftonModule def hello "Hello from OftonModule" end end class Ofton prepend OftonModule def hello "Hello from Ofton" end end obj = Ofton.new puts obj.hello #=> "Hello from OftonModule"
実用例としてRuby on Railsのbefore_actionが例としてあげられています.
Paizaで遊ぶ(Ruby) #1
こんにちは!おふとん王国の土曜日,とても平和です.
oftonkingdom.hatenablog.com パーフェクトRubyを読んでいますが,インプット中心で実際のコードを書いていなかったので練習のためRubyでPaizaのB問題あたりを大量に解いてみました.
実際に書いてみるといろいろ足りないところがわかったので復習のためのエントリを書いてみます.競技プログラミングのいろんな問題に共通する話だと思うから問題のネタバレにはならない…はず
(正確には足りないと言っても,競技プログラミングっぽいプログラムを書くときの知識として足りないものなので,アプリケーション開発(最終目標)に向けた勉強とはまた話が違ってくるのかもしれないが,共通部分もあると思うので書いてみます.)
それにしても言語の本を買って体系的に勉強していてよかったな,と思う場面は非常に多かったです.もともとパーフェクトRubyを読み始めたのも,「分厚い言語の入門書を一冊極めるといい」という話をどこかで聞いたことがあるからなのですが,なんとなくその効果を実感しています.
以下,復習記録です.
StringやEnumerable,Arrayのメソッドのうち,よく使うものは秒で出てくるようになりたい
こういうニーズを感じたときこそ記憶が定着しやすくなるはず!というわけでパーフェクトRuby「5-2 String」「5-5 Enumerable」をざっと読み直しました.以下メモ.
- StringやArrayで長さを求めるメソッド,sizeかlengthどっちだっけ?となったけど,どっちも大丈夫だった
- Enumerableの検索系メソッド Enumerable#grep,Enumerable#detect(Enumerable#find),Enumerable#select
- Array#popは破壊的メソッドであり自身を変更する.戻り値は,取り除いた要素そのものである(ここではまった). また,要素を指定して削除する場合はArray#delete(完全に忘れていました).
- Array#eachをArray#foreachと書いてしまう.他言語の癖が…
- Array#[]で範囲オブジェクトを指定できるのとても便利(今日書いてみて実感した)
変数のスコープについて忘れている
メソッドの外で定義された変数はメソッドから使えない
scope.rb
cat = 'Russian Blue' def display_cat cat.upcase end display_cat
出力
scope.rb:4:in `display_cat': undefined local variable or method `cat' for main:Object (NameError)
使わせたかったら$をつけてグローバル変数で宣言することになる
この一方でブロックの外で定義されたローカル変数はブロックの中でも使える.
標準入力でハマる
1行ずつ標準入力で受け付けた文字列をsplitで配列に分割したかった
str = gets
p str.split("")
「hello」と入力した場合の出力
["h", "e", "l", "l", "o", "\n"]
getsで取り込んだ文字列には改行コードが入ってしまうのでそれが配列の最後の要素に入ってしまう. とりあえず文字列の末尾の改行コードを1つ取り除くメソッドString#chompで対処したのちsplit("").(ぱっとString#chompが思いつかず少し迷走した.ついでにString#chopは改行コードにかかわらず文字列の末尾の1文字を取り除く) split("")と書いたけど,正規表現を使ってsplit(//)でも,String#each_charしてto_aを使ってもよかった.
ついでに標準入出力周りに限らず,競技プログラミングで使えそうなもの全般について見ておく.
gets.split.map(&:to_i)みたいなのはするっと書けるくらいになっていて成長したんじゃない?と.シンボルとかProcあたりがわかってきた!
$stdin.readで複数行一気に読み込み,配列に分割して多重代入とかも便利そう.
if文のthenを付け忘れがち
if 条件1 then 処理1 elsif 条件2 then 処理2 else 上記以外の場合の処理 end
2次元配列の扱い
ふつうに迷った.以下の記事とか見た.
putsで配列を出力するとき,配列の要素が1行ずつ出力される
そう,2次元配列でもね.
array = [[1, 2], [3, 4], [5, 6]] puts array
出力
1 2 3 4 5 6
Array#flattenが内部で呼び出されているということですかね.
1次元目だけ展開したかったんだ私は…というわけでArray#joinを使って文字列にした.
array.each do |line| puts line.join end
出力
12 34 56
まとめ
ここのところ詰め込みすぎて頭がパンクしそうだったので,いい復習になりました.
この手の,ランクが付いたり時間制限があったりするものは楽しい.
AtCoderでも就職・転職につながるサービスがリリース予定らしいので,ぜひ挑戦していきたいところです.
パーフェクトRubyお勉強フェスティバル 第5章 その1
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の違い
以下を見ると機能的な違いはないみたい.内部の実装が違う?
整数型,浮動小数点型の算術演算
例えば整数型同士の除算は結果が整数型(切り捨て)だが,いずれかが浮動小数点なら結果は浮動小数点となる.
整数の0除算の場合はZeroDivisionError,浮動小数点の場合はNaNまたはInfinityが返る.
NaNってなんなんですか?というずっと思っていた疑問. 「Not a Number」なんですね…なん…ですね
constant Float::NAN (Ruby 2.5.0)
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オブジェクトはマッチ結果に関する様々な情報を持っている.
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
調べました.先読み・後読みは位置を示すものなのですね.
戦闘力の数字だけ変換したい場合なんかは,先読み・後読みを使わないとダメそう.
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クラスの詳細は以下の通り.
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
繰り返し処理の実行を遅延させることができる.非常に大きな配列や,無限の要素を持つオブジェクトの集まりを手軽に扱える.
自分で考えるの限界だったので以下の記事を読みました.