「オフラインリアルタイムどう書く第19回(不良セクタの隣)」の問題を解いてみた


[横浜へなちょこプログラミング勉強会](https://yhpg.doorkeeper.jp/)にて過去に出題された[不良セクタの隣](http://nabetani.sakura.ne.jp/hena/ord19nebasec/)を解いてみた。
回答にかかった時間は100分程度と40分オーバー。

初めは`DisckSectors#search_pending_sectors`の中にごちゃごちゃと条件文やら数式やら書いていたのだが、書いていて自分で混乱してしまったのでかなり細かくメソッドに分けることにした。
結果`search_pending_sectors`の中身自体はすごくシンプルになったのだが、その代わりごちゃごちゃとプライベートメソッドを生やしすぎてしまった。
プライベートメソッドたちはリファクタリングすればもっと減らせる（そもそもメソッドにする必要すらないものもある）のだが、テストにパスした時点で目標時間60分を大幅に超えていたため、ひとまずこのままとすることにした。

```ruby
class DiscSectors
  def search_pending_sectors bad_sectors
    bad_sectors = bad_sectors.split(",").map &:to_i
    neighbor_sectors = bad_sectors.inject([]){|addresses, addr|
      track, sec = addr.to_i.divmod(100)
      [
        addresses,
        next_address(track, sec),
        prev_address(track, sec),
        track < 4 ? next_track_addresses(track, sec) : nil,
        track > 1 ? prev_track_addresses(track, sec) : nil
      ].flatten
    } - bad_sectors
    pending = neighbor_sectors.compact.group_by{|v| v}.select{|addr, group| group.size > 1}.keys.sort
    pending.empty? ? ["none"] : pending
  end

  private

  def next_address track, sec
    track_address(track, sec + 1)
  end

  def prev_address track, sec
    track_address(track, sec - 1)
  end

  def next_track_addresses track, sec
    [
      (sec % track).zero? ? track_address(track + 1, next_track_bigger_sector(track, sec) - 1) : nil,
      track_address(track + 1, next_track_bigger_sector(track, sec)),
      track_address(track + 1, next_track_bigger_sector(track, sec) + 1)
    ].compact
  end

  def prev_track_addresses track, sec
    address = [
      track_address(track - 1, prev_track_bigger_sector(track, sec)),
      (sec % track).zero? ? nil : track_address(track - 1, prev_track_bigger_sector(track, sec) - 1)
    ].compact
  end

  def track_address track, sec
    track_address_group(track) + track_sector(track, sec)
  end

  def track_address_group track
    track * 100
  end

  def next_track_bigger_sector track, sec
    (track + 1) * (sec / track) + (sec % track)
  end

  def prev_track_bigger_sector track, sec
    (track - 1) * (sec / track) + (sec % track)
  end

  def track_sector track, serial_number
    serial_number % track_sectors(track)
  end

  def track_sectors track
    8 * track
  end
end

test = <<_TEST
/*0*/ test( "400,401,302", "300,301,402" );    
/*1*/ test( "105,100,306,414", "none" );    
/*2*/ test( "100", "none" );    
/*3*/ test( "211", "none" );    
/*4*/ test( "317", "none" );    
/*5*/ test( "414", "none" );    
/*6*/ test( "100,106", "107" );    
/*7*/ test( "205,203", "102,204" );    
/*8*/ test( "303,305", "304" );    
/*9*/ test( "407,409", "306,408" );    
/*10*/ test( "104,103", "207" );    
/*11*/ test( "204,203", "102,305" );    
/*12*/ test( "313,314", "209,418" );    
/*13*/ test( "419,418", "314" );    
/*14*/ test( "100,102,101", "201,203" );    
/*15*/ test( "103,206,309", "205,207,308,310" );    
/*16*/ test( "414,310,309", "206,311,413" );    
/*17*/ test( "104,102,206,307,102,202", "101,103,203,204,205,207,308" );    
/*18*/ test( "104,206,308,409,407", "103,205,207,306,307,309,408,410" );    
/*19*/ test( "313,406,213,301,409,422,412,102,428", "none" );    
/*20*/ test( "101,300,210,308,423,321,403,408,415", "none" );    
/*21*/ test( "304,316,307,207,427,402,107,431,412,418,424", "none" );    
/*22*/ test( "205,408,210,215,425,302,311,400,428,412", "none" );    
/*23*/ test( "200,311,306,412,403,318,427,105,420", "none" );    
/*24*/ test( "105,305,407,408,309,208,427", "104,209,306,406" );    
/*25*/ test( "311,304,322,404,429,305,316", "203,303,321,405,406,430" );    
/*26*/ test( "210,401,316,425,101", "211,315" );    
/*27*/ test( "414,403,404,416,428,421", "303,415" );    
/*28*/ test( "207,300,103,211,428", "104,206" );    
/*29*/ test( "322,314,310", "none" );    
/*30*/ test( "427,200,215", "100,323" );    
/*31*/ test( "311,402,424,307,318,430,323,305,201", "200,204,301,302,306,322,423,425,431" );    
/*32*/ test( "425,430,408", "none" );    
/*33*/ test( "202,320,209,426", "319,427" );    
/*34*/ test( "430,209,302,310,304,431,320", "202,303,323" );    
/*35*/ test( "208,206,406,424,213,312", "207,311,313" );    
/*36*/ test( "420,302,313,413,317,402", "301,403" );    
/*37*/ test( "319,306,309,418,204,411", "305,307,308,412" );    
/*38*/ test( "400,308,105,430,203,428,209", "104,210,429,431" );    
/*39*/ test( "200,305,214", "215" );    
/*40*/ test( "214,408,410,407,317,422", "306,316,409,423" );
_TEST

require 'minitest/autorun'

describe 'BadSector' do
  test.split("\n").each do |line|
    t, n, input, expect = line.match(/^\/\*(\d+)\*\/\s*test\(\s*"([^"]+)",\s*"([^"]+)"\s*\);\s*$/).to_a
    disc = DiscSectors.new
    it input do
      assert_equal expect, disc.search_pending_sectors(input).join(",")
    end
  end
end
```