Ruby Silver ポイントメモ

## はじめに

RubySilverに合格したので、試験対策メモと、実際の試験で間違えたと思われる箇所を共有します。個人的に要注意と思ったところのメモ書きなので、網羅はされておりません。Silver受験のお役に立てば幸いです！

## ポイントメモ
### 演算子

- 演算子の優先順位
    
    累乗(**)、四則演算子、&、|、比較演算子、&&、||、範囲演算子、条件演算子(?:)、=、and、or
    
    [演算子式 (Ruby 3.3 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html)
    
- 複数の三項演算子 `a = 条件式1 ? 条件式2 ? "A" : "B" : "C";`
    
    
    | 条件式１ | 条件式２ | 結果 |
    | --- | --- | --- |
    | 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](https://qiita.com/scivola/items/1dbb6f7d0b04dda4a633))

- 「式1..式2」
    式1が真になったらtrue。そのとき式2も真なら1回trueの後初期状態へ。
    そのとき式2が偽なら、それ以降は式2が真になるまでtrueを返し、式2が真になったら初期状態へ。
    
- 「式1…式2」
    基本同上だが、式1が真になったとき式2が真でも初期状態に戻らない。
    

```ruby
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を記述しない場合は最後に記述した式の値が評価される
- 定数の再代入は警告出るが、破壊的メソッドの呼び出しは警告出ない
    
    ```ruby
    B = "foo"
    B.concat("bar")
    p B  #=> "foobar"：警告なし
    ```
    
- unlessはelse節は書けるがelsifは書けない！
- do..endより{}のほうが結合度が高い。pで出力するときは()使うか{}で記述
    
    ```ruby
    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`
    
    ```ruby
    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番目の要素が最初に呼ばれる
    
    ```ruby
    # 合計を計算する。
    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つの配列に入れて返す(二次元配列)
- 多重代入
    
    ```ruby
    # 要素を捨てる
    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
    
    ```ruby
    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などでハッシュの内容を参照する際は表示されない
    
    ```ruby
    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で渡される
    
    ```ruby
    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 を繰り返しマッチし、**マッチした部分文字列の配列**を返す
    
    ```ruby
    # 単語の出現回数をカウントする
    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も含めた配列を返す
    
    ```ruby
    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：ファイルの末尾から

```ruby
メソッド                      空のファイルに対して
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：引数に指定した文字列の一番後ろの`/`より前の文字列を返す
    
    ```ruby
    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で除算
- `StandardError`を継承しないクラスのインスタンス（例えばArrayなど）を`raise`メソッドの引数に指定すると、`TypeError`が発生し「exception class/object expected」とメッセージが表示される
- 複数の例外クラスを補足する方法3選
    - rescue節を複数書く
    - rescueに続けて例外クラスを複数書く
    - `rescue *[例外クラス1, 例外クラス2]`のように例外クラスの配列をsplat演算子で展開する

### 問題集で詰まった箇所

- メソッドの中にProcオブジェクト
    
    ```ruby
    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によるフォーマット指定　`%<幅>.<精度><指示子>`
    ```ruby
    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になる
    ```


- 演算子の優先順位
    
    ```ruby
    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)
  引数に取るのがインデックスか値かど忘れしました…悲
    ```ruby
    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と同じように動作するそうです。

## 参考文献、リンク
https://qiita.com/menmenshod/items/aa03c7fe1701a1f03a56

https://docs.ruby-lang.org/ja/latest/doc/index.html

https://rex.libertyfish.co.jp

https://www.amazon.co.jp/gp/aw/d/B0756VF9Y3/ref=tmm_kin_swatch_0?ie=UTF8&qid=&sr=