はじめに
RubySilverに合格したので、試験対策メモと、実際の試験で間違えたと思われる箇所を共有します。個人的に要注意と思ったところのメモ書きなので、網羅はされておりません。Silver受験のお役に立てば幸いです!
ポイントメモ
演算子
-
演算子の優先順位
累乗(**)、四則演算子、&、|、比較演算子、&&、||、範囲演算子、条件演算子(?:)、=、and、or
-
複数の三項演算子
a = 条件式1 ? 条件式2 ? "A" : "B" : "C";
条件式1 条件式2 結果 true true A true false B false true C false false C
条件式でのRangeオブジェクト範囲式
(2024.4.24追記:条件式として範囲式を用いる際はRangeオブジェクトは生成されない旨と、Rubyでの真偽とTrue/Falseの違いをコメントでご指摘いただき、訂正しました。
【参考URL】Ruby の真/偽と true/false は違う #Ruby - Qiita)
-
「式1..式2」
式1が真になったらtrue。そのとき式2も真なら1回trueの後初期状態へ。
そのとき式2が偽なら、それ以降は式2が真になるまでtrueを返し、式2が真になったら初期状態へ。 -
「式1…式2」
基本同上だが、式1が真になったとき式2が真でも初期状態に戻らない。
10.times{|d| print d == 3..d == 5 ? "T" : "F" }
# => dは0~9まで。0~2はF、3~5はT、6~9はFなのでFFFTTTFFFFとなる。
オブジェクト指向、その他文法事項
-
irbで改行するには「\」を使う。メソッド名の前の「.」を付けてからの改行であればirbもOK。プログラム上は「.」が改行前後どちらでもエラーにならない。
-
メソッドと変数の探索順位は変数が先⇒同名のものがあれば変数が優先される
-
メソッド内で定数を宣言するとSyntaxError
-
メソッドの戻り値はreturnを記述しない場合は最後に記述した式の値が評価される
-
定数の再代入は警告出るが、破壊的メソッドの呼び出しは警告出ない
B = "foo" B.concat("bar") p B #=> "foobar":警告なし
-
unlessはelse節は書けるがelsifは書けない!
-
do..endより{}のほうが結合度が高い。pで出力するときは()使うか{}で記述
p [1,2,3,4].map do |e| e * e end # => <Enumerator: [1, 2, 3, 4]:map>:mapまでがpされてる p [1,2,3,4].map { |e| e * e } # => [1, 4, 9, 16] p ([1,2,3,4].map do |e| e * e end) # => [1, 4, 9, 16]
-
&:<メソッド名>
とするとSymbol#to_proc
が実行されます。 -
@@クラス変数は継承したクラスでも共有されます!!
-
可変長引数は1つしか定義できない。最後でなくてもOK
-
多重代入で右辺が不足している場合は、nilが代入される
-
メソッドと引数の間に空白あると解釈に注意
foo (2) * 2 # => foo ((2) * 2)が呼ばれたと解釈される
-
特異クラスと特異メソッドの定義
特異クラス:指定したインスタンスだけに適用される特別なクラス。
class << 対象のオブジェクト…(特異メソッド定義)…end
特異メソッド:インスタンスに直接定義されたメソッド
def <オブジェクト名>.<新たに定義するメソッド名>…end
s = "Hello" # Stringオブジェクト def s.greet # オブジェクトsに特異メソッドを定義 puts "Hi!" end class String def greet puts "Hello!" end end s.greet # => "Hi!":クラスを拡張したメソッドより、特異メソッドが優先される
-
継承クラスのsuperは引数も()も省略すると、現在の引数を引き継いで実行される
Object
- Object#equal?:オブジェクトIDが同じかどうか比較。上書き不可
- eql?は自作クラスでは上書きしないとオブジェクトID比較になってしまう
Array
-
Arrayの生成
-
Array(3)
はArray([3])
と同じ:[3]
が生成される。ブロックは無視される -
Array.new(3){"a"}
は要素数3の配列を作成し、ブロックの値でそれぞれ初期化 -
Array.new(3, "a")
は要素数3の配列を作成し、全要素を"a"
で初期化(同一オブジェクト)
-
-
select、filter:ブロックを評価し真になった要素を配列で返す、なければ空配列。非破壊
-
|和演算では重複要素は取り除かれる
-
flattenは平坦化した配列を返すが、flatten!は平坦化しなかった場合nilを返す
-
inject:引数に初期値を設定できる。しない場合は1,2番目の要素が最初に呼ばれる
# 合計を計算する。 p [2, 3, 4, 5].inject {|result, item| result + item } #=> 14 # 自乗和を計算する。初期値をセットする必要がある。 p [2, 3, 4, 5].inject(0) {|result, item| result + item**2 } #=> 54
-
連番Arrayの用意
[*1..10]
-
any?:真になる要素があれば即true。ブロックの戻り値がtrueになった時点で繰り返しを止める
-
delete_ifとreject!の違い:delete_ifは常にselfを返すが、reject!は削除しなかった場合nilを返す
-
partition:ブロックの評価が真だった要素と偽だった要素を2つの配列に入れて返す(二次元配列)
-
多重代入
# 要素を捨てる array = [1,2] x, = array p x #=> 1 # **配列以外もあるときは、配列が1要素とみなされる** x, y = 1,[2, 3] p x # => 1 p y # => [2,3] # **変数側に()があるときは配列と認識される** (x, y), z = 1, 2, 3 # y = nil, z = 2となり、3は無視される (x, y), z = [1, 2], 3 # y = 2, z = 3 # 変数が一つであれば、配列として代入される a = 1,2,3 # => [1,2,3]
-
sortは
<=>
で比較できる必要があるので、文字列と数値が入った配列をsortするとArgumentErrorが発生する -
push,append:末尾に追加
-
each_with_indexは引数取らないが、with_indexはoffsetを引数でとる
-
*”a”
はArrayクラスhoge = *"a” #⇒[”a”]
、piyo = *7 #⇒[7]
-
zipとtranspose
a = %w(a b c) b = [1,2,3] a.zip(b){|x|p x} # ["a", 1]["b", 2]["c", 3] a.zip(b).each{|x|p x} # 同上。zipした結果を出力している [a,b].transpose.each{|x,y|p [x,y]} # 同上。transposeはレシーバに2次元以上かつ要素数共通の配列がないとエラーになる。 [a,b].transpose.each{|x|p x} # 同上。transposeの結果は2次元配列。要素数一つずつ出力するならパラメータ一つでよい [a,b].zip{|x,y| p [x,y]} # ["a", "b"] [1, 2] p [1,2,3].zip #=> [[1], [2], [3]]:引数無しでzipを使うと、レシーバの要素ごとの配列が作られる p [[1,2,3],[4,5,6]].zip #=> [[[1, 2, 3]], [[4, 5, 6]]] [[1,2,3],[4,5,6]].zip{|x| p x} #=> [[1, 2, 3]]\n [[4, 5, 6]] # ブロックでパラメータを1つにすると、配列が取り出される [[1,2,3],[4,5,6]].zip{|x,y| p [x,y]} #=> [1, 2]\n [4, 5] # パラメータを2つにすると、それぞれの配列の1つ目と2つ目の要素が取り出される [[1,2,3],[4,5,6]].zip{|x,y,z| p [x,y,z]} #=> [1, 2, 3]\n [4, 5, 6] # 同上、それぞれの配列から3要素ずつ取り出される
Hash
-
clear、replace:Arrayと共通のメソッド
-
invert:値とキーを入れ替えてハッシュを返す。キーが重なった場合は、あとの定義が優先される
-
Hash#sortの返り値はArray
-
store:キーと値を格納(hash[key] = valueでの格納と同じ)
hash.store(key,value)
-
Hashの作り方:
hash = {:a => 1, b: 3, "c" => 5}
またはhash = Hash[:a, 1, :b, 3, :c, 5]
-
空のHashオブジェクトを生成するには
Hash({})
,{}
,Hash.new(デフォルト値)
,Hash[]
のいずれかを用います。 -
Hashのデフォルト値
デフォルト値が適用されたキーは、pなどでハッシュの内容を参照する際は表示されない
Hash.new("foo") # 同一オブジェクトのfooがデフォルトとなるので、一つの破壊的変更が他にも影響 Hash.new{|hash,key| hash[key]="foo"} # 値がまだないキーが追加されるたびにブロックが評価されるので、異なるオブジェクト Hash.new{|hash,key| raise(IndexError,"hash[#{key}] has no value")} # 上記を利用して未登録のキーに対し例外を発生させる # Hash#default_proc=nilを指定すると現在のdefault_procをクリアする h = Hash.new([].freeze) # デフォルト値の変更が起きないようfreeze p h[0] #=>[] p h[0] += [0] #=>[0]:[] = [] + [0]:+は非破壊メソッドなのでエラー出ない。<<は破壊的メソッドなのでエラー発生
-
Hash#eachのブロックパラメータはArrayで渡される
h = {a: 100} h.each {|p| p p # => [:a, 100]:パラメータを2つ設定すると、キーと値それぞれが渡される p p.class # => Array }
String
-
delete:selfから引数に含まれる文字を除いた文字列を返す。非破壊。2つ以上の引数は&&判定。先頭に^がついていればそれらを除く
str.delete!("^2-41-") #⇒2,3,4,1,-を除く
-
tr、delete、squeezeは引数に正規表現は使えない
-
replaceは完全置き換えなので引数1つ。
-
chomp:rs が "\n" ($/ のデフォルト値) のときは、実行環境によらず "\r", "\r\n", "\n" のすべてを改行コードとみなして取り除く。末尾に改行コードがない時はなにもしない
-
chop:文字列の終端が "\r\n" であればその 2 文字を取り除く
-
chop,chomp,stripそれぞれ!(破壊的)か否か要注意!!!!
-
scan:self に対して pattern を繰り返しマッチし、マッチした部分文字列の配列を返す
# 単語の出現回数をカウントする s = "To be or not to be, that is the question." hash = Hash.new(0) s.scan(/\w+/) {|i| hash[i] += 1} # +=の処置ならデフォルト値は変更されない p hash["be"] #=>2 # ブロックを取る他の例 "Ruby Examination".scan("xa") { |s| p s.upcase } # => "XA"
-
split:第2引数で分割戸数のlimitを指定できる。未指定時は0=制限なく分割
また、正規表現内で()を使うと、デリミタを含んだ結果を返す
()だと\n,\tなどは削除、(//)だと\n,\tも含めた配列を返す
p "a,b,c,d,e".split(/,/, 1) # => ["a,b,c,d,e"] p "a,b,c,d,e".split(/,/, 2) # => ["a", "b,c,d,e"] p "a,b,c,d,e".split(/,/, 3) # => ["a", "b", "c,d,e"] p "a,b,c,d,e".split(/,/, 4) # => ["a", "b", "c", "d,e"] p "a,b,c,d,e".split(/,/, 5) # => ["a", "b", "c", "d", "e"] p "a,b,c,d,e".split(/,/, 6) # => ["a", "b", "c", "d", "e"] p "a,b,c,d,e".split(/,/, 7) # => ["a", "b", "c", "d", "e"] p "a,b,c,d,e".split /(,)/ # => ["a", ",", "b", ",", "c", ",", "d", ",", "e"] p "a b c d".split() # => ["a", "b", "c", "d"] p "a\nb\nc\nd".split(//) # => ["a", "\n", "b", "\n", "c", "\n", "d"] p "a\tb\tc\td".split() # => ["a", "b", "c", "d"] p "a b c d".split(//) # => ["a", " ", "b", " ", "c", " ", "d"]
-
splitで区切り文字を複数指定するときは正規表現を使う
/; | :/
-
index(pattern, pos):pos番目から検索開始
-
==、===、eql?:内容が同じか?
-
%演算⇒詰まった問題参照
-
Stringにも[]、sliceメソッドがあるよ
-
String#strip!
は文字列の先頭と末尾の空白文字(\t\r\n\f\v)を取り除きます -
to_i:できるとこまで整数にする。変換不可、空文字、nilには0を返す。引数で進数指定。引数0の場合は変換対象の接頭辞で判断
-
hex,octメソッドは変換できる文字が見つからなければ0を返す
-
Stringにできないこと
- to_aメソッドはない!charsを利用
- to_hメソッドもない!
- appendはない!<<またはconcatを利用。ちなみに+は非破壊。
Integer
- chr:引数で与えられたエンコーディングで数値を一文字に変換。正しく解釈できない場合はRangeError
- ==、===:数値として等しいかどうか比較。
1==1.0 #⇒true
- eql?:同じクラスのオブジェクト同士で==で等しければtrue。
1.eql?(1.0) #⇒false
- equal?:オブジェクトIDが等しければtrue
1.equal?1.0 #⇒false
- Numeric#abs2:絶対値の2乗を返す
-
Numeric#step(limit, step)
はself
からstep
ずつ加算し、limit
までをブロックに渡します。 - to_s:引数で進数の変更もできる
IO
- read(テキスト名 , 読み込む文字数, offset = 開始位置):サイズ指定無ければEOFまで読み込む
- write(ファイル名, 書き込む文字列, 開始位置)
- seek(offset,whence):offsetの分だけポインタをwhenceから移動する。
- IO::SEEK_SET:whenceのデフォルト。ファイルの先頭から
- IO::SEEK_CUR:現在のポインタ位置から
- IO::SEEK_END:ファイルの末尾から
メソッド 空のファイルに対して
IO.read(空ファイル) ""
IO.read(空ファイル, length) nil
IO.readlines(空ファイル) []
IO.foreach(空ファイル) 何もしない
メソッド 既にEOFだったら
IO#each_byte 何もしない
IO#getc nil
IO#gets nil
IO#read() ""
IO#read(length) nil
IO#read_nonblock EOFError
IO#readchar EOFError
IO#readline EOFError
IO#readlines []
IO#readpartial EOFError
IO#sysread EOFError
File
-
chmod(モード、テキスト名)
-
chown
-
directory?:ファイルがディレクトリかどうかboolで返す
-
delete(テキスト名):削除成功すれば削除数、失敗した場合はエラーを返す
-
extname:ファイル名の拡張子部分を返す
-
basename:引数のfilenameの一番最後の
/
に続く文字列を返す。suffix指定すれば取り除く -
dirname:引数に指定した文字列の一番後ろの
/
より前の文字列を返すFile.dirname("text.txt") # => "." File.dirname("REx/text.txt") # => "REx" File.dirname("Desktop/REx/text.txt") # => "Desktop/REx"
-
join:File#joinは定数FILE::SEPARATOR "/"を使って文字列を連結。すでに/入ってたら削除して連結
-
IO#closeはFileクラスのメソッドではない!
-
openはIOにもFileにもあるメソッド
-
ファイルオープン時のモード一覧:指定無ければ読み込みモード
"r" 読み込みモード
"w" 書き込みモード。ファイルが存在していればファイルを空にする。
"a" 書き込みモード。ファイルが存在していれば、ファイルの末尾から追記する。rewindやseek(0,IO::SEEK_SET)でポインタを先頭などに移動しても書き込みは末尾から行う
"r+" 読み書き両用モード。ファイルの先頭から読み書きを行う。
"w+" 読み書き両用モード。ファイルが存在していればファイルを空にする。
"a+" 読み書き両用モード。ファイルの末尾から読み書きを行う。読み込みは先頭から行うが、rewindやseek(0,IO::SEEK_SET)でポインタを先頭などに移動しても書き込みは末尾から行う
Dir
- rmdir:空のディレクトリを削除、成功すれば0を返す。空でないなど失敗時はエラー
- delete:同上
- chdir:カレントディレクトリを引数のpathに変更する
- pwd:カレントディレクトリのフルパスを返す
- getwd:同上
- home:現在のユーザまたは引数で指定したユーザのホームディレクトリを返す
Time
- フォーマット文字列 %x # ⇒ 日付(%m/%d/%y)
- フォーマット文字列 %F # ⇒ 日付(%Y-%m-%d)
Thread
- new,start,forkでスレッドオブジェクトを生成(ブロック必須、ないとエラー)
- newのみinitializeを呼び出す
正規表現
- \w:英数字とアンダースコア、\W:それ以外
- \d:数字、\D:数字以外
- .:改行除く任意の1文字、mオプションあれば改行も含む
- \s:空白文字(\t, \n,\r,\f)、\S:それ以外
- \A:先頭の文字。改行に影響されない
- \z:末尾の文字。改行に影響されない、\Z:改行で終わっていればその直前の文字
例外処理
- raiseで引数を省略すると、RuntimeError
- rescueで引数を省略すると、StandardErrorを捕捉する
例外クラスの階層
- Exception
- ScriptError
- SyntaxError:文法エラー
- Signal Exception:補足していないシグナルを受けた
- StandardError
- ArgumentError:引数の数が合わない、値が正しくない
- RuntimeError:特定のクラスに該当しないエラー、例外クラスを省略したraise呼び出し
- NameError:未定義のローカル変数や定数の参照
- NoMethodError:未定義のメソッド呼び出し
- ZeroDivisionError:整数に対し整数の0で除算
- ScriptError
-
StandardError
を継承しないクラスのインスタンス(例えばArrayなど)をraise
メソッドの引数に指定すると、TypeError
が発生し「exception class/object expected」とメッセージが表示される - 複数の例外クラスを補足する方法3選
- rescue節を複数書く
- rescueに続けて例外クラスを複数書く
-
rescue *[例外クラス1, 例外クラス2]
のように例外クラスの配列をsplat演算子で展開する
問題集で詰まった箇所
-
メソッドの中にProcオブジェクト
def hoge(step = 1) current = 0 Proc.new { current += step } end p2 = hoge(2) p p2.class #=> Proc:hogeメソッドの返り値はProcオブジェクト # つまりp2 = Proc.new{current += step}で、currentは最初0、step値として2を渡した # p2.callするたびに、step値(今回は2)ずつcurrentに加算されていく
-
文字列の%演算
sprintfによるフォーマット指定%<幅>.<精度><指示子>
sprintf("result: %02d",1) #=> "result: 01":0で余ったケタは0詰め、2で桁数指定、dで10進数表記 "result: %02d" % 1 #=> "result: 01":文字列内の%表記に埋め込む数字を外の%の後に表記 p "Hello%d" % 5 #=> "Hello5" member = [10, "Tanaka"] print "ID:%2d Name:%s"% member # => ID:10 Name:Tanaka # [文字列パターン%配列]で配列の要素を文字列パターン内の%dや%sなどに埋め込んで表示できる # 配列から要素が順番に取り出されるので、順番を間違えるとArgumentErrorになる
-
演算子の優先順位
v1 = 1 - 1 == 0 v2 = v1 || raise RuntimeError # ||の優先度が高くv1 || raiseとなり、残った「RuntimeError」が文法エラー puts v2 && false # 解決策 v2 = v1 or raise RuntimeError v2 = v1 || raise(RuntimeError) v2 = v1 || (raise RuntimeError)
試験本番で間違えたところ
-
String#delete_suffix!
メソッドの存在を知りませんでした。 -
Array#delete(val)
引数に取るのがインデックスか値かど忘れしました…悲arr = [1,2,3,1,2,3,1,2,3] arr.delete(2) p arr # => [1,3,1,3,1,3]
-
String#lines
メソッドが存在するかうろ覚えで間違えました。ブロックを指定するとString#each_lineと同じように動作するそうです。
参考文献、リンク