「オフラインリアルタイムどう書く第25回(めぐるセル)」の問題を解いてみた


[横浜へなちょこプログラミング勉強会](https://yhpg.doorkeeper.jp/)にて過去に出題された[めぐるセル](http://nabetani.sakura.ne.jp/hena/ord25rotcell/)を解いてみた。
回答にかかった時間はぎりぎり60分。

1. 指定セルの外周を取得
2. 外周を時計回りさせやすいようにソート
3. `Array.rotate`で時計回りとなるようにセルの入れ替え
4. 入れ替えたセルの内容を返す

もうちょっとうまいやり方があったんじゃないだろうかと思うが、考えているばかりで時間が過ぎてしまったのでひとまず真っ先に思いついた方法で実装してしまった。

```ruby
class RotationBox
  def initialize
    chars = (?a..?y).to_a
    @sort_priority = [:top_left, :top, :top_right, :right, :bottom_right, :bottom, :bottom_left, :left]
    @box = 5.times.map{|y|
      5.times.map{|x|
        chars[(y * 5) + x]
      }
    }
  end

  def rotate_all input
    last = input.scan(/(\w)(\w)/).inject([]){|r, (a, b)|
      rotate a, b
    }
    last.empty? ? "none" : last.sort.join
  end

  private

  def axis val
    @box.each.with_index.inject([nil, nil]){|r, (line, y)|
      if x = line.find_index(val)
        break [x, y]
      else
        r
      end
    }
  end

  def rotate a, b
    ax, ay = axis a
    bx, by = axis b
    top, bottom, left, right = [
      ay < by ? ay : by,
      ay > by ? ay : by,
      ax < bx ? ax : bx,
      ax > bx ? ax : bx
    ]
    round = round(top, bottom, left, right).sort{|(axx, ayy), (bxx, byy)|
      position_a = position axx, ayy, top, bottom, left, right
      position_b = position bxx, byy, top, bottom, left, right
      if position_a == :top and position_b == :top
        axx <=> bxx
      elsif position_a == :right and position_b == :right
        ayy <=> byy
      elsif position_a == :bottom and position_b == :bottom
        (axx <=> bxx) * -1
      elsif position_a == :left and position_b == :left
        (ayy <=> byy) * -1
      else
        @sort_priority.index(position_a) <=> @sort_priority.index(position_b)
      end
    }
    round.map{|(x, y)| @box[y][x]}.rotate(-1).each.with_index{|c, i|
      x, y = round[i]
      @box[y][x] = c
    }
  end

  def block top, bottom, left, right
    5.times.select{|x|
      x.between? left, right
    }.product(5.times.select{|y|
      y.between? top, bottom
    })
  end

  def round top, bottom, left, right
    block = block top, bottom, left, right
    block.map{|(x, y)|
      [
        [x - 1, y - 1], [x    , y - 1], [x + 1, y - 1],
        [x - 1, y    ], [x + 1, y    ],
        [x - 1, y + 1], [x    , y + 1], [x + 1, y + 1]
      ].select{|xx, yy|
        xx.between?(0, 4) && yy.between?(0, 4) && !block.include?([xx, yy])
      }
    }.flatten(1).uniq
  end

  def position x, y, top, bottom, left, right
    if top > y
      left > x ? :top_left : (right < x ? :top_right : :top)
    elsif bottom < y
      left > x ? :bottom_left : (right < x ? :bottom_right : :bottom)
    elsif left > x
      :left
    elsif right < x
      :right
    end
  end
end

test = <<_TEST
/*0*/ test( "ab,gg,uj,pt,an,ir,rr", "hpqsvwxy" );    
/*1*/ test( "gs,ok", "abcdftvwxy" );    
/*2*/ test( "gs,sg,ok", "none" );    
/*3*/ test( "aa,bb,hh,nn", "hiostwxy" );    
/*4*/ test( "ae,ko,uy,cw", "bdgilnqsvx" );    
/*5*/ test( "am,gs,am,gs,am,gs,am,gs", "cfhkmqrvwx" );    
/*6*/ test( "ay", "none" );    
/*7*/ test( "gs,ay", "defjkoptuv" );    
/*8*/ test( "bx,ay", "none" );    
/*9*/ test( "ft,ay", "defjkoptuv" );    
/*10*/ test( "ab,cd,ef,gh,ij,kl,mn,op,qr,st,uv,wx", "cdjmnry" );    
/*11*/ test( "wx,uv,st,qr,op,mn,kl,ij,gh,ef,cd,ab", "kmoxy" );    
/*12*/ test( "am,cj,ac,em,ss,cy,aa,ee,ff,vp", "none" );    
/*13*/ test( "uf,oq,gn,ss,ca,hv,ej", "none" );    
/*14*/ test( "cc,wk,uu,ws,bk,aa,vv", "bei" );    
/*15*/ test( "tr,ou,ll,pp,jh,vf,yy,nr,rr,oo", "rxy" );    
/*16*/ test( "ky,ov,ri,qm,nn,ee,ws,em,ca,ak", "biju" );    
/*17*/ test( "ty", "nosx" );    
/*18*/ test( "ll,uh,hq,ss,nx,ry,ku,ab,jj", "efouv" );    
/*19*/ test( "yl,mu,qj,ss,ep", "mnqru" );    
/*20*/ test( "kj,ee,qk", "fglruv" );    
/*21*/ test( "xi,wd,hf", "ciknqr" );    
/*22*/ test( "fx,ak,cc,ce", "bdhijnp" );    
/*23*/ test( "li,jf,pp,qm,hg,sf", "akntuwx" );    
/*24*/ test( "jw", "bcdeglqv" );    
/*25*/ test( "uk,oe,xr", "dglmoqsy" );    
/*26*/ test( "bb,ov,pd,dd,xk,is,hh,xd,xx,kq,pp,ku", "cfhjopqvy" );    
/*27*/ test( "iq,fn,il,ww,ox,la,or,ga,wg,ef,us", "cfgjopvxy" );    
/*28*/ test( "km,po", "abcdenqrst" );    
/*29*/ test( "tc,mh,cw", "abefjkoptu" );    
/*30*/ test( "fm,jx,xx,pi,gs,au,uq,ut,ap,vb", "cdghjmortux" );    
/*31*/ test( "ik,xl,si", "abcdflorvwx" );    
/*32*/ test( "nu,cc,lv,bu,tt,ww,xk,ia,in,sa,my", "abcefgpqrstu" );    
/*33*/ test( "tt,ak,xh,tk,oo,yr,na,yv,gm,vh", "degiklmnquwx" );    
/*34*/ test( "kk,ob,kk,fm,xk", "acdegjlopqruy" );    
/*35*/ test( "uq,ko,pf,yy,ig,tu,ve,ve,qy,mh,oo,dv", "befjkoqrtuwxy" );    
/*36*/ test( "aj,hb,ar,ii,np,ki,hg,vd", "cefhjlmopqtwxy" );    
/*37*/ test( "vv,sf,ww,my,mm,sq,fb,ly,fu,ls", "bfghkmnptuvwxy" );    
/*38*/ test( "jj,bp,gs", "abdefijkprtuvwxy" );    
/*39*/ test( "sv,sn,mn,gn,gi", "abcdefhjnpqtuvxy" );
_TEST

require 'minitest/autorun'

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