AtCoder Grand Contest 038「A - 01 Matrix」(Ruby)

取っ掛かりさえ見つけられれば、非常に簡単な問題でした

問題

atcoder.jp

実行時間制限: 2 sec / メモリ制限: 1024 MB

配点 : 300 点

問題文 H 行 W 列からなるマス目があります。 すぬけくんは、各マスに 0 または 1 を書き込みたいです。 その際、以下の条件を全て満たす必要があります。

どの行についても、その行に含まれる 0 の個数と、その行に含まれる 1 の個数のうち、小さい方が A である。 (ここで、 0 , 1 の個数が同じ場合、小さい方はどちらとしてもよい)。 どの列についても、その列に含まれる 0 の個数と、その列に含まれる 1 の個数のうち、小さい方が B である。 これらの条件を満たすように各マスに 0 , 1 を書き込めるか判定し、 可能な場合は条件を満たす書き込み方を 1 つ示してください。

制約 1 ≤ H , W ≤ 1000 0 ≤ A 2 × A ≤ W 0 ≤ B 2 × B ≤ H 入力される値はすべて整数である。

解き方

様々な書き込み方で条件を満たせる場合がありますが、全てにおいて条件を満たす法則を考える必要があります。

今回のケースだと、以下の図の様な書き込み方であれば、どの様な数値が設定されても条件を満たすことが出来ます。

f:id:ryoutaku_jo:20190922004025p:plain

条件さえ分かれば、あとは一行ごと上から順番に出力していくだけです。

コード

h,w,a,b = gets.split.map(&:to_i)
 
(h-b).times { puts ([0] * (w-a) + [1] * a).join }
b.times { puts ([1] * (w-a) + [0] * a).join }

f:id:ryoutaku_jo:20190922001344p:plain

DHH流ルーティングについて(Rails)

【結論】

・DHH(David Heinemeier Hansson)は、Ruby on Railsの生みの親

・DHH流ルーティングとは、DHHが推奨しているRailsのルーティング手法

・具体的には、コントローラはデフォルトのCRUDアクション(index、show、new、edit、create、update、destroy)のみを使うという内容

【目次】

【本題】

DHHについて

DHHとは、Ruby on Railsの生みの親であるDavid Heinemeier Hansson(デイヴィッド・ハイネマイヤー・ハンソン)の略称です。

DHHは、2004年にRuby on Railsオープンソースとして公開しています。

DHH流ルーティングとは

DHH流ルーティングとは、DHHが推奨しているRailsのルーティング手法です。

考え方としては、RESTの原則に従って、新しいコントローラーを作るタイミングを決めるというものです。

もう少し掘り下げると、コントローラはデフォルトのCRUDアクション(index、show、new、edit、create、update、destroy)のみを使って、それ以外のアクションが必要になった場合は、別のコントローラーに分けるという内容です。

例えば、PostsControllerというユーザーから投稿された記事を扱うコントローラーがあったとします。

そこに、全ての記事を一覧表示するindexアクションと、お気に入りにした記事のみ一覧表示するfavoritesアクションが定義されています。

class PostsController < ApplicationController
  def index
  end
 
  def favorites
  end
end

DHH流ルーティングの場合、これら2つのアクションは、以下のように別のコントローラーに分けます。

class PostsController < ApplicationController
  def index
  end
end

class Posts::FavoritesController < ApplicationController
  def index
  end
end

メリット

コントローラーの肥大化を防げる

もし際限なくアクションを追加していった場合、コントーラーの行数が増えていき、可読性が落ちてしまいます。

コントローラーを分けることで、この問題を回避することができます。

コントローラーの責務が明確になる

コントローラーに、特定のリソースに対する操作を全て記述した場合、もしコードの改修が必要になった際、その中から目的のアクションを探すのに時間が掛かってしまいます。

全てのコントローラーがデフォルトのCRUDアクションしか使用していないと分かっていれば、どのコントローラーが「どのリソース」の「どういった操作」を担当しているのか明確になります。

これにより、コントローラーの中から、目的のアクションを探し回る手間が省けます。

一貫性のあるルーティングが実現できる

チームで開発を行っている場合、コントローラーを分けるタイミングが開発者ごとに異なっていると、ルーティングに一貫性がなく、可読性が落ちてしまいます。

デフォルトのCRUDアクションしか使わないというのは非常にシンプルなルールなので、誰もが遵守しやすく、ルーティングの一貫性を保つことが出来ます。

参考情報

DHH流のルーティングで得られるメリットと、取り入れる上でのポイント - KitchHike Tech Blog

DHHはどのようにRailsのコントローラを書くのか | POSTD

DHH流ルーティングの導入しようとした際にハマったこと - Qiita

【AtCoder:19回目】AtCoder Beginner Contest 141の振り返り(Ruby)

【目次】

【本題】

振り返り

今回は 9/15(日)に開催されたAtCoder Beginner Contest 141の振り返りを行います。

AtCoder Beginner Contest 141 - AtCoder

今回は3問回答出来ました

レーティング変化

A - Weather Prediction

問題文 高橋君の住む町の天気は、一日ごとに晴れ(Sunny)、くもり(Cloudy)、雨(Rainy)、晴れ、くもり、雨、… と周期的に変化します。

今日の天気を表す文字列 S が与えられるので、明日の天気を予測してください。

制約 S は Sunny, Cloudy, Rainy のいずれかである

私の提出コードは以下の通りです。

s = gets.chomp
 
case s
when 'Sunny'
  puts 'Cloudy'
when 'Cloudy'
  puts 'Rainy'
when 'Rainy'
  puts 'Sunny'
end

以下の様に3通りの出力を配列に格納して、入力によって出し分ける方法だと、よりコードが短縮出来ます。

w=%w(Sunny Cloudy Rainy)
puts w[(w.index(gets.chomp)+1)%3]

B - Tap Dance

問題文 高橋君はタップダンスをすることにしました。タップダンスの動きは文字列 S で表され、 S の各文字は L, R, U, D のいずれかです。各文字は足を置く位置を表しており、 1 文字目から順番に踏んでいきます。

S が以下の 2 条件を満たすとき、またその時に限り、 S を「踏みやすい」文字列といいます。

奇数文字目がすべて R, U, D のいずれか。 偶数文字目がすべて L, U, D のいずれか。 S が「踏みやすい」文字列なら Yes を、そうでなければ No を出力してください。

制約 S は長さ 1 以上 100 以下の文字列 S の各文字は L, R, U, D のいずれか

条件では4種類の文字のうち3つのいずれかが含まれていると記述されています。

それをそのままコードに落とし込むと記述量が多くなるので、4種類のうち1つを含まないという逆のパターンで考えます。

私の提出コードは以下の通りです。

s = gets.chomp
 
odd = s.chars.select.with_index(1) { |_, i| i.odd? }
even = s.chars.select.with_index(1) { |_, i| i.even? }
 
if odd.include?('L') || even.include?('R')
  puts 'No'
else
  puts 'Yes'
end

C - Attack Survival

問題文 高橋君は早押しクイズの大会を開くことにしました。スコアボードの作成を任されたキザハシ君は、次のルールを持つラウンドのポイントを管理するプログラムを書くのに苦戦しています。

このラウンドの参加者は N 人であり、 1 から N までの番号がついています。ラウンド開始時点では全員が K ポイントを持っています。

誰かが問題に正解すると、その人以外の N − 1 人のポイントが 1 減ります。これ以外によるポイントの変動はありません。

ラウンド終了時にポイントが 0 以下の参加者は敗退し、残りの参加者が勝ち抜けます。

このラウンドでは Q 回の正解が出て、 i 番目に正解したのは参加者 A i でした。 キザハシ君の代わりに、 N 人の参加者のそれぞれが勝ち抜けたか敗退したかを求めるプログラムを作成してください。

制約 入力はすべて整数 2 ≤ N ≤ 10 5 1 ≤ K ≤ 10 9 1 ≤ Q ≤ 10 5 1 ≤ A i ≤ N

( 1 ≤ i ≤ Q )

毎回の個々の点数を更新して行くと処理が非常に重くなります。

そこで、まず各参加者の正答数の合計を集計します。

その正答数と、ラウンド数Qから持ち点Kを引いた値を比較します。

これで勝ち抜けたか敗退したかを判定することが出来ます。

以下が私の提出コードです。

n,k,q = gets.split(' ').map(&:to_i)
ary = []
q.times { ary << gets.to_i }
 
ans = [0] * n
ary.each do |a|
  ans[a-1] += 1
end
 
ans.each do |a|
  if a > q - k 
    puts 'Yes'
  else
    puts 'No'
  end
end

D - Powerful Discount Tickets

問題文 高橋くんは N 個の品物を 1 個ずつ順番に買う予定です。

i 番目に買う品物の値段は A i 円です。

高橋くんは M 枚の割引券を持っています。

品物を買う際に割引券を好きな枚数使うことができます。

X 円の品物を買う際に Y 枚の割引券を使った場合、その品物を X 2 Y 円(小数点以下切り捨て)で買うことができます。

最小で何円あれば全ての品物を買うことができるでしょうか。

制約 入力は全て整数である。 1 ≤ N , M ≤ 10 5 1 ≤ A i ≤ 10 9

最も金額の大きい商品に割引券を使用するのが、最も割引額が大きくなります。

難点は、割引を繰り返す中で、金額が最大の商品が変動することです。

その為、1回割引をする度に、最大値を探し直す必要があります。

その様に考えて、以下のコードを提出しましたが、TLE(実行時間超過)になってしまいました。

n,m = gets.split.map(&:to_i)
nums = gets.split.map(&:to_i)
 
m.times do |i|
  nums.sort!.reverse!
  nums[0] /= 2
end
 
puts nums.inject(:+)

最大値は別の配列に格納して、そちらで比較を行う方法で通りました。

n,m = gets.split.map(&:to_i)
nums = gets.split.map(&:to_i).sort.reverse

s = [nums.shift / 2] 
(m-1).times do |i|
  if nums == []
    nums = s
    s = [nums.shift / 2]
  elsif nums[0] > s[0]
    s << nums.shift / 2
  else
    s << s.shift / 2
  end
end

nums += s
puts nums.inject(:+)

環境構築不要かつ無料で機械学習が試せる「Colaboratory」について

【結論】

・Colaboratoryとは、Googleが提供しているクラウド上で機械学習のプログラムを実行できるサービス

環境構築不要かつ無料で利用できるので、気軽にプログラムを試すことができる

・しかし一定時間が経過すると、仮想マシンが停止してデータが初期化されるなどの制約がある

【目次】

【本題】

Colaboratoryについて

Colaboratoryとは、クラウド上で機械学習のプログラムを実行できるサービスです。Googleが無料で提供しています。

Pythonも含め、機械学習の実行環境を0から整えようとすると、ある程度の労力は必要になります。

その点、Colaboratoryはクラウドに実行環境が用意されているので、環境構築をスキップして、気軽に機械学習の開発を始めることが出来ます。

また、機械学習のプログラム実行は、一定にマシーンスペックが要求される場合がありますが、ColaboratoryであればGoogleのサーバー上で処理が行われるので、PCのスペックが低かったとしても、快適に開発を進めることが出来ます

Colaboratoryは、デスクトップに限らず、タブレット端末からでも操作可能なので、幅広い場面で利用することが可能です。

使用方法

Colaboratoryには、以下からアクセスすることが出来ます。

colab.research.google.com

リンクを開くと、以下の様なノートブックの選択・作成画面が表示されます。

PYTHON3の新しいノートブック」を選択すると、新しいノートブックが作成されます。

Pythonの実行環境が整っているので、以下の様にPythonのコードでプログラムを実行することが出来ます。

GitHubに上がっていた機械学習のサンプルコードを試しても、正常に動作しました。

この実行結果については、GoogleDriveに保存することが可能です。

制限について

非常に有用なサービスですが、無料ゆえに注意すべきポイントもあります。

それは、一定時間が経過すると、仮想マシンが停止するということです。

停止するパターンは2通りあります。

1つ目は、ブラウザを閉じだり、PCがスリープ状態に入ったことで、ノートブックのセッションが切れた場合です。

直ぐには停止しませんが、90分経過すると仮想マシンが停止します

2つ目は、仮想マシンの起動から12時間が経過した場合です。

この場合、ブラウザを開き続けた状態にしていたとしても関係なく停止してしまいます。

仮想マシンが停止すると、ダウンロードしたデータやインストールしたライブラリなどが初期化されます。

なので、この点に注意して利用する必要があります。

参考情報

Google Colaboratory(Colab)ならPythonですぐに学べる - ガンマソフト

【秒速で無料GPUを使う】深層学習実践Tips on Colaboratory - Qiita

Callbackメソッドの「only」「except」の使い分け(Rails)

【結論】

・Controllerのコールバックメソッドは、onlyexceptで実行するアクションを指定できるが、適切に使い分けないと、バグ発生の要因となり得る

・基本的にonlyを使用する。指定するアクションが増えて、追加するアクションが無くなったらexceptに置き換える。

・権限の制御など、アクションの実行に制限を設けるコールバックであれば、始めからexceptを利用する。

【目次】

【本題】

Callbackメソッドのオプションの使い分け

before_actionafter_actionといったControllerのCallbackメソッドは、オプションでコールバックを実行させるアクションを指定することができます。

onlyでは、指定したアクションだけ、コールバックが実行されます。

exceptでは、指定したアクション以外で、コールバックが実行されます。

class PostsController < ApplicationController
  before_action :authenticate_user!, :except=>[:show]
  before_action :set_post, only: [:show, :edit]

  def show
    @post_comments = PostComment.where(post_id: params[:id])
  end

  def edit
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end
end

これにより、共通の処理を一つの記述にまとめることが出来て、コードの見通しが良くなります。

暗黙的に実行されていることによる弊害

しかしコールバックの処理は、アクション内では明示されていない為、存在を見落としてしまう可能性もあります。

そうなると、アクションを指定し忘れてしまい、それによって意図せぬ挙動が発生してしまうリスクがあります。

例えば、本来は管理者しかアクセスできないページ(アクション)に、一般のユーザーもアクセスできてしまうという事象です。

なので、アクションの指定方法も、それらのリスクを最小限に留めるように設定を行う必要があります。

基本的にonlyを使用する

exceptだと明示的に指定したアクション以外にコールバックが適用されます。

なので、アクションを追加した際にコールバックを見落としてしまうと、本来適用したくなかったコールバックが適用されてしまうことになってしまいます。

onlyを使用すれば、実行するアクションを明示的に指定できるので、意図せぬコールバックが適用される事態は防げます。

しかし、適用するアクションが増えるほど記述量が多くなり、見通しが悪くなることで、どのアクションにコールバックが適用されているのか見辛くなります。

そこで、exceptで書いた方が記述量が減るのであれば、exceptに置き換えましょう。

但し、今後もアクションを追加する予定があるのであれば、exceptに置き換えることで先ほどの問題が発生する可能性もあるので、アクションの追加がひと段落してからの方が良いでしょう。

制限を設けるコールバックはexceptを使用する

基本的にはonlyを使用しますが、exceptを使用した方が良いと思われる場合もあります。

それはコールバックによって、アクションの実行に制限を設ける場合です。

例えば「管理者のみアクセスできるようにしたい」といった権限の制御などです(そもそもコールバックで処理すべきでは無いかもしれないですが・・・)

これをonlyで設定してしまうと、もしコールバックの設定を見逃した場合、一般のユーザーでも管理者専用のアクションが実行できてしまいます。

exceptを使用すれば、追加したアクションに直ぐコールバックが適用されるので、こういった事態を防ぐことが可能です。

参考情報

Action Controller の概要 - Railsガイド

AbstractController::Callbacks::ClassMethods

「let・let!・before」の違いと実行順序(RSpec)

【結論】

let/let!インスタンス変数を定義する際、beforeより分かりやすく記述できる

beforeインスタンス変数の定義以外(メソッドの実行など)でも利用することができる

letは変数が初めて使用された時に評価(遅延評価)され、let!beforeはブロック宣言時に評価される。

【目次】

【本題】

let・let!・beforeの役割について

let・let!・beforeは、いずれもRSpecにおいて、各exampleで共通の処理を定義することができます。

これによりそれぞれのテストに同じ記述をする手間が省け、DRYな状態をキープできます。

なお、これらは意識していないと、どれも同じ動きをしている様に見えますが、実際には挙動や使用する場面が異なります。

今回はそれをまとめます。

「let・let!」はインスタンス変数の定義に用いる

各exampleで共通利用するインスタンス変数を定義する際、beforeを利用すると、以下の様なコードになります。

  before do
    @user = create(:user, name: 'alice')
    @post = create(:post, title: 'test')
  end

これをletで書き換えると以下の様になります。

let(:user)   { create(:user, name: 'alice') }
let(:post)   { create(:post, title: 'test') }

letの方がやや見通しが良くなったと思います。

この事からインスタンス変数の定義を共通化する際には、let・let!を使用するのが一般的の様です。

「before」はメソッドを実行する際に用いる

ではbeforeはどの様な場面で用いるかというと、インスタンス変数の定義以外で利用します。

例えば、以下の様にサインインの処理を記述すれば、各exampleが実行される前に共通して処理が走ります。

before { sign_in(current_user) }

let・let!では、この様な使い方は出来ないので、beforeが活躍します。

「let」と「let!」の実行順の違い

なおletlet!については、それぞれの実行順に注意する必要があります。

letは変数が初めて使用された時に評価(遅延評価)されます。

let!はブロック宣言時に評価される。

例えば、以下のコードは実行順の関係で、正常に完了しません。

let(:post)   { create(:post, title: 'test') }
it 'ポストが取得できること' do
  expect(Post.first).to eq post
end

Post.firstが呼び出された時点では、postが生成されていない(呼び出されていない為)ことが原因です。

これを回避するにはlet!を用います。

let!(:post)   { create(:post, title: 'test') }
it 'ポストが取得できること' do
  expect(Post.first).to eq post
end

これにより、Post.firstが呼び出された時点では、postが生成されるので、正常にテストが通ります。

参考情報

RSpecのletを使うのはどんなときか?(翻訳) - Qiita

RSpec の letとlet!とbeforeの挙動と実行される順番 - Qiita

Strategyパターンについて(デザインパターン)

【結論】

・Strategyパターンとは、デザインパターンの一つで、状況に応じてアルゴリズムを切り替える処理を記述する時に有効な実装手法

・ConcreteStrategy役に個々の具体的なアルゴリズムが記述されており、どれを使用するかはStrategy役が規定する

アルゴリズムを容易に切り替えられるようになる為、新しいアルゴリズムの拡張や、既存のアルゴリズムの改修がしやすくなる

【目次】

【本題】

Strategyパターンについて

Strategyパターンは、デザインパターンの一種です。

Strategyは「戦略」という意味を持ちますが、プログラミングで言えばアルゴリズムがそれに相当します。

Strategyパターンにおいては、アルゴリズム(戦略)を状況に応じて切り替える実装方法になります。

構成

Strategyパターンは、Strategy(抽象戦略)とConcreteStrategy(具体戦略)とContext(文脈)の3つで構成されています。

ConcreteStrategy(具体戦略)に、具体的なアルゴリズムがプログラミングされています。

Strategy(抽象戦略)で、ConcreteStrategy(具体戦略)のどれを利用するのかを定めます。

Context(文脈)が、実際にStrategy(抽象戦略)を利用して、ConcreteStrategy(具体戦略)のアルゴリズムを実行します。

メリット

IF文などでメソッド内で条件分岐させる方法を採ると、アルゴリズムが増えるほどコード量が多くなり、可読性が落ちてしまいます。

また、アルゴリズムを追加・改良するにしても、他のアルゴリズムに影響を及ぼさない様に細心の注意を払う必要があります。

Strategyパターンを利用すれば、アルゴリズムを他の部分と分離することが出来ます。

これにより、簡単にアルゴリズムの追加・改良を行うことが可能になります。

サンプルコード

以下は、映画の各タイトルに3つの評価点が与えられており、それらをランキング形式で出力するコードです。

ランキングは「合計点」「最高点」「平均点」の3つのアルゴリズムから切り替えて出力できるようになっています。

コード

class Result
  attr_reader :scores
  attr_accessor :sort_type

  def initialize(scores, sort_type)
    @scores = scores
    @sort_type = sort_type
  end

  def output_ranking
    @sort_type.output_ranking(self)
  end
end

class TotalScoreSort
  def output_ranking(result)
    output = {}
    result.scores.each do |s|
      output[s[:title]] = [s[:judge1], s[:judge2], s[:judge3]].sum
    end
    puts '++++++ランキング(合計スコア)++++++'
    Hash[output.sort_by{ |_, v| -v }].each.with_index(1) do |o, i|
      puts "#{i}位:#{o[0]}(スコア:#{o[1]}"
    end
  end
end

class HighestScoreSort
  def output_ranking(result)
    output = {}
    result.scores.each do |s|
      output[s[:title]] = [s[:judge1], s[:judge2], s[:judge3]].max
    end
    puts '++++++ランキング(最高スコア)++++++'
    Hash[output.sort_by{ |_, v| -v }].each.with_index(1) do |o, i|
      puts "#{i}位:#{o[0]}(スコア:#{o[1]}"
    end
  end
end

class AverageScoreSort
  def output_ranking(result)
    output = {}
    result.scores.each do |s|
      output[s[:title]] = ([s[:judge1], s[:judge2], s[:judge3]].sum / 3.0).round(1)
    end
    puts '++++++ランキング(平均スコア)++++++'
    Hash[output.sort_by{ |_, v| -v }].each.with_index(1) do |o, i|
      puts "#{i}位:#{o[0]}(スコア:#{o[1]}"
    end
  end
end

入力

scores  = [
  {title: 'ダークナイト', judge1: 8, judge2: 6, judge3: 7},
  {title: 'トイストーリー4', judge1: 1, judge2: 3, judge3: 0},
  {title: '容疑者Xの献身', judge1: 5, judge2: 4, judge3: 4},
  {title: 'コマンドー', judge1: 2, judge2: 9, judge3: 1},
  {title: 'インセプション', judge1: 7, judge2: 5, judge3: 6},
  {title: 'LIFE!', judge1: 5, judge2: 6, judge3: 3}
]

result = Result.new(scores, TotalScoreSort.new)
result.output_ranking

result = Result.new(scores, HighestScoreSort.new)
result.output_ranking

result = Result.new(scores, AverageScoreSort.new)
result.output_ranking

出力

++++++ランキング(合計スコア)++++++
1位:ダークナイト(スコア:21)
2位:インセプション(スコア:18)
3位:LIFE!(スコア:14)
4位:容疑者Xの献身(スコア:13)
5位:コマンドー(スコア:12)
6位:トイストーリー4(スコア:4)
++++++ランキング(最高スコア)++++++
1位:コマンドー(スコア:9)
2位:ダークナイト(スコア:8)
3位:インセプション(スコア:7)
4位:LIFE!(スコア:6)
5位:容疑者Xの献身(スコア:5)
6位:トイストーリー4(スコア:3)
++++++ランキング(平均スコア)++++++
1位:ダークナイト(スコア:7.0)
2位:インセプション(スコア:6.0)
3位:LIFE!(スコア:4.7)
4位:容疑者Xの献身(スコア:4.3)
5位:コマンドー(スコア:4.0)
6位:トイストーリー4(スコア:1.3)

参考情報

ストラテジ(Strategy) | Ruby デザインパターン | 酒と涙とRubyとRailsと

Rubyによるデザインパターン【Strategy】-取り替え可能パーツ群を戦略的に利用せよ- - Qiita

10. Strategy パターン | TECHSCORE(テックスコア)

「describe・context・it(example)」の使い分け(RSpec)

【結論】

・describeには、テストの対象(グループの範囲)を記述する

・contextには、テストの条件(状態)を記述する

・it(example)には、テストの内容(処理と期待)を記述する

【目次】

【本題】

「describe・context・it(example)」の使い分け

RSpecでは、テストコードの可読性を高める為、コメントアウトの他に文字でテスト内容の説明を記述することが出来ます。

それにはテスト対象をグループ化するdescribecontextと、単一のテスト内容を表すit(example)があります。

今回は、それらの一般的な使い分けの方法について説明します。

describeについて

describeは、複数のテスト(it(example))をグループにまとめることが出来ます。

describeは、クラスやメソッドといった単位で、テストをグループ分けする際に利用されるが一般的です。

つまり、テスト対象の範囲を記述することになります。

describe 'POST #follow' do

contextについて

contextは、describeと同様に、複数のテストをグループにまとめることが出来ます。

contextでは、関連データの有無や時間経過の差異などの単位で、テストのグループ分けする際に利用されるが一般的です。

つまり、テストの条件(状態)を記述することになります。

context 'with unlocked account' do

なおcontextdescribeに機能的な違いは無い為、役割を入れ替えて使用することも可能です。

しかし、一般的な書き方と離れる為、可読性が落ちるリスクがあります。

it(example)

it(example)には、単一のテスト内容を記述します。

テストには実際に行う処理内容と、期待する結果をセットで記述して、それが一致するかでテストを実行します。

it 'returns http success' do

なお、これらの説明用の文字列が無くても、テストは正常に実行されます。

しかし、可読性を高める為には不可欠な要素です。

参考コード

以下はMastodonというサービスのテストコードです。

GitHub - mastodon/mastodon: Your self-hosted, globally interconnected microblogging community

describe 'POST #follow' do
  let(:scopes) { 'write:follows' }
  let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', locked: locked)).account }

  before do
    post :follow, params: { id: other_account.id }
  end

  context 'with unlocked account' do
    let(:locked) { false }

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end

このようにdescribe・context・it(example)を使い分けることで、コードの可読性が高まります。

参考情報

RSpecの(describe/context/example/it)の使い分け - Qiita

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita

【AtCoder:18回目】AtCoder Beginner Contest 140の振り返り(Ruby)

【目次】

【本題】

振り返り

今回は 9/7(土)に開催されたAtCoder Beginner Contest 140の振り返りを行います。

AtCoder Beginner Contest 140 - AtCoder

今回は3問回答出来ました

レーティング微増

A - Password

A - Password

3桁のパスワードを1〜 Nの数字で構成した場合、何通り存在するかですが、Nの3乗で求めることができます。

以下が提出したコードです。

n = gets.to_i
puts n ** 3

B - Buffet

B - Buffet

番号が+1の料理を食べると満足度が別途加算されるので、その条件も含めて繰り返し処理を行います。

以下が提出したコードです。

n = gets.to_i
foods = []
3.times { foods << gets.split(' ').map(&:to_i) }
 
count = 0
n.times do |i|
  num = foods[0][i]
  count += foods[1][num-1]
  if  num == foods[0][i-1] + 1 && i > 0
    count += foods[2][num-2]
  end
end
 
puts count

C - Maximal Value

C - Maximal Value

Bi ≥ max(Ai, Ai + 1)ということは、Ai = min(Bi - 1, Bi)ということになります。

制約的に全探索でも実装可能です。

以下が提出したコードです。

n = gets.to_i
b = gets.split(' ').map(&:to_i)
 
a = [b.first, b.last]
1.upto(n-2) do |i|
  a << [b[i-1], b[i]].min
end
 
p a.inject(:+)

D - Face Produces Unhappiness

D - Face Produces Unhappiness

まずは、現状で幸福な人の数をカウントします。

次に残りの人の中から、幸福になれる人の最大値を求めます。

K回操作するパターンと、K回より少ない操作で済むパターンの二つで条件を分ける必要があります。

K回より少ない操作で済むパターンでは、幸福になれる人の最大値を求めます。

前提として、全員が同じ方向を向いていたとしても、一人は幸福にはなれません。

その為、幸福になれる余地がある人はn - 1 - countで求められます。これが変化する最大値になります。

K回操作するパターンでは、1回あたりの操作で幸福になれる人の最大値から求めます。

前提として、どのような範囲で反転させたとしても、反転させる範囲の両端にいる人しか幸福の状態は変わりません

その為、1回の反転で幸福になれる人の最大値は2になり、K回だと2 * kとなります。

以上をもとに、実装したコードは以下になります。

n,k = gets.split.map(&:to_i)
s = gets.chomp

count = 0
(n-1).times do |i|
  count += 1 if s[i] == s[i+1]
end

if (n - 1 - count) / 2 >= k
  count += 2 * k
else
  count += n - 1 - count
end

puts count

Rubyの「nil?・empty?」とRailsの「blank?・present?」について

【結論】

nil?メソッドは、レシーバーがnilのときtrueを返し、nilではない場合falseを返す

empty?メソッドは、オブジェクトが空(空文字列("")・空配列([])・空ハッシュ({})のときtrueを返し、1つ以上の要素があればfalseを返す。文字列・配列・ハッシュ以外に使うとエラーになる。

blank?メソッドは、オブジェクトがnil, '', ' ', [], {}, falseのときtrueを返し、1つ以上の要素があればfalseを返す

present?メソッドは、オブジェクトがnil, '', ' ', [], {}, false」以外のときにtrueを返し、それ以外はfalseを返す(blank?の逆)

【目次】

【本題】

オブジェクトの有無を判定するメソッド

Railsでオブジェクトの有無を判定する場合、細かい使い分けが出来るようにいくつかメソッドが用意されています。

それは、Rubyに組み込まれている「nil?・empty?」メソッドと、RailsActiveSupportによって提供されている「blank?・present?」です。

今回は、それらの仕様をまとめます。

nil?メソッド

レシーバーがnilのときtrueを返し、nilではない場合falseを返すメソッドです。

a = nil
a.nil?
# => true

a = 0
a.nil?
# => false

a = ''
a.nil?
# => false

a = []
a.nil?
# => false

RubyのObjectClassのメソッドなので、全てのオブジェクトで使用可能です。

empty?メソッド

オブジェクトが空(空文字列("")・空配列([])・空ハッシュ({})のときtrueを返し、1つ以上の要素があればfalseを返す。

なお、空白スペース(ホワイトスペース)だけの文字は、要素が入っていると見なされて、falseを返します。

a = ''
a.empty?
# => true

a = [ ]
a.empty?
# => true

a = {}
a.empty?
# => true

a = ' '
a.empty?
# => false

a = 'aaa'
a.empty?
# => false

a = [1, 2, 3]
a.empty?
# => false

a = { key: 'value' }
a.empty?
# => false

なお、nilに対してempty?メソッドを利用すると、エラーが発生します。

a = nil
a.empty?
# => NoMethodError (undefined method `empty?' for nil:NilClass)

これは、empty?メソッドがString・Array・HashClassで定義されているメソッドで、NilClassでは定義されていないためです。

なので、使用できるのは文字列と配列とハッシュのみです。

blank?メソッド

オブジェクトがnil, '', ' ', [], {}, falseのときtrueを返し、1つ以上の要素があればfalseを返すメソッドです。

ObjectClassの拡張なので、nilやfalseも含めた全てのオブジェクトに対して使用することが可能です。

なお、empty?と異なり空白スペース(ホワイトスペース)だけの文字も、trueを返します。

a = nil
a.blank?
# => true

a = false
a.blank?
# => true

a = ''
a.blank?
# => true

a = ' '
a.blank?
# => true

a = []
a.blank?
# => true

a = {}
a.blank?
# => true

a = 'aaa'
a.blank?
# => false

a = [1, 2, 3]
a.blank?
# => false

a = { key: 'value' }
a.blank?
# => false

present?メソッド

オブジェクトがnil, '', ' ', [], {}, false」以外のときtrueを返し、それ以外でfalseを返すメソッドです。

blank?メソッドと全く逆の働きをするメソッドです。

これもObjectClassの拡張なので、nilやfalseも含めた全てのオブジェクトに対して使用することが可能です。

a = nil
a.present?
# => false

a = false
a.present?
# => false

a = ''
a.present?
# => false

a = ' '
a.present?
# => false

a = []
a.present?
# => false

a = {}
a.present?
# => false

a = 'aaa'
a.present?
# => true

a = [1, 2, 3]
a.present?
# => true

a = { key: 'value' }
a.present?
# => true

なおpresenceメソッドという似た働きを持つメソッドが存在します。

これは条件に一致した場合、trueではなく、レシーバーを返します。

状況によっては、こちらの方が可読性が良くなる場合があります。

ryoutaku-jo.hatenablog.com

参考情報

https://ref.xaio.jp/ruby/classes/object/nil

https://ref.xaio.jp/wikis/2/entries/search?q=empty

Object