Roll With IT

tamakiのIT日記

チェリー本輪読会 第3週目まとめ

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

チェリー本輪読会の第3週目のエントリーになります。

輪読会の概要については第1週目にまとめています。

🍒 輪読会 第3週目まとめ

第4章4.1.1〜第4章4.6.5まで

期間:2021年06月07日〜2021年06月11日

今週から第4章の「配列や繰り返し処理を理解する」へ突入!

hex と ints

  • hexは、16進数(hexadecimal numberの略)のことです。

  • 進数の英語表現

    • 2進数: binary number
    • 8進数: octal number
    • 10進数: decimal number
    • 16進数: hexadecimal number

進数 - Wikipedia

  • ints は、integer(整数型)のことです。

でもなぜ? int ではなくて、 ints なのか?

以下、メンターさんに質問してみたところ回答をいただきました。

Ruby界隈はrailsの影響で特に配列が入ってる変数は複数形にするという文化が強いからできた略語に見えますね。

JavaC言語などでは、int と表現されるようです。

なるほど…ややこしい...汗

プログラミングでよく使う英単語のまとめ【随時更新】 - Qiita

ちなみに、るりま(Rubyリファレンスマニュアル)でsが付く単語が他にもありました。

arguments(実引数)を、args と表現、

parameters(仮引数)を、params と表現してありました。

仮引数(parameter)と実引数(argument)

この2つの「引数」もややこしいので、整理したいと思います。

まずは、parameterから

  • メソッドを定義するときに受け取る引数のこと。
  • 仮引数のこと。仮引数とは、「プログラム言語・数学などで関数を定義しているところに書かれている仮の数字・変数(パラメーター)」のことを指す。
  • params(パラムス)、またはparam(パラム)と略する。

paramsハッシュ 特殊な使い方

argument

  • メソッドを呼び出すときに、メソッドに渡す値や変数のこと。
  • 実引数のこと。実引数は、「プログラムの実行時に関数に引き渡される値となる引数」のことを指す。
  • args(アーグス) と略する。

paramsとargsの例

定義したcalculate(n)nがparams

呼び出しているcalculate(100)100がargs

irb(main):013:1* def calculate(n)
irb(main):014:1*   puts n * 10
irb(main):015:0> end
=> :calculate
irb(main):016:0> calculate(100)
1000
=> nil

配列を使った多重代入

  • 基本形
irb(main):017:0* a, b = 1, 2
=> [1, 2]
  • 応用編

競プロでよく使うパターン

標準入力からスペース区切りの数値を受け取り、配列にして返します。

irb(main):019:0> gets.split.map(&:to_i)
100 200 300 400 500
=> [100, 200, 300, 400, 500]

split('foo')で、区切ることもできます。

+ を区切りで指定してみます。

irb(main):022:0> gets.split('+').map(&:to_i)
100+200+300+400+500
=> [100, 200, 300, 400, 500]

do..end と  { } の使い分け

do...end、{} ともに、ブロックの範囲を表しています。

eachメソッドを例に説明します。

do..endがブロックの範囲です。eachメソッドで「配列の要素を最初から最後まで取り出す」指示を実行し、取り出した要素を順にブロックへ渡しています。

# 公式
変数名.each do |ブロック変数|
    繰り返したい処理
end

irb(main):037:0> beatles = ["YEAH!","YEAH!YEAH!","YEAH!YEAH!YEAH!"]
=> ["YEAH!", "YEAH!YEAH!", "YEAH!YEAH!YEAH!"]
irb(main):038:1* beatles.each do |x|
irb(main):039:1*   puts "ビートルズがやってくる#{x}"
irb(main):040:0> end
ビートルズがやってくるYEAH!
ビートルズがやってくるYEAH!YEAH!
ビートルズがやってくるYEAH!YEAH!YEAH!
=> ["YEAH!", "YEAH!YEAH!", "YEAH!YEAH!YEAH!"]

{ }ブロックで書き換えてみます。

同じ結果が返ります。1行でコンパクトに書きたいときは {}ブロック を使うとよさそうです。

irb(main):001:0> beatles = ["YEAH!","YEAH!YEAH!","YEAH!YEAH!YEAH!"]
=> ["YEAH!", "YEAH!YEAH!", "YEAH!YEAH!YEAH!"]
irb(main):002:0> beatles.each { |x| puts "ビートルズがやってくる#{x}" }
ビートルズがやってくるYEAH!
ビートルズがやってくるYEAH!YEAH!
ビートルズがやってくるYEAH!YEAH!YEAH!
=> ["YEAH!", "YEAH!YEAH!", "YEAH!YEAH!YEAH!"]

map / collect(エイリアス) メソッド

今のところ、一番使ったメソッドナンバーワンのmapメソッド。

輪読会でも好きなメソッドとして一番名前が上がっていました。

チェリー本にも記載がありましたが、配列処理の大半はmapでいけます。

空の配列を用意して、ほかの配列をループ処理した結果を空の配列に詰め込んでいくような処理の大半は、mapメソッドに置き換えることができるはずです。

irb(main):040:0> names = ["Lennon", "McCartney", "Harrison", "Starr"]
=> ["Lennon", "McCartney", "Harrison", "Starr"]
irb(main):041:0> names.map { |n|  "Mr." + n }
=> ["Mr.Lennon", "Mr.McCartney", "Mr.Harrison", "Mr.Starr"]

ちなみに、eachで書き換えるとこうなります。

irb(main):036:0> names = ["Lennon", "McCartney", "Harrison", "Starr"]
=> ["Lennon", "McCartney", "Harrison", "Starr"]
irb(main):037:1* name_mr = []
=> []
irb(main):038:0> names.each { |n| name_mr << "Mr." + n }
=> ["Lennon", "McCartney", "Harrison", "Starr"]
irb(main):039:0> name_mr
=> ["Mr.Lennon", "Mr.McCartney", "Mr.Harrison", "Mr.Starr"]

slect / find_all(エイリアス) メソッド

戻り値が真trueの要素を集めた配列を返すメソッドです。真になる要素がひとつもなかった場合は空の配列を返します。ブロックを省略した場合は、各要素に対しブロックを評価し真になった値の配列を返すような Enumerator を返します。class Enumerator (Ruby 2.5.0 リファレンスマニュアル)

ブロックなしでメソッドを呼ぶと返ってくる値について、メソッドのクラスが返ってくるようです。

irb(main):003:0> [1,2,3,4,5].select { |num| num.even? }
=> [2, 4]

irb(main):004:0> [1,3,5].select { |num| num.even? }
=> []

irb(main):005:0> [1,2,3,4,5].select
=> #<Enumerator: [1, 2, 3, 4, 5]:select>

reject メソッド

selectメソッドの反対。戻り値が偽falseになった要素を返します。

rejectは、拒否するの意。

5で割り切れない数だけを返します。

irb(main):014:0> [1,5,10,12,15,100,202,500 ].reject { |n| n % 5 == 0 }
=> [1, 12, 202]

find / detect(エイリアス) メソッド

findメソッドは、selectと似ていますが、ブロックの戻り値が真になった「最初」の要素を返します。

irb(main):021:0> [10,50,111,200,333].find { |n| n.odd? }
=> 111

inject / reduce(エイリアス) メソッド

たたみ込み演算を行うメソッドです。

まず、ブロックの第1引数には、メソッドの引数を入れ、その後は第2引数へブロックの戻り値を順番に入れていきます。

irb(main):022:0> num = [10, 100, 1000, 10000]
=> [10, 100, 1000, 10000]
irb(main):023:0> num.inject(1) { |result, n| result + n }
=> 11111
  • まずは、resultへ1が入ります。
  • その後、1 + n の nへ、配列から取り出した10が入ります。 1+ 10=11 この結果がresultへ入る。
  • その後、11 + n  の nへ、配列から取り出した100が入ります。 11+100=111 この結果がresultへ入る。
  • その後、111 + n  の nへ、配列から取り出した1000が入ります。 111+1000=1111 この結果がresultへ入る。
  • その後、1111 + n  の nへ、配列から取り出した10000が入ります。 111+10000=11111 この結果がresultへ入る。
  • 結果、11111 が戻り値になります。

&:メソッド名で短くキメる

ブロックの中身を短く簡潔に書く方法です。

競プロでよく使っている記法だったので理解しやすかったです。

以下の条件のときに使えます。

  • ブロック引数が1個
  • ブロックの中で呼び出すメソッドには引数がない
  • ブロックの中では、ブロック引数に対してメソッド1回呼び出す以外の処理がない
irb(main):026:0> ["1", "2", "3"].map{|v| v.to_i}
=> [1, 2, 3]

irb(main):028:0> ["1", "2", "3"].map(&:to_i)
=> [1, 2, 3]

範囲 Range

範囲オブジェクトは、Rangeクラスのオブジェクトです。

FizzBuzz問題をはじめ、いろんな場面で応用が効くので覚えておきたいオブジェクトです。

# 公式
(1..10) 1 2 3 4 5 6 7 8 9 10  最後の値を含む
(1...10) 1 2 3 4 5 6 7 8 9 最後の値を含まない

irb(main):031:1* (1..10).each do |n|
irb(main):032:1*   puts "#{n}回目のYEAH!"
irb(main):033:0> end
1回目のYEAH!
2回目のYEAH!
3回目のYEAH!
4回目のYEAH!
5回目のYEAH!
6回目のYEAH!
7回目のYEAH!
8回目のYEAH!
9回目のYEAH!
10回目のYEAH!
=> 1..10

irb(main):001:1* (1...10).each do |n|
irb(main):002:1*   puts "#{n}回目のYEAH!"
irb(main):003:0> end
1回目のYEAH!
2回目のYEAH!
3回目のYEAH!
4回目のYEAH!
5回目のYEAH!
6回目のYEAH!
7回目のYEAH!
8回目のYEAH!
9回目のYEAH!
=> 1...10

範囲オブジェクトで、to_aメソッドすると、配列になります。

(1..5).to_a など、繰り返し処理にも応用できます。

irb(main):004:0> (1..5).to_a
=> [1, 2, 3, 4, 5]

irb(main):005:0> (1..5)
=> 1..5

*splat展開でも同じように処理できます。[] で囲います。

irb(main):006:1* [*1..5]
=> [1, 2, 3, 4, 5]

irb(main):007:0> [1..5]
=> [1..5]

[ ] もメソッド

String#[] (Ruby 3.0.0 リファレンスマニュアル)

[] もメソッドなのは知りませんでした。

指定した位置(添字)の文字を返します。

irb(main):015:0> "TheBeatles"[3]
=> "B"

範囲オブジェクトと組み合わせてみます。

irb(main):012:0> name = "TheBeatles"
=> "TheBeatles"
irb(main):013:0> name[3..6]
=> "Beat"

著者の伊藤さんに質問してみた

輪読会内で解決できなかった疑問を直接質問してみました(第2週目に取り組んだ内容)

直接著者に質問できる輪読会。改めて凄いです....

伊藤さん(id:JunichiIto)、ご回答ありがとうございました!

質問内容

「Minitestのテストが失敗した場合のログについて」

今日のチェリー本の輪読会で3.3「FizzBuzzプログラムのテスト自動化」(p.76~)の項目を読み、テストコードと同じような流れで実際にモブプロ形式でテストをやっていたのですが、失敗した時に表示されるログのdiffの部分が分かりそうで分からないみたいな状態になってしまったので質問したいと思います。

# p.76より抜粋
 1) Failure:
FizzBuzzTest#test_fizz-buzz [lib/fizz_buzz.rb:23]:
--- expected
+++ actual
@@ -1 +1,2 @@
-"Fizz Buzz"
+# encoding: US-ASCII
+"16"

このログの中の、

@@ -1 +1,2 @@

の数字が具体的に何を表しているのかがよく分からなかったので、わかりやすく解説してもらえると嬉しいです。よろしくお願いします。

回答

Minitestは内部的にdiffコマンドを実行してdiffを表示しているはずです。

というわけで、Unixのdiffコマンドの仕様を調べるのが良さそうです。

この場合はUnified形式の説明が該当しそうです。

【Linux】diffコマンドで二つのファイルを比較する - Man On a Mission

冒頭で「--- 1つ目のファイル名」と「+++ 2つ目のファイル名」が表示されます。 その後に「@@ -1つ目のファイルの開始行と表示行数 +2つ目のファイルの開始行と表示行数 @@」が表示され、続いて該当箇所の内容が出力されます。

上記、最初の差異を例に取ると、「@@ -1,7 +1,6 @@」は、a.txtの1行目から7行分、b.txtでは1行目から6行分に該当する箇所が表示されていることを意味しています。 次の差異、「@@ -9,3 +8,4 @@」は、a.txtの9行目から3行分、b.txtでは8行目から4行分に街頭する箇所の表示を意味しています。

この説明で考えると、-1 +1,2

  • 1 = 期待値の1行目を表示中(つまり"Fizz Buzz"を指す)
  • +1,2 = 実際の値の1行目から2行分を表示中(つまり# encoding: US-ASCII"16"を指す)

ということになると思います。

TDD とは?

コラムで取り上げられていたテスト駆動開発

輪読会メンバーの yana_giさん(id:yana_g)に教えていただいた、TDDの権威、@t_wadaさんの動画を見てみました。

TDDについて理解しやすい。おすすめ動画です。

f:id:shirotamaki:20210625172120p:plain
「ライオンに怒られる」http://www.publickey1.jp/2018/t_wada01.gif

動画:50 分でわかるテスト駆動開発

channel9.msdn.com

以下、動画内容をまとめと感想になります。

  • TDDのゴール「動作するきれいなコード Kent Beck
  • 2つの道がある。
    • きれいな設計を考えてきれいなコードを書く。(完璧主義)
    • 書いて動かしてからきれいにしていく。(堕落、焦り、恐れ) ← TDDはこっち。
  • TDDのサイクル
    1. 次の目標を考える
    2. その目標を示すテストを書く
    3. そのテストを実行して失敗させる(Red)
    4. 目的のコードを書く
    5. 2で書いたテストを成功させる(Green)
    6. テストが通るまでリファクタリングを行う(Refactor)
    7. 1~6を繰り返す
  • TDDと黄金の回転

  • リファクタリング
    • リファクタリングの軸はブレやすい。(忙しいから。後でいいか。とりあえず動けばいいし。直近で恩恵はないし。自己満だし…)
    • つねにプログラミングの中に組み込もう。TDDの中に組み込む。
    • テストは増やすのは簡単だが減らすのは難しい。
  • 具体的に検証内容を書く(こうなってほしい)
  • ゴールから書いていく。(具体的な例を元に逆算して考えることがポイント)
  • 「ゴールはこうなるから、こうなってほしい」と、具体的に。
  • 小さく考えて、積み上げていくイメージ。
  • 問題を小さく分割し、To-Do リストを作成する。
  • 「テストコードにバグがあったらどうする?」問題。
    • テストから先に書いてエラーを出す。その後に実装する。

競プロでテストを書いてみた

TDDに触発され、早速競プロでもテストを書いてみました。 A問題なのですごく簡単なテストですが、今後はB、C問題でも書けるようになりたいと思います。

A - kcal

def atcoder(x, y)
  if x == y
    x
  else
    3 - x - y
  end
end


require 'minitest/autorun'

class AtcoderTest < Minitest::Test
  def test_atocoder
    assert_equal 1, atcoder(2,0)
    assert_equal 2, atcoder(2,2)
  end
end

参考書籍

🍒 まとめ

先週に続き例題問題では、Minitestを実際に動かすことでテストの理解がより深まりました。 また、TDDについて深堀りすることができたのも良かったです。今後も競プロでMinitestを書いてみたりと日頃から使うことで慣れていきたいと思います。

では、また来週!(次回、第4週目)