2h 20m 28s
아이디어는
JuNe 선배가 말했던 것이다.(저번 자바 컨퍼런스에서 였던가..) 한 번 나선형으로 진행되는 것을 같은 층으로 본다. 그러면 가장 바깥쪽은 1층, 다음 안쪽은 2층 이런 식으로 안쪽으로 갈수록 높이가 높아진다. 한 사람이 피라미드를 한 바퀴 돌고 다음 층으로 올라가면서 자신이 들렀던 곳이 몇 번째인지, 좌표는 무엇인지 기억한다. 한 층을 다 돌면 시작했던 자리로 돌아오기 때문에 중복해서 기억한 좌표는 지우고 다음 층으로 이동한다.
하지만 여지껏 그러한 접근법을 알고서도 TDD로 풀지를 못했었다. 매번 나선형 "행렬"에 어떻게 숫자를 새길지만 생각했기 때문이다. 그러다 보니 2차원 배열의 인덱스를 조작하는 수준에서 생각이 벗어나질 못했다. 하지만 사실은 움직임(이전의 인덱스 조작), 움직인 점들, 행렬을 따로 생각할 수 있었다. 아! 이렇게 테스트 하면 되겠구나!
TDD로 풀었다는 점이 기쁘다. 처음부터 너무 메서드를 어디에 속하게 할 지 고민하지 않고 시작한 것이 유용했다. 그 결과로 예전 같으면 생각하지 못했을 Direction클래스와 그 하위 클래스가 탄생했다. 또한 행렬은 최종 결과물을 저장하고 보여주는 일종의 뷰처럼 쓰였다.
현재는 행렬 구성이 비효율적이다. 움직였던 기록을 가지고 행렬을 구성하기를 반복한다.이것을 수정할 때 좀더 효율적으로 작동하게 만들어야겠다. Mover클래스, Array클래스의 종료검사, 테스트 케이스는 확실히 Refactoring이 필요하다.
~cpp
import unittest
ROW = 0
COL = 1
class Direction:
def move(self, coordinate, board):
if ( board.isWall( self.next(coordinate) ) ):
return coordinate
return self.next(coordinate)
class Down(Direction):
def next(self, coordinate):
return (coordinate[ROW] + 1, coordinate[COL])
class Up( Direction ):
def next(self, coordinate):
return (coordinate[ROW] - 1, coordinate[COL])
class Right(Direction):
def next(self, coordinate):
return (coordinate[ROW], coordinate[COL] + 1 )
class Left(Direction):
def next(self, coordinate):
return (coordinate[ROW], coordinate[COL] - 1 )
class Board:
def __init__(self):
self.size = 0
def setBoundary( self, start, end ):
self.start = start
self.end = end
def isWall( self, coordinate ):
if ( coordinate[ROW] < self.start ):
return True
elif ( coordinate[ROW] >= self.end ):
return True
elif ( coordinate[COL] < self.start ):
return True
elif ( coordinate[COL] >= self.end ):
return True
return False
def decreaseBoundary(self, amount):
self.setBoundary( self.start+amount, self.end-amount )
class Mover:
def __init__(self):
self.coordinate = (0,0)
self.moveCount = 0
self.history = []
def position(self):
return self.coordinate
def goStraight(self, direction, board):
while ( not self.coordinate == direction.move(self.coordinate, board) ):
self.history.append( Point( self.coordinate, self.moveCount+1 ) )
self.moveCount += 1
self.coordinate = direction.move( self.coordinate, board )
self.history.append( Point( self.coordinate, self.moveCount+1 ) )
def mark(self, point, value):
point.value = value
def _setMoveCount(self, count):
self.moveCount = count
def _setPosition(self, coordinate):
self.coordinate = coordinate
def getHistory(self):
return self.history
def turnRound(self, board):
self.goStraight( Right(), board )
self.goStraight( Down(), board )
self.goStraight( Left(), board )
self.goStraight( Up(), board )
board.decreaseBoundary(1)
self.eraseLastMovement()
self._setPosition( self.nextFloor() )
def nextFloor(self):
return (self.coordinate[ROW]+1, self.coordinate[COL]+1 )
def eraseLastMovement(self):
self._setMoveCount( self.history.pop().value - 1 )
class Point:
def __init__(self, coordinate, value):
self.coordinate = coordinate
self.value = value
class Array:
def __init__(self, size):
self.matrix = []
for i in range(size):
self.matrix.append( [Point((-1,-1),-1)] * size )
def isAllFilled(self):
for row in self.matrix:
for point in row:
if ( point.value < 0 ):
return False
return True
def _fillAll(self):
for row in self.matrix:
for point in row:
point.value = 1
def get(self, row, col):
return self.matrix[row][col]
def construct(self, pointList):
for p in pointList:
self.matrix[p.coordinate[ROW]][p.coordinate[COL]] = p
def printValues(self):
for row in self.matrix:
for point in row:
print point.value,
print "\t",
print
class SpiralArrayTest(unittest.TestCase):
def setUp(self):
self.board = Board()
self.size = 10
self.board.setBoundary(start=0, end=self.size)
self.mover = Mover()
self.array = Array( self.size )
def testMoveDown(self):
self.assertEquals( (1,0), Down().move( (0,0), self.board ) )
self.assertEquals( (self.size-1,0), Down().move( (self.size-1,0), self.board ) )
def testMoveUp(self):
self.assertEquals( (0,0), Up().move( (0,0), self.board ) )
self.assertEquals( (self.size-1,0), Up().move( (self.size,0), self.board ) )
def testIsWall(self):
self.assertEquals( False, self.board.isWall( (0,0) ) )
self.assertEquals( True, self.board.isWall( (-1,0) ) )
self.assertEquals( True, self.board.isWall( (0,-1) ) )
self.assertEquals( True, self.board.isWall( (0,self.size) ) )
self.assertEquals( True, self.board.isWall( (self.size,0) ) )
def testGoStraightRight(self):
self.mover.goStraight(Right(), self.board)
self.assertEquals( (0,self.size-1), self.mover.position() )
def testMark(self):
point = Point((0,0),0)
self.mover._setMoveCount(1)
self.mover.mark( point, self.mover.moveCount )
self.assertEquals( 1, point.value )
def testStorePointCoordinate(self):
self.mover.goStraight( Down(), self.board )
points = self.mover.getHistory()
self.assertEquals( (self.size-1,0), points.pop().coordinate )
self.assertEquals( (self.size-2,0), points.pop().coordinate )
def testStoreMoveCount(self):
self.mover._setPosition((self.size-1, self.size-1))
self.mover.goStraight( Left(), self.board )
points = self.mover.getHistory()
self.assertEquals( self.size, points.pop().value )
self.assertEquals( self.size-1, points.pop().value )
def testTurnRound(self):
self.mover._setPosition( (0,0) )
self.mover.turnRound(self.board)
self.assertEquals( (1,0), self.mover.getHistory().pop().coordinate )
def testIsFinished(self):
self.assertEquals( False, self.array.isAllFilled() )
self.array._fillAll()
self.assertEquals( True, self.array.isAllFilled() )
def testConstructArray(self):
self.mover._setPosition( (0,0) )
self.mover.turnRound(self.board)
self.array.construct( self.mover.getHistory() )
self.assertEquals( (0,0), self.array.get(0,0).coordinate )
self.assertEquals( (self.size-1, self.size-1),
self.array.get(self.size-1, self.size-1).coordinate )
if __name__ == "__main__":
## unittest.main()
inputSize = input()
board = Board()
board.setBoundary(0, inputSize )
array = Array(inputSize)
mover = Mover()
mover._setPosition((0,0))
while not ( array.isAllFilled() ):
mover.turnRound(board)
array.construct(mover.getHistory())
array.printValues()