- 投稿日:2020-02-13T23:02:13+09:00
【備忘録】Ruby : 継承(override,super)
https://qiita.com/noytdm1021/items/9e3a5d67234fd6a904c1 の追記
sample.rbclass Greeting def hello puts "こんにちは #{@name}さん" end end class User < Greeting # Greeting を継承 def initialize(name) @name = name end def hello super # super で親クラスの hello メソッド呼び出し # override puts "Hello. #{@name}." end end user = User.new("Tanaka") user.hello #=>こんにちは Tanakaさん #=>Hello. Tanaka. #どちらも出力
- 投稿日:2020-02-13T22:30:39+09:00
【備忘録】Ruby : 基本的な継承クラスの使い方
sample.rbclass Greeting def hello puts "こんにちは #{@name}さん" end def goodmorning puts "おはよう #{@name}さん" end def goodevening puts "こんばんわ #{@name}さん" end end class Tanaka < Greeting # Greeting を継承 def initialize(name) @name = name end def goodbye # Tanaka クラスのオブジェクトで使用可能な goobye メソッド puts "#{@name}さん さよなら" end end class Sato < Greeting # Greeting を継承 def initialize(name) @name = name end end tanaka = Tanaka.new("Tanaka") tanaka.hello #=>こんにちは Tanakaさん tanaka.goodmorning #=>おはよう Tanakaさん tanaka.goodevening #=>こんばんわ Tanakaさん tanaka.goodbye #=>Tanakaさん さよなら sato = Sato.new("Sato") sato.hello #=><こんにちは Satoさん sato.goodmorning #=>おはよう Satoさん sato.goodevening #=>こんばんわ Satoさん #sato.goodbye => エラー # Userオブジェクトなので継承したGreetingクラスのhelloメソッドを使用可能 #継承の使い方は複数あるクラスの共通処理をまとめて置いたりする。 # => つまり継承元にメソッドを定義しておくことによって複数のクラスオブジェクトで使うことが #可能となる。
- 投稿日:2020-02-13T21:51:29+09:00
【備忘録】Ruby : privateメソッドの使い方
sample.rbrequire 'date' class User attr_accessor :name,:birthday def initialize(name,birthday) @name = name @birthday = birthday end def display_birthday @birthday.strftime("%Y年 %m月 %d日") end def inner_call_display_birthday self.display_birthday # self は省略可能。オブジェクト化して外部から呼び出した場合、 # この self がオブジェクト自体を指す。 # 下の例でいうと user を指す。 display_birthday end def call_private_name private_name # インスタンスメソッドからならprivateメソッドを呼び出せる。 # しかし冒頭に self をつけるとエラーになるので注意。selfはオブジェクト自体をさす。 # selfをつけないメソッドの呼び出しを「関数形式」というらしい。 end private #ここより下に定義されたメソッドは private メソッドになる def private_name #クラス外部から直接呼び出すことは不可能 #同じクラスのインスタンスメソッドからなら呼び出して使える。 @name end end birthday = Date.new(2000,1,1) user = User.new("sato",birthday) puts user.inner_call_display_birthday #=> 2000年01月01日 #puts user.private_name => private_name は private メソッドなので外部から呼び出し不可 puts user.call_private_name #=> sato ## おまけ # あまり使うことはないがprotectedメソッドも同じように定義し、protectedメソッドでは self # をつけても大丈夫
- 投稿日:2020-02-13T21:06:27+09:00
fizzbuzzについて
fizzbuzzとは
1から100まで数を数える時に、3の倍数なら「fizz」、5の倍数なら「buzz」、ただし15の倍数なら「fizzbuzz」、それ以外はその数を答えるもの。
例
1
2
Fizz
4
.
.これを実現するコード
num = 1 while num < 101 str = "" if num % 3 == 0 str = str + "fizz" end if num % 5 == 0 str = str + "buzz" end if str == "" str = str + num.to_s end puts str num += 1 end終わりに
fizzbuzz・・・rubyの基礎って感じがしますね・・・
もっと勉強します!
- 投稿日:2020-02-13T17:48:58+09:00
超初学者向け RailsのMVCについて
Ruby on Railsを学ぼうと思っている方、学び始めの方にMVCってこういう感じのものなんだ、と雰囲気だけでもわかってもらえると良いなと思い投稿します。
MVCとは
MVCとはModel(モデル), View(ビュー), Controller(コントローラー)の3つを指しています。
MVCの役割
- Model(モデル)
Modelとはデータベース(データを保存しているところ)からデータを取り出したり、データベースに新しいデータを保存するといったやりとりをするものです。- View(ビュー)
Viewとは普段みなさんが目にするwebサイト等での見た目の部分、つまり画面です。 Viewファイルを編集することによって画面に何を表示するか、なにを表示させないかを記述します。- Controller(コントローラー)
ControllerとはModelとViewの中間でやりとりをするものです。ViewのURLによってどのようなデータを取り出すか、保存するかを判断しています。(ルーティング)
まとめ
ModelからControllerにデータを受け渡す
ControllerからViewにデータを渡す。
Viewで渡されたデータを表示する。
という認識で良いかと思います。
- 投稿日:2020-02-13T15:45:59+09:00
自動デプロイ時エラー発生
エラー記述
2020年2月 AWS Capistano 自動デプロイエラー解決内容
ローカルディレクトリでbundle exec cap production deployを実行
SSHKit::::ExecuteError: Exception while executing as ec2-user@..**: No such file or directory @ rb_file_s_stat - config/secrets.yml
Caused by:
Errno::ENOENT: No such file or directory @ rb_file_s_stat - config/secrets.yml
ruby ver 2.5.1
Capistano 3.12.0
AWSサーバー
EC2エラーが解決しません。
bundle install 実行済み 本番 ローカル
PC再起動済み
インスタンス再起動済み
nameなどの記述ミスも確認済み
本番環境のサーバー立ち上げ、ディレクトリ確認OK
kill作業→確認済み
push済み現在解決してません。
どなたか解決に繋がる作業やヒント頂ければ幸いです。
必要な情報ファイル名指定頂ければ記述内容載せます。解決できれば記述しておきます。
解決策できました
そもそもスクールの学習カリキュラムの記述を参考にしたことにより
ymlのファイルをひっパテ来れなかったからと思われます。# set :linked_files, %w{ config/secrets.yml }
# desc 'upload secrets.yml'
# task :upload do
# on roles(:app) do |host|
# if test "[ ! -d #{shared_path}/config ]"
# execute "mkdir -p #{shared_path}/config"
# end
# upload!('config/secrets.yml', "#{shared_path}/config/secrets.yml")
# end
# end
# before :starting, 'deploy:upload'
# after :finishing, 'deploy:cleanup'指定のymlファイルをコメントアウト
これで自動デプロイできました。
- 投稿日:2020-02-13T13:40:18+09:00
middleman 正しいコマンド の打ち方
❌
gem install bundler
⭕️
rbenv exec gem install bundler
bash_profileにエイリアス設定したもの。
参考:https://qiita.com/naokazuterada/items/072f28ee15f8fe86bdec
- 投稿日:2020-02-13T13:31:57+09:00
rbenv install 2.6.5を実行、Installing openssl-1.1.1d...で止まる
5-10分待てば、動き出す。
今回の私の場合は、5分弱で動き出して、結果うまくいった。
- 投稿日:2020-02-13T13:14:41+09:00
FluentdをMacでセットアップして、CloudWatchLogsへログ転送するまで
はじめに
実務で、ローカルで稼働しているMac miniが複数台あるので、それをCloudWatchLogsへ集約したいという要望があったので、勉強がてらFluentDを使った時の備忘録的なアレです
色々、試行錯誤しながら試したので、同じくハマっている人の助けになればと思います
失敗パターン
td-agentを使って、cloudwatchLogsに転送させる設定をしようとしたが、ripperが足りない?せいか、以下のエラーが発生して、td-agent内包のRubyを使っているため、これ以上は自力解決が難しいと思い、断念
`require': cannot load such file -- ripper (LoadError)
やったこと
Fluentd公式から、MacOS向けのtd-agent.dmgをDLして、セットアップ
https://docs.fluentd.org/installation/install-by-dmgCloudWatchLogsに飛ばすためのPluginは、ここからDL
https://github.com/fluent-plugins-nursery/fluent-plugin-cloudwatch-logsREADME.md通りに、
gem install fluent-plugin-cloudwatch-logs
を実行しても、td-agent自体が、そのものに内包されているRuby(2.4)を使用してるっぽいので、エラーになるStarting td-agent: 2018-05-31 01:27:51 +0000 [error]: fluent/supervisor.rb:373:rescue in main_process: config error file="/etc/td-agent/td-agent.conf" error="Unknown output plugin 'cloudwatch_logs'. Run 'gem search -rd fluent-plugin' to find plugins" td-agent
- td-agentを使っている場合は、td-agent-gemを使用して、インストールする
インストールコマンド
/opt/td-agent/usr/sbin/td-agent-gem install <プラグイン>
インストール済みリストを取得
/opt/td-agent/usr/sbin/td-agent-gem list
参考にしたリンク
https://qiita.com/nkg/items/cbf72adb436456804d76
成功パターン
td-agentだと、内包されているRubyを使う方法だったので、すでにインストールされてるRubyを使う方法を採用しました
下記を実行すると、CloudWatchLogsへ、自動的にログストリームが作成され、ログが出力されるようになりましたやったこと
- gem版のFluentdをインストール→セットアップ
# Fluentdのインストール $gem install fluentd -N # セットアップ $ fluentd --setup ./fluent`https://docs.fluentd.org/installation/install-by-gem
- gemを使って、CloudWatchLogs-Pluginをインストール
# fluentd-cloudwatch-logs-pluginをインストール gem install fluent-plugin-cloudwatch-logs
- fluentd.confファイルを編集
<source> @type tail path ./logs/logger.log tag sample <parse> @type multiline format_firstline /\[(\d{4}-\d{2}-\d{2}T\d{1,2}:\d{1,2}:\d{1,2}.\d{1,3})\]/ format1 /^\[(?<logtime>\d{4}-\d{2}-\d{2}T\d{1,2}:\d{1,2}:\d{1,2}.\d{1,3})\] \[(?<lebel>.*)\] (?<hoge>.*) - \[(?<title>.*)\] (?<message>.*)/ </parse> </source> <match sample> @type cloudwatch_logs log_group_name test auto_create_stream true use_tag_as_stream true </match>
- IAM User Policyの設定
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:*", "s3:GetObject" ], "Resource": [ "arn:aws:logs:us-east-1:*:*", "arn:aws:s3:::*" ] } ] }
- AWSのアクセスキーとシークレットキーを環境変数に登録
export AWS_REGION=us-east-1 export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY" export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_ACCESS_KEY"
fluentdを起動&停止
- 起動
fluentd -c ./fluent/fluent.conf -o ~/Desktop/tmp/fluentd.log -vv &
- 停止
pkill -f fluentd
まとめ
初めてFluentdを使って、ログの収集を行いましたが、ドキュメントがかなり充実していたので、ハマりはしたものの設定ファイルを作る等は、そこまで苦労しませんでした。
いろんなPluginがあるので、まだまだ色々できそうな気がしています。
また、Fluentdとtd-agentでの挙動が違ったりする場合があるので、調査の時には注意が必要かなと思いました。
- 投稿日:2020-02-13T11:16:45+09:00
テスト駆動開発から始めるRuby入門 ~アルゴリズムのパフォーマンスチューニングとベンチマークを実践する~
外伝
初めに
この記事は テスト駆動開発から始めるRuby入門 ~2時間でTDDとリファクタリングのエッセンスを体験する~ の外伝エピソードです。ちなみに前半の元ネタは テスト駆動開発の付録B フィボナッチをRubyで実装したものです。後半はオリジナルエピソードでは言及できなかったアルゴリズムの プロファイリング と ベンチマーク の実施に関して解説しています。
前提として、テスト駆動開発から始めるRuby入門 ~ソフトウェア開発の三種の神器を準備する~ で開発環境を構築したところから始まります。別途、セットアップ済み環境 を用意していますのでこちらからだとすぐに始めることが出来ます。
前半の用語の詳細については エピソード1 で解説しています。後半の用語の詳細については エピソード3 で解説していますので興味があれば御一読ください。
パフォーマンスチューニングから始めるテスト駆動開発
概要
フィボナッチ数 を計算するプログラムを テスト駆動開発 で作ります。
初めに TODOリスト をプログラミング作業をリストアップします。次に、最初に失敗するテストを作成します。 その後 仮実装でベタ書き値を返すテストを実行します。 それから 三角測量 を使って慎重にアルゴリズムを一般化していきます。そして、 明白な実装 によりアルゴリズムを完成させます。
アルゴリズムが完成したら リファクタリング を実施してコードベースを 動作するきれいなコード に洗練していきます。
動作するきれいなコード になったらパフォーマンスの検証をするためパフォーマンスチューニングを実施します。 パフォーマンスチューニングでは プロファイラ を使ったプログラムのボトルネック調査を実施します。アルゴリズムのパフォーマンスを改善したら別途追加したアルゴリズムと ベンチマーク を実施してどのアルゴリズムを採用するかを決定します。
仕上げは、 モジュール分割 によりRubyアプリケーションとしてリリースします。
仕様
仕様は以下の通りです。
n 番目のフィボナッチ数を Fn で表すと、Fn は再帰的に
F0 = 0,
F1 = 1,
Fn + 2 = Fn + Fn + 1 (n ≧ 0)
で定義される。これは、2つの初期条件を持つ漸化式である。
この数列 (Fn)はフィボナッチ数列(フィボナッチすうれつ、(英: Fibonacci sequence)と呼ばれ、
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987,
1597, 2584, 4181, 6765, 10946, …(オンライン整数列大辞典の数列 A45) と続く。最初の二項は 0, 1
であり、以後どの項もその直前の2つの項の和となっている。— Wikipedia
表形式にすると以下のようになります。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 … 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 … TODOリスト
TODOリスト
何をテストすべきだろうか----着手する前に、必要になりそうなテストをリストに書き出しておこう。
— テスト駆動開発
TODOリスト を書き出す取っ掛かりとして仕様で定義されている内容からプログラムで実施できる内容に分解してきましょう。仕様では以下のように定義されているので。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 … 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 … 最初のタスクは
0を渡したら0を返す
振る舞いをするプログラムを作ることにしましょう。
0 … 0 … 同様のパターンで他のタスクも切り出してみましょう。
0 1 … 0 1 …
0 1 2 … 0 1 1 … とりあえず、3件ほどタスクとして切り出したので TODOリスト の作成は一旦終了してプログラミング作業に入るとしましょう。
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す
仮実装
仮実装を経て本実装へ
失敗するテストを書いてから、最初に行う実装はどのようなものだろうか----ベタ書きの値を返そう。
— テスト駆動開発
0を渡したら0を返す
早速、 TODOリスト の1つ目から片付けていくとしましょう。
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す
まずは最初に失敗するテストを書きますがまずは以下のサンプルコードを使ってテスティングフレームワークの動作確認をしておきましょう。今回利用するRubyのテスティングフレームワークは minitestです。
test
フォルダ以下にfibonacci_test.rb
ファイルを追加して以下のコードを入力します。
test/fibonacci_test.rb
# frozen_string_literal: true require 'minitest/reporters' Minitest::Reporters.use! require 'minitest/autorun' class FibonacciTest < Minitest::Test def greeting 'hello world' end def test_greeting assert_equal 'hello world', greeting end end今回テスト結果を見やすくするため
minitest/reporters
というgemを使っているのでまずインストールしておきます。$ gem install minitest-reportersgemインストールが完了したらコマンドラインに
ruby test/fibonacci_test.rb
コマンドを入力してテストを実施します。$ ruby test/fibonacci_test.rb Started with run options --seed 28548 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.01040s 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ...テストは無事実行されたようですね。続いてテストが失敗するか確認しておきましょう。
greeting
メソッドのhello world
をhello world!
に変更してテストを実行します。... class Fibonacci < Minitest::Test def greeting 'hello world!' end ... endテストは失敗して以下のようなメッセージが表示されました。
$ ruby test/fibonacci_test.rb Started with run options --seed 30787 FAIL["test_greeting", <Minitest::Reporters::Suite:0x000055eaefeef5e0 @name="Fibonacci">, 0.003157061990350485] test_greeting#Fibonacci (0.00s) Expected: "hello world" Actual: "hello world!" test/fibonacci_test.rb:13:in `test_greeting` 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00398s 1 tests, 1 assertions, 1 failures, 0 errors, 0 skipsテスティングフレームワークのセットアップと動作確認が終了したので最初の失敗するテストを書きます。まずは アサーションファースト でサンプルコードを削除して以下のコードにします。
... class FibonacciTest < Minitest::Test def test_fibonacci assert_equal 0, fib(0) end endテストは無事?失敗します。
$ ruby test/fibonacci_test.rb Started with run options --seed 21656 ERROR["test_fibonacci", <Minitest::Reporters::Suite:0x0000559acae8d068 @name="FibonacciTest">, 0.001314591965638101] test_fibonacci#FibonacciTest (0.00s) Minitest::UnexpectedError: NoMethodError: undefined method `fib' for #<FibonacciTest:0x0000559acae8d860> test/fibonacci_test.rb:9:in `test_fibonacci'` 1/1: [========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00419s 1 tests, 0 assertions, 0 failures, 1 errors, 0 skipsまずは 仮実装 でテストを通すようにしましょう。
... class FibonacciTest < Minitest::Test def fib(n) 0 end def test_fibonacci assert_equal 0, fib(0) end endテストはレッドからグリーンになりました。
$ ruby test/fibonacci_test.rb Started with run options --seed 2885 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00352s 1 tests, 1 assertions, 0 failures, 0 errors, 0 skipsテストが通ったのでバージョン管理システムにコミットしておきます。
$ git add . $ git commit -m 'test: 0を渡したら0を返す'
0を渡したら0を返す1を渡したら1を返す
2を渡したら1を返す
三角測量
三角測量
テストから最も慎重に一般化を引き出すやり方はどのようなものだろうか----2つ以上の例があるときだけ、一般化を行うようにしよう。
— テスト駆動開発
1を渡したら1を返す
1つ目の TODOリスト を片付けたので2つ目の TODOリスト に取り掛かるとしましょう。
0を渡したら0を返す1を渡したら1を返す
2を渡したら1を返す
テストファースト アサーションファースト なのでまずはテストを追加するとこから始めます。
... class FibonacciTest < Minitest::Test def fib(n) 0 end def test_fibonacci assert_equal 0, fib(0) assert_equal 1, fib(1) end endテストは失敗します。
$ ruby test/fibonacci_test.rb Started with run options --seed 21207 FAIL["test_fibonacci", <Minitest::Reporters::Suite:0x000056525007ccb0 @name="FibonacciTest">, 0.0014098359970375896] test_fibonacci#FibonacciTest (0.00s) Expected: 1 Actual: 0 test/fibonacci_test.rb:14:in `test_fibonacci` 1/1: [========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00196s 1 tests, 2 assertions, 1 failures, 0 errors, 0 skips仮実装 で0しか返さないベタ書きのコードなのだから当然ですよね。0ならば0を返してそれ以外の場合は1を返すようにプログラムを変更します。
... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? 1 end ... endプログラムの変更によりテストはレッドからグリーンに戻りました。
$ ruby test/fibonacci_test.rb Started with run options --seed 58331 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00169s 1 tests, 2 assertions, 0 failures, 0 errors, 0 skipsここでコミットしておきます。
$ git add . $ git commit -m 'test: 1を渡したら1を返す'リファクタリング
0を渡したら0を返す
1を渡したら1を返す2を渡したら1を返す
次の TODOリスト に着手する前にテストケース内の重複が気になり始めたので、共通部分をアサーションからくくり出して、入力値と期待値の組でテストを回すようにテストコードを リファクタリング します。
... class Fibonacci < Minitest::Test ... def test_fibonacci cases = [[0, 0], [1, 1]] cases.each do |i| assert_equal i[1], fib(i[0]) end end endテストを実行してプログラムが壊れていないことを確認します。
$ ruby test/fibonacci_test.rb Started with run options --seed 5991 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00200s 1 tests, 2 assertions, 0 failures, 0 errors, 0 skipsプログラムが壊れていないことが確認できたのでコミットしておきます。
$ git add . $ git commit -m 'refactor: アルゴリズムの置き換え'1を渡したら2を返す
0を渡したら0を返す
1を渡したら1を返す2を渡したら1を返す
テストコードの リファクタリング を実施したので続いて TODOリスト の3つ目に着手します。まずは アサーション の追加ですね。
... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? 1 end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1]] cases.each do |i| assert_equal i[1], fib(i[0]) end end endおや、今回はプロダクトコードを変更しなくてもテストは通るようです。
$ ruby test/fibonacci_test.rb Started with run options --seed 26882 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00287s 1 tests, 3 assertions, 0 failures, 0 errors, 0 skipsここでコミットしておきます。
$ git add . $ git commit -m 'test: 1を渡したら2を返す'
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す明白な実装
明白な実装
シンプルな操作を実現するにはどうすればいいだろうか----そのまま実装しよう。
仮実装や三角測量は、細かく細かく刻んだ小さなステップだ。だが、ときには実装をどうすべきか既に見えていることが。
そのまま進もう。例えば先ほどのplusメソッドくらいシンプルなものを仮実装する必要が本当にあるだろうか。
普通は、その必要はない。頭に浮かんだ明白な実装をただ単にコードに落とすだけだ。もしもレッドバーが出て驚いたら、あらためてもう少し歩幅を小さくしよう。— テスト駆動開発
3を渡したら2を返す
最初に定義した TODOリスト の内容は完了しましたがプログラムの一般化にはまだテストケースが足りないでしょう。3を渡した場合のテストケースを追加します。
0 1 2 3 … 0 1 1 2 …
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す3を渡したら2を返す
テストケースを追加してテストを実施します。
... class FibonacciTest < Minitest::Test ... def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2]] cases.each do |i| assert_equal i[1], fib(i[0]) end end endテストが失敗しました。
$ ruby test/fibonacci_test.rb Started with run options --seed 7598 FAIL["test_fibonacci", <Minitest::Reporters::Suite:0x000055c987498120 @name="FibonacciTest">, 0.00104286998976022] test_fibonacci#FibonacciTest (0.00s) Expected: 2 Actual: 1 test/fibonacci_test.rb:17:in `block in test_fibonacci'' test/fibonacci_test.rb:16:in `each' test/fibonacci_test.rb:16:in `test_fibonacci' 1/1: [========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00160s 1 tests, 4 assertions, 1 failures, 0 errors, 0 skips2までは1を返すので条件分岐を追加します。
class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n <= 2 1 end ... endまだ、失敗したままです。
$ ruby test/fibonacci_test.rb Started with run options --seed 26066 FAIL["test_fibonacci", <Minitest::Reporters::Suite:0x0000562bc96ee330 @name="Fibonacci">, 0.0055934099946171045] test_fibonacci#Fibonacci (0.01s) Expected: 2 Actual: 1 test/fibonacci_test.rb:24:in `block in test_fibonacci' test/fibonacci_test.rb:23:in `each' test/fibonacci_test.rb:23:in `test_fibonacci'' 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00882s 1 tests, 4 assertions, 1 failures, 0 errors, 0 skipsどの条件にも該当としない場合は2を返すように変更します。
... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n <= 2 2 end ... endグリーンになりました。
$ ruby test/fibonacci_test.rb Started with run options --seed 25117 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.01680s 1 tests, 4 assertions, 0 failures, 0 errors, 0 skipsここでコミットしておきます。
$ git add . $ git commit -m 'test: 3を渡したら2を返す'
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す
3を渡したら2を返すフィボナッチ数計算
そろそろゴールが見えてきました。TODOリスト を追加してフィボナッチ数計算アルゴリズムを完成させましょう。
0 1 2 3 4 … 0 1 1 2 3 …
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す
3を渡したら2を返す4を渡したら3を返す
テストファースト アサートファースト です。
... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n <= 2 2 end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3]] cases.each do |i| assert_equal i[1], fib(i[0]) end end end$ ruby test/fibonacci_test.rb Started with run options --seed 34595 FAIL["test_fibonacci", <Minitest::Reporters::Suite:0x0000564fdbd6dfe0 @name="Fibonacci">, 0.005386559059843421] test_fibonacci#Fibonacci (0.01s) Expected: 3 Actual: 2 test/fibonacci_test.rb:24:in `block in test_fibonacci' test/fibonacci_test.rb:23:in `each' test/fibonacci_test.rb:23:in `test_fibonacci'' 1/1: [==========================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.01030s 1 tests, 5 assertions, 1 failures, 0 errors, 0 skips最後に2を返すのではなく合計値をかえすのだから
... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n <= 2 1 + 1 end ... end$ ruby test/fibonacci_test.rb Started with run options --seed 10848 FAIL["test_fibonacci", <Minitest::Reporters::Suite:0x00005621247c9f48 @name="Fibonacci">, 0.0007573128677904606] test_fibonacci#Fibonacci (0.00s) Expected: 3 Actual: 2 test/fibonacci_test.rb:24:in `block in test_fibonacci' test/fibonacci_test.rb:23:in `each' test/fibonacci_test.rb:23:in `test_fibonacci'' 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00130s 1 tests, 5 assertions, 1 failures, 0 errors, 0 skips一つ前の
fib
の結果を足すのだから... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n <= 2 fib(n - 1) + 1 end ... endグリーンになりました。
$ ruby test/fibonacci_test.rb Started with run options --seed 25629 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00467s 1 tests, 5 assertions, 0 failures, 0 errors, 0 skipsここでコミット...しないで今回は更に進めます。 TODOリスト を追加します。
0 1 2 3 4 5 … 0 1 1 2 3 5 …
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す
3を渡したら2を返す
4を渡したら3を返す5を渡したら5を返す
テストケースを追加して
... class FibonacciTest < Minitest::Test ... def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], fib(i[0]) end end endレッド
$ ruby test/fibonacci_test.rb Started with run options --seed 54754 FAIL["test_fibonacci", <Minitest::Reporters::Suite:0x000055c42397e108 @name="Fibonacci">, 0.00174815789796412] test_fibonacci#Fibonacci (0.00s) Expected: 5 Actual: 4 test/fibonacci_test.rb:24:in `block in test_fibonacci' test/fibonacci_test.rb:23:in `each' test/fibonacci_test.rb:23:in `test_fibonacci'' 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00237s 1 tests, 6 assertions, 1 failures, 0 errors, 0 skips結局1つ前と2つ前の
fib
の結果を合計して返しているのだから... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n <= 2 fib(n - 1) + fib(n - 2) end ... endグリーン
$ ruby test/fibonacci_test.rb Started with run options --seed 8399 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00107s 1 tests, 6 assertions, 0 failures, 0 errors, 0 skips一般化ができたので0の場合と1の場合は与えらた値を返せば良くなったので
... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n == 1 fib(n - 1) + fib(n - 2) end ... endリファクター
$ ruby test/fibonacci_test.rb Started with run options --seed 42476 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00162s 1 tests, 6 assertions, 0 failures, 0 errors, 0 skipsフィボナッチ数計算アルゴリズムが完成したのでコミットします。
$ git add . $ git commit -m 'feat: フィボナッチ数計算'
0を渡したら0を返す
1を渡したら1を返す
2を渡したら1を返す
3を渡したら2を返す
4を渡したら3を返す
5を渡したら5を返すリファクタリング
リファクタリング(名詞) 外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること。
— リファクタリング(第2版)
リファクタリングする(動詞) 一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること。
— リファクタリング(第2版
アルゴリズムの実装は出来ましたがアプリケーションとしては不十分なので リファクタリング を適用してコードを 動作するきれいなコード に洗練していきます。
クラスの抽出
まず、テストケース内でメソッドを定義していますがこれでは一つのクラスでアルゴリズムの実行とテストの実行という2つの責務を
FibonacciTest
クラスが担当しています。 単一責任の原則 に違反しているので クラスの抽出 を実施して責務を分担させましょう。... class FibonacciTest < Minitest::Test def fib(n) return 0 if n.zero? return 1 if n == 1 fib(n - 1) + 1 end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], fib(i[0]) end end end
Fibonacci
クラスを作成して クラスメソッド のFibonacci.fib
をコピー&ペーストで作成します。... class Fibonacci def self.fib(n) return 0 if n.zero? return 1 if n == 1 fib(n - 1) + fib(n - 2) end end class FibonacciTest < Minitest::Test def self.fib(n) return 0 if n.zero? return 1 if n == 1 fib(n - 1) + fib(n - 2) end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], fib(i[0]) end end endテストが壊れていないことを確認したら
FibonacciTest
クラス内の クラスメソッドFIbonacciTest.fib
を削除して フィクスチャーsetup
メソッドを作成して インスタンス変数@fib
にFibonacci
クラスの参照を代入します。... class Fibonacci def self.fib(n) return 0 if n.zero? return 1 if n == 1 fib(n - 1) + fib(n - 2) end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.fib(i[0]) end end endテストが壊れていないかを確認します。
$ ruby test/fibonacci_test.rb Started with run options --seed 40694 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00393s 1 tests, 6 assertions, 0 failures, 0 errors, 0 skipsクラスの抽出 の リファクタリング 適用が完了したのでコミットします。
$ git add . $ git commit -m 'refactor: クラスの抽出'変数名の変更
続いて、
Fibonacci
クラスに移動した クラスメソッド ですが引数がn
というのは分かりづらいですね。... class Fibonacci def self.fib(n) return 0 if n.zero? return 1 if n == 1 fib(n - 1) + fib(n - 2) end end ...ここは省略せず、引数の型を表す名前に変更して可読性を上げておきましょう。
... class Fibonacci def self.fib(number) return 0 if number.zero? return 1 if number == 1 fib(number - 1) + fib(number - 2) end end ...テストが壊れていないか確認します。
$ ruby test/fibonacci_test.rb Started with run options --seed 37760 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00744s 1 tests, 6 assertions, 0 failures, 0 errors, 0 skipsコミットします。
$ git add . $ git commit -m 'refactor: 変数名の変更'メソッド名の変更
Fibonacci
クラスの クラスメソッドFibonacci.fib
はフィボナッチ数を計算するメソッドですが名前が紛らわしいので メソッド名の変更 を適用します。... class Fibonacci def self.fib(number) return 0 if number.zero? return 1 if number == 1 fib(number - 1) + fib(number - 2) end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.fib(i[0]) end end endインスタンスメソッド を
fib
からcalc
に変更します。今回は呼び出し先のFibonacciTest
のテストコードも修正する必要があります。... class Fibonacci def self.calc(number) return 0 if number.zero? return 1 if number == 1 calc(number - 1) + calc(number - 2) end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.calc(i[0]) end end endテストが壊れていないか確認します。
$ ruby test/fibonacci_test.rb Started with run options --seed 15099 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00285s 1 tests, 6 assertions, 0 failures, 0 errors, 0 skipsメソッド名の変更 の適用が完了したのでコミットします。
$ git add . $ git commit -m 'refactor: メソッド名の変更'パフォーマンスチューニング
心がけるべきことは、他のパフォーマンス分析とおなじように、実際のデータを使い、リアルな利用パターンを試し、プロファイリングを行ってからでないと、パフォーマンスを問題にする資格はない、ということだ。
— テスト駆動開発
これまでのテストケースでは小さな値を使ってきましたが大きな値の場合のプログラムの挙動が問題無いか確認しておく必要があります 100番目までのフィボナッチ数列 を参考に大きな値の場合のテストケースを追加してアプリケーションのパフォーマンスを検証しましょう。
メモ化によるパフォーマンス改善
TODOリスト に新しいタスクを追加します。
0 1 … 38 39 40 … 0 1 … 39088169 63245986 102334155 …
- 大きな数値を計算する
テストケースを追加します。
... class FibonacciTest < Minitest::Test ... def test_large_number assert_equal 102_334_155, @fib.calc(40) end endテストを実行します
$ ruby test/fibonacci_test.rb Started with run options --seed 1160 2/2: [=========================================] 100% Time: 00:00:51, Time: 00:00:51 Finished in 51.15914s 2 tests, 7 assertions, 0 failures, 0 errors, 0 skipsテストが完了するのが随分遅くなってしまいました。これはアルゴリズムを改善する必要がありそうです。 まずは プロファイラ を使って実行状況を確認します。今回は profileライブラリ を使います。
$ ruby -r profile test/fibonacci_test.rb Started with run options --seed 42383 2/1: [====================== ] 50% Time: 00:00:00, ETA: 00:00:00処理が終わらないようなら
ctr-c
で強制終了すれば結果が出力されます。出力内容のFibonacci.calc
がフィボナッチ数計算メソッド実行部分です。... % cumulative self self total time seconds seconds calls ms/call ms/call name 192.39 25.50 25.50 2 12750.69 12750.69 Thread::Queue#pop 75.32 35.49 9.98 246940 0.04 1.65 Fibonacci.calc ....
再帰呼び出しが何度も実行された結果パフォーマンスを低下させているようです。ここは メモ化 を使ってパフォーマンスを改善させましょう。
... class Fibonacci def self.calc(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= calc(number - 1, memo) + calc(number - 2, memo) end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.calc(i[0]) end end def test_large_number assert_equal 102_334_155, @fib.calc(40) end endプロファイラ で確認します。
$ ruby -r profile test/fibonacci_test.rb Started with run options --seed 20468 2/2: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.04214s 2 tests, 7 assertions, 0 failures, 0 errors, 0 skips % cumulative self self total time seconds seconds calls ms/call ms/call name ... 12.09 0.06 0.06 2 32.09 32.09 Thread::Queue#pop ... 1.33 0.26 0.01 105 0.07 1.41 Fibonacci.calc ...一気に再帰呼び出し回数が減りパフォーマンスを改善することが出来ましたのでコミットします。
$ git add . $ git commit -m 'perf: メモ化によるパフォーマンス改善'ベンチマーク
続いて、異なるフィボナッチ数計算アルゴリズムを実装してどのアルゴリズムを採用するべきかを ベンチマーク を取って判断したいと思います。
ループ処理による実装
まずはループ処理によるフィボナッチ数計算のアルゴリズムを実装します。以下が テストファースト アサートファースト で作成したコードです。
... class Fibonacci def self.calc(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= calc(number - 1, memo) + calc(number - 2, memo) end def self.calc2(number) a = 0 b = 1 c = 0 (0...number).each do |i| a = b b = c c = a + b end c end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.calc(i[0]) end end def test_large_number assert_equal 102_334_155, @fib.calc(40) end def test_large_number_calc2 assert_equal 102_334_155, @fib.calc2(40) end end$ ruby test/fibonacci_test.rb -n test_large_number_calc2 Started with run options -n test_large_number_calc2 --seed 18167 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00123s 1 tests, 1 assertions, 0 failures, 0 errors, 0 skipsテストが通ることを確認したらコミットします。
$ git add . $ git commit -m 'feat: ループ処理による実装'一般項による実装
フィボナッチ数列の一般項 で定義されているのでこれを テストファースト アサートファースト で実装します。
... class Fibonacci def self.calc(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= calc(number - 1, memo) + calc(number - 2, memo) end def self.calc2(number) a = 0 b = 1 c = 0 (0...number).each do |i| a = b b = c c = a + b end c end def self.calc3(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.calc(i[0]) end end def test_large_number assert_equal 102_334_155, @fib.calc(40) end def test_large_number_calc2 assert_equal 102_334_155, @fib.calc2(40) end def test_large_number_calc3 assert_equal 102_334_155, @fib.calc3(40) end end$ ruby test/fibonacci_test.rb -n test_large_number_calc3 Started with run options -n test_large_number_calc3 --seed 55659 1/1: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00111s 1 tests, 1 assertions, 0 failures, 0 errors, 0 skipsテストが壊れていないか確認したらコミットします。
$ git add . $ git commit -m 'feat: 一般項による実装'メソッド名の変更
各アルゴリズムのメソッド名が
calc
では分かりづらいので メソッド名の変更 を適用して リファクタリング します。... class Fibonacci def self.recursive(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= recursive(number - 1, memo) + recursive(number - 2, memo) end def self.calc2(number) a = 0 b = 1 c = 0 (0...number).each do |i| a = b b = c c = a + b end c end def self.calc3(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.recursive(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @fib.recursive(40) end def test_large_number_calc2 assert_equal 102_334_155, @fib.calc2(40) end def test_large_number_calc3 assert_equal 102_334_155, @fib.calc3(40) end endまず、最初に実装した再帰呼び出しアルゴリズムのメソッド名を
Fibonacci.calc
からFibonacci.recursive
に変更します。$ ruby test/fibonacci_test.rb Started with run options --seed 15174 4/4: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00137s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skips続いて、ループアルゴリズムのメソッド名を
Fibonacci.calc2
からFibonacci.loop
に変更します。class Fibonacci def self.recursive(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= recursive(number - 1, memo) + recursive(number - 2, memo) end def self.loop(number) a = 0 b = 1 c = 0 (0...number).each do |i| a = b b = c c = a + b end c end def self.calc3(number) a = ((1 + Math.sqrt(5)) / 2) ** number b = ((1 - Math.sqrt(5)) / 2) ** number ((a - b) / Math.sqrt(5)).round end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.recursive(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @fib.recursive(40) end def test_large_number_loop assert_equal 102_334_155, @fib.loop(40) end def test_large_number_calc3 assert_equal 102_334_155, @fib.calc3(40) end end$ ruby test/fibonacci_test.rb Started with run options --seed 28586 4/4: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00188s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skips最後に、一般項アルゴリズムのメソッド名を
Fibonacci.calc3
からFibonacci.general_term
に変更します。... class Fibonacci def self.recursive(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= recursive(number - 1, memo) + recursive(number - 2, memo) end def self.loop(number) a = 0 b = 1 c = 0 (0...number).each do |i| a = b b = c c = a + b end c end def self.general_term(number) a = ((1 + Math.sqrt(5)) / 2) ** number b = ((1 - Math.sqrt(5)) / 2) ** number ((a - b) / Math.sqrt(5)).round end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.recursive(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @fib.recursive(40) end def test_large_number_loop assert_equal 102_334_155, @fib.loop(40) end def test_large_number_general_term assert_equal 102_334_155, @fib.general_term(40) end end$ ruby test/fibonacci_test.rb Started with run options --seed 42729 4/4: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00736s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skips変更によりテストが壊れていないことを確認したらコミットします。
$ git add . $ git commit -m 'refactor: メソッド名の変更'サブクラスによるタイプコードの置き換え 1
現在の
Fibonacci
クラスはアルゴリズムを追加する場合クラスを編集する必要があります。その際に既存のアルゴリズムを壊してしまう可能性があります。これは オープン・クローズド原則 に違反しているので サブクラスによるタイプコードの置き換え を適用してアルゴリズムを カプセル化 して、安全に追加・変更できる設計に リファクタリング します。... class Fibonacci def self.recursive(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= recursive(number - 1, memo) + recursive(number - 2, memo) end def self.loop(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end def self.general_term(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciRecursive def calc(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= calc(number - 1, memo) + calc(number - 2, memo) end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci @recursive = FibonacciRecursive.new end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @recursive.calc(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.calc(40) end def test_large_number_loop assert_equal 102_334_155, @fib.loop(40) end def test_large_number_general_term assert_equal 102_334_155, @fib.general_term(40) end end$ ruby test/fibonacci_test.rb Started with run options --seed 12762 4/4: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00130s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skipsまず、クラスの抽出 により再帰呼び出しアルゴリズムの メソッドオブジェクト
FibonacciRecursive
クラスを作成して テスト フィクスチャー で インスタンス化 して インスタンス変数 にオブジェクトの参照を代入します。ここではメソッドの呼び出しがexec
に変更されているのでテストコードもそれに合わせて変更します。... class Fibonacci def self.loop(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end def self.general_term(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciRecursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci @recursive = FibonacciRecursive.new end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @recursive.exec(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.exec(40) end def test_large_number_loop assert_equal 102_334_155, @fib.loop(40) end def test_large_number_general_term assert_equal 102_334_155, @fib.general_term(40) end endまだ、 仕掛ですがコードが壊れていない状態でコミットをしておきます。
$ git add . $ git commit -m 'refactor(WIP): サブクラスによるタイプコードの置き換え'サブクラスによるタイプコードの置き換え 2
続いて、 メソッドオブジェクト
FibonacciLoop
クラスを抽出します。... class Fibonacci def self.general_term(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciRecursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end class FibonacciLoop def exec(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci @recursive = FibonacciRecursive.new @loop = FibonacciLoop.new end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @recursive.exec(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.exec(40) end def test_large_number_loop assert_equal 102_334_155, @loop.exec(40) end def test_large_number_general_term assert_equal 102_334_155, @fib.general_term(40) end end$ ruby test/fibonacci_test.rbStarted with run options --seed 33171 4/4: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00337s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skipsコミットします。
$ git add . $ git commit -m 'refactor(WIP): サブクラスによるタイプコードの置き換え'サブクラスによるタイプコードの置き換え 3
続いて、 メソッドオブジェクト
FibonacciGeneralTerm
クラスを抽出します。... class Fibonacci end class FibonacciRecursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end class FibonacciLoop def exec(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end end class FibonacciGeneralTerm def exec(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciTest < Minitest::Test def setup @fib = Fibonacci @recursive = FibonacciRecursive.new @loop = FibonacciLoop.new @general_term = FibonacciGeneralTerm.new end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @recursive.exec(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.exec(40) end def test_large_number_loop assert_equal 102_334_155, @loop.exec(40) end def test_large_number_general_term assert_equal 102_334_155, @general_term.exec(40) end end$ ruby test/fibonacci_test.rbStarted with run options --seed 65058 4/4: [===========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.01576s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skipsコミットします。
$ git add . $ git commit -m 'refactor(WIP): サブクラスによるタイプコードの置き換え'サブクラスによるタイプコードの置き換え 4
最後に、
Fibonacci
クラスに Strategyパターン を適用して各アルゴリズムの実行を 委譲 します。... class Fibonacci def initialize(algorithm) @algorithm = algorithm end def exec(number) @algorithm.exec(number) end end class FibonacciRecursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end class FibonacciLoop def exec(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end end class FibonacciGeneralTerm def exec(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end class FibonacciTest < Minitest::Test def setup @recursive = Fibonacci.new(FibonacciRecursive.new) @loop = Fibonacci.new(FibonacciLoop.new) @general_term = Fibonacci.new(FibonacciGeneralTerm.new) end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @recursive.exec(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.exec(40) end def test_large_number_loop assert_equal 102_334_155, @loop.exec(40) end def test_large_number_general_term assert_equal 102_334_155, @general_term.exec(40) end endサブクラスによるタイプコードの置き換え の適用が完了したのでコメントから
(WIP)
を外してコミットします。$ git add . $ git commit -m 'refactor: サブクラスによるタイプコードの置き換え'ファイル分割
続いてテストとアプリケーションを分割します。
lib
ディレクトリを作成してfibonacci.rb
ファイルを追加してアプリケーションコード部分をカット&ペーストします。
lib/fibonacci.rb
# frozen_string_literal: true # Fibonacci Calcultor class Fibonacci def initialize(algorithm) @algorithm = algorithm end def exec(number) @algorithm.exec(number) end end # Fibonacci Recursive algorithm class FibonacciRecursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end # Fibonacci Loop algorithm class FibonacciLoop def exec(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end end # Fibonacci General Term algorithm class FibonacciGeneralTerm def exec(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end続いて、分割した
fibonacci.rb
ファイル内に定義されたクラスを読み込むようにテストクラスを修正します。 ファイルの読み込みにはrequire
を使います。
test/fibonacci_test.rb
# frozen_string_literal: true require 'minitest/reporters' Minitest::Reporters.use! require 'minitest/autorun' require './lib/fibonacci' class FibonacciTest < Minitest::Test def setup @fib = Fibonacci.new(FibonacciRecursive.new) @recursive = Fibonacci.new(FibonacciRecursive.new) @loop = Fibonacci.new(FibonacciLoop.new) @general_term = Fibonacci.new(FibonacciGeneralTerm.new) end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @fib.calc(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.calc(40) end def test_large_number_loop assert_equal 102_334_155, @loop.calc(40) end def test_large_number_general_term assert_equal 102_334_155, @general_term.calc(40) end end$ ruby test/fibonacci_test.rb Started with run options --seed 39723 4/4: [==========================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.00227s 4 tests, 9 assertions, 0 failures, 0 errors, 0 skips分割したファイルからクラスが読み込まれテストが通ることを確認したらコミットします。
$ git add . $ git commit -m 'feat: ファイル分割'ベンチマークの実施
ベンチマーク を実施する準備が出来たので
test
ディレクトリに以下のfibonacci_benchmark.rb
ファイルを追加します。
test/fibonacci_benchmark.rb
# frozen_string_literal: true require 'minitest' require 'minitest/autorun' require 'minitest/benchmark' require './lib/fibonacci' class FibonacciTestBenchmark < Minitest::Benchmark def setup @recursive = Fibonacci.new(FibonacciRecursive.new) @loop = Fibonacci.new(FibonacciLoop.new) @general_term = Fibonacci.new(FibonacciGeneralTerm.new) end def bench_recursive assert_performance_constant do |_n| 1000.times do |i| @recursive.exec(i) end end end def bench_loop assert_performance_constant do |_n| 1000.times.each do |i| @loop.exec(i) end end end def bench_general_term assert_performance_constant do |_n| 1000.times.each do |i| @general_term.exec(i) end end end endベンチマーク を実行します。
$ ruby test/fibonacci_benchmark.rb Run options: --seed 1009 # Running: bench_recursive 0.438420 0.436003 0.437170 0.453267 0.428123 .bench_loop 0.157816 0.160366 0.159504 0.160275 0.162165 .bench_general_term 0.001215 0.001200 0.001255 0.001204 0.001184 . Finished in 3.074021s, 0.9759 runs/s, 0.9759 assertions/s. 3 runs, 3 assertions, 0 failures, 0 errors, 0 skips結果を見たところ、再帰処理アルゴリズムが一番遅く、一般項アルゴリズムが一番早く実行されるようです。
ベンチマーク を実施してアルゴリズムの性能を比較できたのでコミットします。
$ git add . $ git commit -m 'perf: ベンチマークの実施'モジュール分割
アプリケーションのリリース
動作するきれいなコード をリリースするにあたってクラスモジュールごとにファイル分割して エントリーポイント からアプリケーションを実行できるようにしたいと思います。
/ |--lib/ | -- fibonacci.rb |--test/ | -- fibonacci_test.rb -- fibonacci_benchmark.rbまず、
lib
にfibonacci
フォルダを追加します。クラスモジュールはFibonacci
の 名前空間 で管理するようにします。
lib/fibonacci/command.rb
# frozen_string_literal: true module Fibonacci # Fibonacci Calcultor class Command def initialize(algorithm) @algorithm = algorithm end def exec(number) @algorithm.exec(number) end end end
lib/fibonacci/recursive.rb
# frozen_string_literal: true module Fibonacci # Fibonacci Recursive algorithm class Recursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end end
lib/fibonacci/loop.rb
# frozen_string_literal: true module Fibonacci # Fibonacci Loop algorithm class Loop def exec(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end end end
lib/fibonacci/general_term.rb
# frozen_string_literal: true module Fibonacci # Fibonacci General Term algorithm class GeneralTerm def exec(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end end
fibonacci.rb
は分割したクラスモジュールを読み込むエントリーポイントに変更します。
lib/fibonacci.rb
# frozen_string_literal: true require './lib/fibonacci/command' require './lib/fibonacci/recursive' require './lib/fibonacci/loop' require './lib/fibonacci/general_term'名前空間 を変更して呼び出すクラスが変わったのでテストとベンチマークを修正します。
... require './lib/fibonacci' class FibonacciTest < Minitest::Test def setup @recursive = Fibonacci.new(FibonacciRecursive.new) @loop = Fibonacci.new(FibonacciLoop.new) @general_term = Fibonacci.new(FibonacciGeneralTerm.new) end ...まず、テストを修正してテストが通ることを確認します。
... require './lib/fibonacci' class FibonacciTest < Minitest::Test def setup @recursive = Fibonacci::Command.new(Fibonacci::Recursive.new) @loop = Fibonacci::Command.new(Fibonacci::Loop.new) @general_term = Fibonacci::Command.new(Fibonacci::GeneralTerm.new) end ...... require './lib/fibonacci' class FibonacciTestBenchmark < Minitest::Benchmark def setup @recursive = Fibonacci.new(FibonacciRecursive.new) @loop = Fibonacci.new(FibonacciLoop.new) @general_term = Fibonacci.new(FibonacciGeneralTerm.new) end ...続いてベンチマークを修正して実行できることを確認します。
... require './lib/fibonacci' class FibonacciTestBenchmark < Minitest::Benchmark def setup @recursive = Fibonacci::Command.new(Fibonacci::Recursive.new) @loop = Fibonacci::Command.new(Fibonacci::Loop.new) @general_term = Fibonacci::Command.new(Fibonacci::GeneralTerm.new) end ...仕上げはコマンドラインから実行できるようにします。 ルート直下に
main.rb
を追加して以下のコードを追加します。 ここでは ベンチマーク で一番良い結果の出た一般項のアルゴリズムを使うことにします。
main.rb
require './lib/fibonacci' number = ARGV[0].to_i command = Fibonacci::Command.new(Fibonacci::GeneralTerm.new) puts command.exec(number)コマンドラインから引数に番号を指定して実行します。
$ ruby main.rb 0 0 $ ruby main.rb 1 1 $ ruby main.rb 2 1 $ ruby main.rb 3 2 $ ruby main.rb 4 3アプリケーションの完成したのでコミットします。
$ git add . $ git commit -m 'feat: モジュール分割'アプリケーションの構成
/main.rb |--lib/ | -- fibonacci.rb fibonacci/ | -- command.rb -- general_term.rb -- loop.rb -- recursive.rb |--test/ | -- fibonacci_test.rb -- fibonacci_benchmark.rb/main.rb.
require './lib/fibonacci' number = ARGV[0].to_i command = Fibonacci::Command.new(Fibonacci::GeneralTerm.new) puts command.exec(number)/lib/fibonacci.rb.
# frozen_string_literal: true require './lib/fibonacci/command' require './lib/fibonacci/recursive' require './lib/fibonacci/loop' require './lib/fibonacci/general_term'/lib/fibonacci/command.rb.
# frozen_string_literal: true module Fibonacci # Fibonacci Calcultor class Command def initialize(algorithm) @algorithm = algorithm end def exec(number) @algorithm.exec(number) end end end/lib/fibonacci/recursive.rb.
# frozen_string_literal: true module Fibonacci # Fibonacci Recursive algorithm class Recursive def exec(number, memo = {}) return 0 if number.zero? return 1 if number == 1 memo[number] ||= exec(number - 1, memo) + exec(number - 2, memo) end end end/lib/fibonacci/loop.rb.
# frozen_string_literal: true module Fibonacci # Fibonacci Loop algorithm class Loop def exec(number) a = 0 b = 1 c = 0 (0...number).each do |_i| a = b b = c c = a + b end c end end end/lib/fibonacci/general_term.rb.
# frozen_string_literal: true module Fibonacci # Fibonacci General Term algorithm class GeneralTerm def exec(number) a = ((1 + Math.sqrt(5)) / 2)**number b = ((1 - Math.sqrt(5)) / 2)**number ((a - b) / Math.sqrt(5)).round end end end/test/fibonacci_test.rb.
# frozen_string_literal: true require 'minitest/reporters' Minitest::Reporters.use! require 'minitest/autorun' require './lib/fibonacci' class FibonacciTest < Minitest::Test def setup @recursive = Fibonacci::Command.new(Fibonacci::Recursive.new) @loop = Fibonacci::Command.new(Fibonacci::Loop.new) @general_term = Fibonacci::Command.new(Fibonacci::GeneralTerm.new) end def test_fibonacci cases = [[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5]] cases.each do |i| assert_equal i[1], @recursive.exec(i[0]) end end def test_large_number_recursive assert_equal 102_334_155, @recursive.exec(40) end def test_large_number_loop assert_equal 102_334_155, @loop.exec(40) end def test_large_number_general_term assert_equal 102_334_155, @general_term.exec(40) end end/test/fibonacci_benchmark.rb.
# frozen_string_literal: true require 'minitest' require 'minitest/autorun' require 'minitest/benchmark' require './lib/fibonacci' class FibonacciTestBenchmark < Minitest::Benchmark def setup @recursive = Fibonacci::Command.new(Fibonacci::Recursive.new) @loop = Fibonacci::Command.new(Fibonacci::Loop.new) @general_term = Fibonacci::Command.new(Fibonacci::GeneralTerm.new) end def bench_recursive assert_performance_constant do |_n| 1000.times do |i| @recursive.exec(i) end end end def bench_loop assert_performance_constant do |_n| 1000.times.each do |i| @loop.exec(i) end end end def bench_general_term assert_performance_constant do |_n| 1000.times.each do |i| @general_term.exec(i) end end end end参考図書
テスト駆動開発 Kent Beck (著), 和田 卓人 (翻訳): オーム社; 新訳版 (2017/10/14)
新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES) Martin
Fowler (著), 児玉 公信 (翻訳), 友野 晶夫 (翻訳), 平澤 章 (翻訳), その他: オーム社; 新装版
(2014/7/26)リファクタリング(第2版): 既存のコードを安全に改善する (OBJECT TECHNOLOGY SERIES) Martin
Fowler (著), 児玉 公信 (翻訳), 友野 晶夫 (翻訳), 平澤 章 (翻訳), その他: オーム社; 第2版
(2019/12/1)
- 投稿日:2020-02-13T09:50:04+09:00
S3バケットを空にするRubyスクリプト
APIなどでバケットを削除する際、空にしてからでないと削除できません。バケットを空にするプログラムを作るのは、けっこうな手間なのですが、Rubyだと一発でいけます。
clear-bucket.rbrequire 'aws-sdk-s3' bucket = Aws::S3::Bucket.new('<バケット名>') bucket.clear!
- 投稿日:2020-02-13T01:31:09+09:00
敢えてRubyで学ぶ「ゼロから作るDeep Learning」第4章 数値微分/勾配
Rubyだとちょっと数値微分に一工夫必要。
Pythonだと
def numerical_diff(f,x): h = 1e-4 return (f(x+h) - f(x-h)) / (2*h)と引数に関数を渡せてしまうが、Rubyで同じことはできない。
Proc(lambda)オブジェクトを定義し、ブロック化して関数を渡すことで実行可能。数値微分
numerical_diff.rbdef numerical_diff(f,x) h = 1e-4 return (f.call(x+h) - f.call(x-h)) / (2*h) end def function_1(x) 0.01*x**2 + 0.1*x end # 実行 f = lambda {|arg| function_1(arg)} numerical_diff(f,5) # => 0.1999999999990898 numerical_diff(f,10) # => 0.2999999999986347勾配
require 'numo/narray' def numerical_gradient(f, x) h = 1e-4 grad = Numo::DFloat.zeros(x.shape) # 各xで偏微分 x.length.times do |idx| tmp_val = x[idx] # 要素に差分を入れて、中心差分を取る x[idx] = tmp_val + h fxh1 = f.call(x) x[idx] = tmp_val - h fxh2 = f.call(x) grad[idx] = (fxh1 - fxh2) / (2*h) # 元のxに戻す x[idx] = tmp_val end grad end def function_2(x) return x[0]**2 + x[1]**2 end x = Numo::DFloat[3.0, 4.0] f = lambda{|arg| function_2(arg)} numerical_gradient(f, x) # =>Numo::DFloat#shape=[2] [6, 8] x = Numo::DFloat[0.0, 2.0] numerical_gradient(f, x) # => Numo::DFloat#shape=[2] [0, 4] x = Numo::DFloat[3.0, 0.0] numerical_gradient(f, x) # => Numo::DFloat#shape=[2] [6, 0]勾配法
def gradient_descent(f, init_x, lr=0.01, step_num=10) x = init_x step_num.times do |i| grad = numerical_gradient(f, x) x -= lr * grad end return x end init_x = Numo::DFloat[-3.0, 4.0] f = lambda{|arg| function_2(arg)} # 良い例 gradient_descent(f, init_x, lr=0.1, step_num=100) => Numo::DFloat#shape=[2][-6.11111e-10, 8.14814e-10] # 悪い例 : 学習率が大きすぎる例 gradient_descent(f, init_x, lr=10, step_num=100) => Numo::DFloat#shape=[2][-2.58984e+13, -1.29525e+12] # 悪い例 : 学習率が小さすぎる例 gradient_descent(f, init_x, lr=-10, step_num=100) => Numo::DFloat#shape=[2][-2.4204e+12, 3.0874e+12]
- 投稿日:2020-02-13T00:17:00+09:00
postgresで任意のid順にorder by
- 投稿日:2020-02-13T00:08:01+09:00
引数、スコープについて
変数はメソッドの中で定義するとそのメソッド内でしか基本使えません。
これをスコープといいます
ある変数を利用できる範囲のことです。スコープの範囲外の変数を使おうとすると、エラーが起こります。その場合どうするかというと引数を使います。
引数はメソッドの外にある変数(スコープ外の変数)をメソッドの中で使うことができます。postに入っているハッシュのデータを配列オブジェクトの中に入れたい場合
def example post = {} post[:name] = "太郎" post[:age] = "25" post[:gender] = "男性" posts << post end #配列オブジェクト posts = []こちらはエラーになってしまいます。
変数postsはexampleメソッドの外にあるためです。そこで引数を使うと
def example(a_posts) #ハッシュオブジェクト post = {} post[:name] = "太郎" post[:age] = "25" post[:gender] = "男性" a_posts << post return a_posts end #配列オブジェクト posts = [] posts = example(a_posts)これで配列オブジェクトpostsにハッシュオブジェクトpostのデータが追加されるコードが書けました。
- 投稿日:2020-02-13T00:01:44+09:00
LeetCode - 56. Merge Intervals
問題
Ver. 1
- とりあえず
interval
をmerged_intervals
にほりこむmerged_intervals
の最後の要素[a, b]
を取り出して、次のinterval
の[x, y]
と重なっているか判定interval
が重なってなかったら、[a, b]
,[x, y]
をそのままmerged_intervals
にほりこむ- 重なってたら、
[a, b]
,[x, y]
をマージしてほりこむ
intervals
は事前にソートされていないとうまくいかないO(n^2)
を避けるため、ソートして、都度マージする# @param {Integer[][]} intervals # @return {Integer[][]} def merge(intervals) merged_intervals = [] intervals.sort.each do |curr| last = merged_intervals.pop if last.nil? merged_intervals << curr elsif last[1] < curr[0] || curr[1] < last[0] # Last and curret are not overlapped merged_intervals << last << curr else # Last and curret are overlapped merged_intervals << [(last + curr).min, (last + curr).max] end end merged_intervals endスコア
Runtime: 88 ms, faster than 15.18% of Ruby online submissions for Merge Intervals. Memory Usage: 9.8 MB, less than 100.00% of Ruby online submissions for Merge Intervals.いまいち...
Ver. 2
コメントいただいて、再度考えてみたバージョン
# @param {Integer[][]} intervals # @return {Integer[][]} def merge(intervals) merged_intervals = [] intervals.sort.each do |curr| last = merged_intervals.last if last.nil? merged_intervals << curr elsif last[1] < curr[0] # Last and curret are NOT overlapped merged_intervals << curr else # Last and curret are overlapped last[1] = (last + curr).max end end merged_intervals endスコア
Runtime: 60 ms, faster than 44.64% of Ruby online submissions for Merge Intervals. Memory Usage: 9.7 MB, less than 100.00% of Ruby online submissions for Merge Intervals.だいぶよくなった