20200213のRubyに関する記事は15件です。

【備忘録】Ruby : 継承(override,super)

https://qiita.com/noytdm1021/items/9e3a5d67234fd6a904c1 の追記

sample.rb
class 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.
#どちらも出力


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Ruby : 基本的な継承クラスの使い方

sample.rb
class 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メソッドを使用可能
#継承の使い方は複数あるクラスの共通処理をまとめて置いたりする。
# => つまり継承元にメソッドを定義しておくことによって複数のクラスオブジェクトで使うことが
#可能となる。


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Ruby : privateメソッドの使い方

sample.rb
require '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
# をつけても大丈夫


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の基礎って感じがしますね・・・
もっと勉強します!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初学者向け 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で渡されたデータを表示する。
という認識で良いかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自動デプロイ時エラー発生

エラー記述

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ファイルをコメントアウト

これで自動デプロイできました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

middleman 正しいコマンド の打ち方

❌ gem install bundler

⭕️rbenv exec gem install bundler

bash_profileにエイリアス設定したもの。

参考:https://qiita.com/naokazuterada/items/072f28ee15f8fe86bdec

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rbenv install 2.6.5を実行、Installing openssl-1.1.1d...で止まる

5-10分待てば、動き出す。

今回の私の場合は、5分弱で動き出して、結果うまくいった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FluentdをMacでセットアップして、CloudWatchLogsへログ転送するまで

はじめに

実務で、ローカルで稼働しているMac miniが複数台あるので、それをCloudWatchLogsへ集約したいという要望があったので、勉強がてらFluentDを使った時の備忘録的なアレです

色々、試行錯誤しながら試したので、同じくハマっている人の助けになればと思います

失敗パターン

td-agentを使って、cloudwatchLogsに転送させる設定をしようとしたが、ripperが足りない?せいか、以下のエラーが発生して、td-agent内包のRubyを使っているため、これ以上は自力解決が難しいと思い、断念

`require': cannot load such file -- ripper (LoadError)

やったこと

  1. Fluentd公式から、MacOS向けのtd-agent.dmgをDLして、セットアップ
    https://docs.fluentd.org/installation/install-by-dmg

  2. CloudWatchLogsに飛ばすためのPluginは、ここからDL
    https://github.com/fluent-plugins-nursery/fluent-plugin-cloudwatch-logs

README.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    
  1. 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での挙動が違ったりする場合があるので、調査の時には注意が必要かなと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テスト駆動開発から始めるRuby入門 ~アルゴリズムのパフォーマンスチューニングとベンチマークを実践する~

外伝

Open in Gitpod

初めに

この記事は テスト駆動開発から始める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-reporters

gemインストールが完了したらコマンドラインに 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 worldhello 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 skips

2までは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 メソッドを作成して インスタンス変数 @fibFibonacci クラスの参照を代入します。

...
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パターン を適用して各アルゴリズムの実行を 委譲 します。

Strategy パターン

diag-3f9e53f62bd2f1b3bfe0f476521170ca.png

...
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

まず、 libfibonacci フォルダを追加します。クラスモジュールは 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: モジュール分割'

アプリケーションの構成

diag-7e042e6c62bb11af0c26498efef6d39b.png

/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)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3バケットを空にするRubyスクリプト

APIなどでバケットを削除する際、空にしてからでないと削除できません。バケットを空にするプログラムを作るのは、けっこうな手間なのですが、Rubyだと一発でいけます。

clear-bucket.rb
require 'aws-sdk-s3'
bucket = Aws::S3::Bucket.new('<バケット名>')
bucket.clear!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

敢えて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.rb
def 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]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

postgresで任意のid順にorder by

任意のid順にorder by

例えば手元にこのようなidsがあり、このidsの順番でuserを取得したいとき

ids = [2,3,1,10,9]

これでうまくいくかも

users = User.order("COALESCE(array_position(ARRAY[#{ids.join(",")}], id), 0) asc")

たまにこういうこと並び順が必要になること
ありますよね

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

引数、スコープについて

変数はメソッドの中で定義するとそのメソッド内でしか基本使えません。
これをスコープといいます
ある変数を利用できる範囲のことです。スコープの範囲外の変数を使おうとすると、エラーが起こります。

その場合どうするかというと引数を使います。
引数はメソッドの外にある変数(スコープ外の変数)をメソッドの中で使うことができます。

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のデータが追加されるコードが書けました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode - 56. Merge Intervals

問題

56. Merge Intervals

Ver. 1

  1. とりあえず intervalmerged_intervals にほりこむ
  2. merged_intervals の最後の要素 [a, b] を取り出して、次の interval[x, y] と重なっているか判定
  3. interval が重なってなかったら、[a, b], [x, y] をそのままmerged_intervals にほりこむ
  4. 重なってたら、[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.

だいぶよくなった

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む