paiza times

paizaがお届けする、テック・キャリア・マネジメント領域における「今必要な情報」を届けるWebメディア

logo

paizaがお届けする、テック・キャリア・マネジメント領域の「今必要な情報」を届けるWebメディア

エッ、今さら!?練習問題と具体的コード例によるTDD超入門。

f:id:paiza:20161213120453j:plain
Photo by Menlo Innovations

f:id:paiza:20140624194011j:plainこんにちは、今回はpaiza開発担当の佐藤がお送りします。

先月からpaizaではLeaning機能が公開されておりますが、皆さん挑戦してみて頂けたでしょうか?解くと★マークが溜まっていく、というちょっとした遊び心もあります。このゲームっぽい流れは今後もうちょっと強化されるかもしれませんので、楽しみにしていて下さい。

さて今回のブログではpaizaの練習問題(paiza Learning)を題材に、いまさらでは有りますがRSpecを使いテスト駆動*1で解いてみる、というちょっと変わったことをやってみたいと思います。※最後にJUnitのコードも有ります。

すでにTDDで開発している場合は良いのですが、これから新たにテスト駆動をやってみようという場合に、いきなり業務の中で試してみる訳にも行かず、かといって何から取り組もうかと悩んでしまったりして最初の取っ付きが中々難しいものです。paiza Learningはそういったときにもちょうど良い練習材料になるのではないかともいます。

※paizaコーディングスキルチェックの方は問題文は非公開ですが、paiza Learningの方は問題文が一般公開されており、何回も提出できます。

■RSpecでpaiza Learningの問題を解いてみる


実際にテスト駆動を体験してみるとわかりますが、テストを記述するためには、これから扱う問題を理解し、どういった機能を組み合わせて実装するかをきちんと考える必要があります

Bランク相当の問題の解法をすぐに思いつかないという方は、一度扱う問題を小さく分割し、1つずつ解決していく、というフローを辿ることで効果的に学習できるのではないか、と考え、今回の記事を書くことにしました。


実際に問題を解く前に、自分の環境でも実行してみたい、という方のために今回使用する RSpec のインストール方法についても簡単に触れておきます。

RSpec のインストールは RubyGems から行います。以下のコマンドで最新の RSpec がインストールできます。

$ gem install rspec

以下のコマンドが動作すれば、インストールは成功です。

$ rspec -v
3.0.2   # インストールされたRSpecのバージョンが表示されます
◆RSpec のインストールと実行方法の解説はこちらの記事がオススメです。◆

スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)


また、今回は unagi.rb と spec/unagi_spec.rb の2ファイルの構成で問題を解いていきますので、任意のディレクトリ配下に以下のようなファイルを作成しておいて下さい。

(任意のディレクトリ)
  |
  +--unagi.rb
  |
  +--spec
    |
    +--unagi_spec.rb


さて、今回扱う問題は「長テーブルのうなぎ屋 (paizaランク B 相当)」です。paizaのスキルチェックでもBランク問題は回答時間が長く、ちょっと難しい部類の問題になります。

まずは問題を読んでみましょう。

■問題文

東京の下町に長テーブルで有名な老舗うなぎ屋がありました。


そのうなぎ屋にはとても大きい長テーブルがあり、テーブルの周りにn個の座席が配置されています。
座席には、時計回りに1, 2, …, nと番号が振られています。
座席はテーブルの周りに配置されているので、座席番号nの座席と1の座席は隣接しています。(下記図を参照の事)



今、m個のグループの人達が座席に順番に座りに来ます。i番目(1≦i≦m)のグループの人数をa_i人とします。
彼らは、長テーブルに並んだ座席の内、ある連続するa_i個の座席に一斉に座ろうとします。

ただしお客さんは江戸っ子なので、それら座席のうち、いずれか一つでも既に先客に座られている座席があった場合、一人も座らずにグループ全員で怒って帰ってしまいます。江戸っ子は気が早いんでぃ。


入力では、i番目のグループが座ろうとする連続した座席の位置は、整数b_iにより指定されます。i番目のグループは、座席番号b_iの座席を始点として、そこから時計回りにa_i個分の座席に座ろうとします。


最後のグループが座りに来た後、無事に長テーブルの座席に着席出来ている人数を出力するプログラムを作成してください。

この問題のポイントは「長テーブルの周りにお客さんがぐるりと座る」という状態をシミュレートする事だと思います。

ではまず、長テーブルを実現するための仕様を考えてみましょう。

問題文によると、

座席番号b_iの座席を始点として、そこから時計回りにa_i個分の座席に座ろうとします。

とあるので、まずは「長テーブルの座席番号」と「お客さんの人数」を指定すると
「座席にお客さんが配置される」といった機能
を作れば良さそうです。

◆unagi.rb

+class Unagi
+end

◆spec/unagi_spec.rb

+require './unagi.rb'
+
+describe Unagi do
+  describe '#seating' do
+    it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」'
+  end
+end

テストの実装はまだ何もありませんが、とりあえず実行してみましょう。

◆実行結果

$ rspec . --color --format documentation
Unagi
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」 (PENDING: Not yet implemented)

Pending:
  Unagi#seating 「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
    # Not yet implemented
    # ./spec/unagi_spec.rb:5

Finished in 0.00045 seconds (files took 0.14543 seconds to load)
1 example, 0 failures, 1 pending

PENDING: Not yet implemented (まだ実装が無いよ!)と言われてしまいますが、テストプログラム自体はとりあえず動いているようです。

では具体的な実装方法を考えてみましょう。

入力については決まっていますが、長テーブルに座っているお客さんをプログラム的にどうやって実現するのか。色々あると思いますが、まずは難しく考えず、単純にint型の配列でいいと思います。

ぐるりと座る長テーブルを一直線に引き伸ばしたような形を想像してみてください。

f:id:paiza:20140902154612g:plain

椅子にお客さんが座るメソッドseatingに仕様は以下のようになります。

引数に「長テーブルの座席番号」と「お客さんの人数」を指定すると、変数 seats に「お客さんが配置される」。

そのような仕様を書いたのがこれです。

◆unagi.rb

 class Unagi
+  def seating(seat_number, people_count)
+  end
 end

◆spec/unagi_spec.rb

 require './unagi.rb'
 
 describe Unagi do
   describe '#seating' do
-    it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」'
+    it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
+      unagi = Unagi.new
+      unagi.seating(1, 3)
+      expect(unagi.seats).to eq [1, 1, 1, 0, 0]
+    end
   end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」 (FAILED - 1)

Failures:

  1) Unagi#seating 「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
     Failure/Error: expect(unagi.seats).to eq [1, 1, 1, 0, 0]
     NoMethodError:
       undefined method `seats' for #<Unagi:0x007ff5e9187e00>
     # ./spec/unagi_spec.rb:8:in `block (3 levels) in <top (required)>'

実装がないのでもちろん動きません。
では実装してみましょう。

◆unagi.rb

 class Unagi
+  attr_reader :seats
+
   def seating(seat_number, people_count)
+    @seats = [0, 0, 0, 0, 0]
+
+    s = seat_number - 1
+    people_count.times do
+      @seats[s] = 1
+      s += 1
+    end
   end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」

はい、動きましたね。

まだまだ足りない点はありますが、現時点の仕様をきちんと満たしたプログラムです。
次に、長テーブルをぐるりと座れるようにプログラムを工夫してみましょう。

◆spec/unagi_spec.rb

 require './unagi.rb'
 
 describe Unagi do
   describe '#seating' do
     it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
       unagi = Unagi.new
       unagi.seating(1, 3)
       expect(unagi.seats).to eq [1, 1, 1, 0, 0]
     end
+
+    it '長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される' do
+      unagi = Unagi.new
+      # 5番目の席から3人座る
+      unagi.seating(5, 3)
+      expect(unagi.seats).to eq [1, 1, 0, 0, 1]
+    end
   end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
    長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される (FAILED - 1)

Failures:

  1) Unagi#seating 長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される
     Failure/Error: expect(unagi.seats).to eq [1, 1, 0, 0, 1]
       
       expected: [1, 1, 0, 0, 1]
            got: [0, 0, 0, 0, 1, 1, 1]      

rubyだと配列を自動的に確保してくれるので上記のような結果になっていますが、大抵の言語では「配列の領域外を参照した」という例外が発生すると思います。

では動くように実装を変えていきましょう。

◆unagi.rb

 class Unagi
   attr_reader :seats
 
   def seating(seat_number, people_count)
     @seats = [0, 0, 0, 0, 0]
 
     s = seat_number - 1
     people_count.times do
       @seats[s] = 1
       s += 1
+      # インデックスが配列の最後を超えると最初に戻る
+      s = 0 if @seats.size <= s
     end
   end
 end

1行追加するだけで実装できました。

ちなみに、インデックスが配列の最後を超えると最初に戻る、と言う仕組みをリングバッファ(環状バッファ)と言います。

さて、最初から気付いていた人もいると思いますが、長テーブルの「座席数」は問題によって変化するんですよね。そろそろここも修正しましょう。

座席数は長テーブルを実現するための根本的なデータなので、ここではコンストラクタで設定するようにします。「完全コンストラクタパターン」という設計パターンがありますが、オブジェクトの生成時点で実行に必要なパラメータを全て揃えるようにコンストラクタを設計するとクラスが安定する、という場合が多いです。

といっても、自分の場合は最初から完全コンストラクタパターンで実装するのではなく、「とりあえず何も考えずに一度作ってみる」→リファクタリングの段階で「コンストラクタで設定すべき」という流れが多いように思います。

◆spec/unagi_spec.rb

 require './unagi.rb'
 
 describe Unagi do
+  describe '#initialize' do
+    it '指定した座席数のゼロ配列が確保される' do
+      expect(Unagi.new(3).seats).to eq [0, 0, 0]
+    end
+  end
+
   describe '#seating' do
     it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
       unagi = Unagi.new
       unagi.seating(1, 3)
       expect(unagi.seats).to eq [1, 1, 1, 0, 0]
     end
 
     it '長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される' do 
       unagi = Unagi.new
       # 5番目の席から3人座る
       unagi.seating(5, 3)
       expect(unagi.seats).to eq [1, 1, 0, 0, 1]
     end
   end
 end

さて、ここでコンストラクタの実装を行うと、座席数固定を前提に書いてきたこれまでのテストが動かなくなります。

◆unagi.rb

 class Unagi
   attr_reader :seats
 
+  def initialize(seat_count)
+    @seats = Array.new(seat_count, 0)
+  end
+
   def seating(seat_number, people_count)
     @seats = [0, 0, 0, 0, 0]
 
     s = seat_number - 1
     people_count.times do
       @seats[s] = 1
       s += 1
       # インデックスが配列の最後を超えると最初に戻る
       s = 0 if @seats.size <= s
     end
   end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #initialize
    指定した座席数のゼロ配列が確保される
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」 (FAILED - 1)
    長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される (FAILED - 2)

テストがきちんと動作するように、テストや実装を修正しましょう。

◆unagi.rb

 class Unagi
   attr_reader :seats
 
   def initialize(seat_count)
     @seats = Array.new(seat_count, 0)
   end
 
   def seating(seat_number, people_count)
-    @seats = [0, 0, 0, 0, 0]
-
     s = seat_number - 1
     people_count.times do
       @seats[s] = 1
       s += 1
       # インデックスが配列の最後を超えると最初に戻る
       s = 0 if @seats.size <= s
     end
   end
 end

◆spec/unagi_spec.rb

 require './unagi.rb'
 
 describe Unagi do
   describe '#initialize' do
     it '指定した座席数のゼロ配列が確保される' do
       expect(Unagi.new(3).seats).to eq [0, 0, 0]
     end
   end
 
   describe '#seating' do
     it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
-      unagi = Unagi.new
+      unagi = Unagi.new(5)
       unagi.seating(1, 3)
       expect(unagi.seats).to eq [1, 1, 1, 0, 0]
     end
 
     it '長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される' do
-      unagi = Unagi.new
+      unagi = Unagi.new(5)
       # 5番目の席から3人座る
       unagi.seating(5, 3)
       expect(unagi.seats).to eq [1, 1, 0, 0, 1]
     end
   end
 end

さて、あと一息です。
もう一度問題文を読んでみましょう。

ただしお客さんは江戸っ子なので、それら座席のうち、いずれか一つでも既に先客に座られている座席があった場合、
一人も座らずにグループ全員で怒って帰ってしまいます。江戸っ子は気が早いんでぃ。

色々脚色されていますが、要は「座席に他のお客さんが配置されている場合、座席の状態は変化しない」という事ですね。実際の現場でも「話の流れから仕様を汲み取る」といった能力が必要とされる事が多いですよね。

ではこの仕様を追加・実装してみましょう。

まずはテストの追加から。

◆spec/unagi_spec.rb

 require './unagi.rb'
 
 describe Unagi do
   describe '#initialize' do
     it '指定した座席数のゼロ配列が確保される' do
       expect(Unagi.new(3).seats).to eq [0, 0, 0]
     end
   end
 
   describe '#seating' do
     it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
       unagi = Unagi.new(5)
       unagi.seating(1, 3)
       expect(unagi.seats).to eq [1, 1, 1, 0, 0]
     end
 
     it '長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される' do
       unagi = Unagi.new(5)
       # 5番目の席から3人座る
       unagi.seating(5, 3)
       expect(unagi.seats).to eq [1, 1, 0, 0, 1]
     end
+
+    it '座席に他のお客さんが配置されている場合、座席の状態は変化しない' do
+      unagi = Unagi.new(5)
+      unagi.seating(2, 3)
+      expect(unagi.seats).to eq [0, 1, 1, 1, 0]
+      unagi.seating(4, 2)
+      expect(unagi.seats).to eq [0, 1, 1, 1, 0]
+    end
   end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #initialize
    指定した座席数のゼロ配列が確保される
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
    長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される
    座席に他のお客さんが配置されている場合は、変化しない (FAILED - 1)

Failures:

  1) Unagi#seating 座席に他のお客さんが配置されている場合は、変化しない
     Failure/Error: expect(unagi.seats).to eq [0, 1, 1, 1, 0]
       
       expected: [0, 1, 1, 1, 0]
            got: [0, 1, 1, 1, 1]

そして、仕様を満たすプログラムが以下です。要するに、座席に配置を行う処理の前に、座席の空き状態をチェックする処理を追加すれば良いことになります。

◆unagi.rb

 class Unagi
   attr_reader :seats
 
   def initialize(seat_count)
     @seats = Array.new(seat_count, 0)
   end
 
   def seating(seat_number, people_count)
+    s = seat_number - 1
+    people_count.times do
+      return if @seats[s] == 1
+      s += 1
+      s = 0 if @seats.size <= s
+    end
+
     s = seat_number - 1
     people_count.times do
       @seats[s] = 1
       s += 1
       # インデックスが配列の最後を超えると最初に戻る
       s = 0 if @seats.size <= s
     end
   end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #initialize
    指定した座席数のゼロ配列が確保される
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
    長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される
    座席に他のお客さんが配置されている場合、座席の状態は変化しない

では最後に、

最後のグループが座りに来た後、無事に長テーブルの座席に着席出来ている人数を出力するプログラムを作成してください。

の部分を実装します。

◆spec/unagi_spec.rb

 require './unagi.rb'
 
 describe Unagi do
   describe '#initialize' do
     it '指定した座席数のゼロ配列が確保される' do
       expect(Unagi.new(3).seats).to eq [0, 0, 0]
     end
   end
 
   describe '#seating' do
     it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
       unagi = Unagi.new(5)
       unagi.seating(1, 3)
       expect(unagi.seats).to eq [1, 1, 1, 0, 0]
     end
 
     it '長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される' do
       unagi = Unagi.new(5)
       # 5番目の席から3人座る
       unagi.seating(5, 3)
       expect(unagi.seats).to eq [1, 1, 0, 0, 1]
     end
 
     it '座席に他のお客さんが配置されている場合、座席の状態は変化しない' do
       unagi = Unagi.new(5)
       unagi.seating(2, 3)
       expect(unagi.seats).to eq [0, 1, 1, 1, 0]
       unagi.seating(4, 2)
       expect(unagi.seats).to eq [0, 1, 1, 1, 0]
     end
   end
+
+  describe '#count' do
+    it '座席に着席しているお客さんの人数を返却する' do
+      unagi = Unagi.new(5)
+      unagi.seating(1, 1)
+      expect(unagi.count).to eq 1
+      unagi.seating(2, 1)
+      expect(unagi.count).to eq 2
+      unagi.seating(3, 1)
+      expect(unagi.count).to eq 3
+      unagi.seating(3, 1)
+      expect(unagi.count).to eq 3
+      unagi.seating(4, 1)
+      expect(unagi.count).to eq 4
+      unagi.seating(5, 1)
+      expect(unagi.count).to eq 5
+    end
+  end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #initialize
    指定した座席数のゼロ配列が確保される
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
    長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される
    座席に他のお客さんが配置されている場合、座席の状態は変化しない
  #count
    座席に着席しているお客さんの人数を返却する (FAILED - 1)

Failures:

  1) Unagi#count 座席に着席しているお客さんの人数を返却する
     Failure/Error: expect(unagi.count).to eq 1
     NoMethodError:
       undefined method `count' for #<Unagi:0x007ff2a30ccdb0 @seats=[1, 0, 0, 0, 0]>

これは、座席を模したint型配列内の1を数えればOKなので、以下のような実装になります。

◆unagi.rb

 class Unagi
   attr_reader :seats
 
   def initialize(seat_count)
     @seats = Array.new(seat_count, 0)
   end
 
   def seating(seat_number, people_count)
     s = seat_number - 1
     people_count.times do
       return if @seats[s] == 1
       s += 1
       s = 0 if @seats.size <= s
     end
 
     s = seat_number - 1
     people_count.times do
       @seats[s] = 1
       s += 1
       # インデックスが配列の最後を超えると最初に戻る
       s = 0 if @seats.size <= s
     end
   end
+
+  def count
+    @seats.select{ |s| s == 1 }.count
+  end
 end

◆実行結果

$ rspec . --color --format documentation

Unagi
  #initialize
    指定した座席数のゼロ配列が確保される
  #seating
    「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」
    長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される
    座席に他のお客さんが配置されている場合、座席の状態は変化しない
  #count
    座席に着席しているお客さんの人数を返却する

Finished in 0.00361 seconds (files took 0.41746 seconds to load)
5 examples, 0 failures

本当はもっと厳密に境界値テスト等を加えていくべきだと思いますが、ここまでの実装に標準入出力を加えれば全てのテストケースをクリアすることが出来ます。

◆unagi.rb

 class Unagi
   attr_reader :seats
 
   def initialize(seat_count)
     @seats = Array.new(seat_count, 0)
   end
 
   def seating(seat_number, people_count)
     s = seat_number - 1
     people_count.times do
       return if @seats[s] == 1
       s += 1
       s = 0 if @seats.size <= s
     end
 
     s = seat_number - 1
     people_count.times do
       @seats[s] = 1
       s += 1
       # インデックスが配列の最後を超えると最初に戻る
       s = 0 if @seats.size <= s
     end
   end
 
   def count
     @seats.select{ |s| s == 1 }.count
   end
 end
+
+if __FILE__ == $0
+  seat_count, group_count = gets.split.map(&:to_i)
+  unagi = Unagi.new(seat_count)
+  group_count.times do
+    people_count, seat_number = gets.split.map(&:to_i)
+    unagi.seating(seat_number, people_count)
+  end
+  puts unagi.count
+end

これで完成!!

■まとめ

テスト駆動で解くpaiza Learning問題、いかがだったでしょうか? 今回は練習問題についてでしたが、特に実務では難しいと思う課題でも、問題を小さく分割し、1つずつ解決していく事で大体の事はクリアできるのではないかと思います。paizaの問題もただ解くのではなく、テスト駆動の練習として使ってみても面白いのではないかと思いまとめてみました。少しでも皆様のプログラミングスキル向上のお役に立てば幸いです。

Rspecでテスト駆動開発を学ぶのにオススメ本

Everyday Rails - RSpecによるRailsテスト入門 テスト駆動開発の習得に向けた実践的アプローチ
https://leanpub.com/everydayrailsrspec-jp

Rails アプリにおけるテスト駆動のノウハウについてまとめられた本です。日本語翻訳された Rspec 解説の本の中では最も内容が充実しているのではないでしょうか。
Webで検索すれば何となくRspecの使い方は分かるのですが、実際のRailsアプリに対して具体的にどのような方針で、どのようなテストを書けば良いのか、というのは中々見えてこないものです。
この本では、Modelのテスト、Controllerのテスト、Capybaraを使ったFeatureテストについて、具体的なシチュエーションと共に解説されているので非常に理解し易いです
既にRspecを使用されている方でも、他者のアプローチを見ることで新たな発見があるのではないか、と思います。自分がそうだったのですが、これまで独学でRspecを使用されてきた方には特にオススメです。
また、一度購入すれば、加筆修正があった際に無料で再ダウンロード出来る、という点も非常にありがたいです。

paiza Learningは、特に登録しなくて簡易実行が出来ますので、プログラミングスキルをあげてみたいという方は是非覗いてみてください。

最後にRubyとJavaの完成系についても掲載をしておきます。

◆unagi.rb (完成形)

class Unagi
  attr_reader :seats

  def initialize(seat_count)
    @seats = Array.new(seat_count, 0)
  end

  def seating(seat_number, people_count)
    s = seat_number - 1
    people_count.times do
      return if @seats[s] == 1
      s += 1
      s = 0 if @seats.size <= s
    end

    s = seat_number - 1
    people_count.times do
      @seats[s] = 1
      s += 1
      s = 0 if @seats.size <= s
    end
  end

  def count
    @seats.select{ |s| s == 1 }.count
  end
end

if __FILE__ == $0
  seat_count, group_count = gets.split.map(&:to_i)
  unagi = Unagi.new(seat_count)
  group_count.times do
    people_count, seat_number = gets.split.map(&:to_i)
    unagi.seating(seat_number, people_count)
  end
  puts unagi.count
end

◆spec/unagi_spec.rb (完成形)

require './unagi.rb'

describe Unagi do
  describe '#initialize' do
    it '指定した座席数のゼロ配列が確保される' do
      expect(Unagi.new(3).seats).to eq [0, 0, 0]
    end
  end

  describe '#seating' do
    it '「長テーブルの座席番号」と「お客さんの人数」を指定すると「座席にお客さんが配置される」' do
      unagi = Unagi.new(5)
      unagi.seating(1, 3)
      expect(unagi.seats).to eq [1, 1, 1, 0, 0]
    end

    it '長テーブルの最初と最後の席をまたぐ場合、最初の席から配置される' do
      unagi = Unagi.new(5)
      # 5番目の席から3人座る
      unagi.seating(5, 3)
      expect(unagi.seats).to eq [1, 1, 0, 0, 1]
    end

    it '座席に他のお客さんが配置されている場合、座席の状態は変化しない' do
      unagi = Unagi.new(5)
      unagi.seating(2, 3)
      expect(unagi.seats).to eq [0, 1, 1, 1, 0]
      unagi.seating(4, 2)
      expect(unagi.seats).to eq [0, 1, 1, 1, 0]
    end
  end

  describe '#count' do
    it '座席に着席しているお客さんの人数を返却する' do
      unagi = Unagi.new(5)
      unagi.seating(1, 1)
      expect(unagi.count).to eq 1
      unagi.seating(2, 1)
      expect(unagi.count).to eq 2
      unagi.seating(3, 1)
      expect(unagi.count).to eq 3
      unagi.seating(3, 1)
      expect(unagi.count).to eq 3
      unagi.seating(4, 1)
      expect(unagi.count).to eq 4
      unagi.seating(5, 1)
      expect(unagi.count).to eq 5
    end
  end
end

◆Main.java (javaで解いたバージョン)

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
  public int[] seats;
  public Main (int seatCount) {
    seats = new int[seatCount];
  }
  public void seating (int seatNumber, int peopleCount) {
    int s = seatNumber - 1;
    for (int i = 0; i < peopleCount; i++) {
      if (seats[s] == 1) {
        return;
      }
      s++;
      if (seats.length <= s){
        s = 0;
      }
    }
    s = seatNumber - 1;
    for (int i = 0; i < peopleCount; i++) {
      seats[s] = 1;
      s++;
      if (seats.length <= s){
        s = 0;
      }
    }
  }
  public int count () {
        int count = 0;
        for (int s : seats){
           count += s;
        }
    return count;
  }
  
    public static void main(String args[]) throws  Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line = br.readLine();
        String[] inputArray = line.split(" ");
        int seatCount = Integer.valueOf(inputArray[0]);
        int groupCount = Integer.valueOf(inputArray[1]);
        Main unagi = new Main(seatCount);
        for(int i = 0; i < groupCount; i++){
          inputArray = br.readLine().split(" ");
          int peopleCount = Integer.valueOf(inputArray[0]);
          int seatNumber = Integer.valueOf(inputArray[1]);
          unagi.seating(seatNumber, peopleCount);
        }

        System.out.println(unagi.count());
    }
}

◆MainTest.java

import static org.junit.Assert.assertThat;
import org.junit.Test;
import static org.hamcrest.core.Is.is;

public class MainTest {
  @Test
  public void 指定した座席数のゼロ配列が確保される () {
    Main main = new Main(3);
        assertThat(main.seats, is(new int[] {0,0,0}));
  }
  @Test
  public void 長テーブルの座席番号とお客さんの人数を指定すると座席にお客さんが配置される() {
    Main main = new Main(5);
    main.seating(1, 3);
    assertThat(main.seats, is(new int[] {1,1,1,0,0}));
  }
  @Test
  public void 長テーブルの最初と最後の席をまたぐ場合は最初の席から配置される() {
    Main main = new Main(5);
    main.seating(5, 3);
    assertThat(main.seats, is(new int[] {1,1,0,0,1}));
  }
  @Test
  public void 座席に他のお客さんが配置されている場合座席の状態は変化しない() {
    Main main = new Main(5);
    main.seating(2, 3);
    assertThat(main.seats, is(new int[] {0, 1, 1, 1, 0}));
    main.seating(4, 2);
    assertThat(main.seats, is(new int[] {0, 1, 1, 1, 0}));
  }
  @Test
  public void 座席に着席しているお客さんの人数を返却する() {
    Main main = new Main(5);
    main.seating(1, 1);
    assertThat(main.count(), is(1));
    main.seating(2, 1);
    assertThat(main.count(), is(2));
    main.seating(3, 1);
    assertThat(main.count(), is(3));
    main.seating(4, 1);
    assertThat(main.count(), is(4));
    main.seating(5, 1);
    assertThat(main.count(), is(5));
  }
}



paizaは、技術を追い続けることが仕事につながり、スキルのある人がきちんと評価される場を作ることで、日本のITエンジニアの地位向上を目指したいと考えています。

自分のスキルを磨いていきたいと考えている方におすすめなのが「paizaラーニング」。オンラインでプログラミングしながらスキルアップできる入門学習コンテンツです。初心者でも楽しくプログラミングの基本を学ぶことができます。

paizaラーニング

そして、paizaでは、Webサービス開発企業などで求められるコーディング力や、テストケースを想定する力などが問われるプログラミングスキルチェック問題も提供しています。

paizaのスキルチェック

スキルチェックに挑戦した人は、その結果によってS・A・B・C・D・Eの6段階のランクを取得できます。必要なスキルランクを取得すれば、書類選考なしで企業の求人に応募することも可能です。「自分のプログラミングスキルを客観的に知りたい」「スキルを使って転職したい」という方は、ぜひチャレンジしてみてください。

*1:正確にはビヘイビア(振る舞い)駆動開発と言います。

paizaのおすすめコンテンツ

PPG proken プログラミングゲーム「初恋 プログラミング研究会〜海に行こうよ〜」 PPG Bingo プログラミングゲーム「コードレビューBINGO!!」
paiza転職 paiza新卒 EN:TRY paizaラーニング 記事内に記載している情報は、記事公開時点でのものとなります。 Copyright Paiza, Inc, All rights reserved.