I told you there would be more!
Today I focused on ERASING my previous Advent of Code solutions and using TDD to write the new code.
Test –> Code –> Refactor
That is what I was focusing on every step of the way. I started from the beginning, so this is the easiest and first ever Advent of Code problem.
Easy Problem
For part 1, you are given a long text filled with mixed parenthesis similar to
)())((()()((
. Each parenthesis corresponds to the movement of Santa ‘(’ means he goes up a floor, and ‘)’ means he goes down on. Your job for part 1 is to take their long string of parentheses and find where Santa ends his journey.
For part 2, you are to use the same logic as before, but find when Santa first enters the basement. What is the position of the character that causes Santa to first enter the basement? Example )
means on the first position Santa enters the basement.
Another example ()())
Santa enters the basement on the fifth position.
My first attempt
Below is my first completion after it had been majorly refactored. I am fairly proud that the function names make sense, and overall it is fairly readable.
def check_basement(location, basement, number):
if location == -1 and not basement:
print('Entering basement after move:', number)
return True
else:
return basement
def get_direction(movement):
if movement == '(':
return 1
return -1
def track_santa(movements):
position = 0
moves = 0
has_entered_basement = False
for movement in movements:
moves += 1
position += get_direction(movement)
has_entered_basement = check_basement(position, has_entered_basement, moves)
return position
Not terrible for a newbie.
However, after learning about TDD and clean code, there is too much going on in these functions. They need to be simpler and ideally do only one thing.
Below is my first completion using TDD with refactoring as well of course. I will provide the tests I used to check my work as I continued solving the problem.
def check_floor(moves):
floor = 0
for move in moves:
floor += translate_move(move)
return floor
def translate_move(move):
if move == '(':
return 1
return -1
def check_basement(moves):
floor = 0
floors_moved = 0
for move in moves:
floor += translate_move(move)
floors_moved += 1
if is_in_basement(floor):
return floors_moved
def is_in_basement(floor):
return floor == -1
## Here are my tests
class Test_Day_1_Part_1(unittest.TestCase):
def test_no_moves(self):
moves = ''
floor = sut.check_floor(moves)
self.assertEqual(floor, 0)
def test_one_move(self):
moves = '('
floor = sut.check_floor(moves)
self.assertEqual(floor, 1)
def test_one_down(self):
moves = ')'
floor = sut.check_floor(moves)
self.assertEqual(floor, -1)
def test_triple(self):
moves = '((('
floor = sut.check_floor(moves)
self.assertEqual(floor, 3)
def test_sample_data(self):
moves = '))((((('
floor = sut.check_floor(moves)
self.assertEqual(floor, 3)
def test_moves_code(self):
floor = sut.check_floor(moves_code)
self.assertEqual(floor, 232)
class Test_Day_1_Part_2(unittest.TestCase):
def test_first_move_enters_basement(self):
moves = ')'
position = sut.check_basement(moves)
self.assertEqual(position, 1)
def test_third_move_enters_basement(self):
moves = '())'
position = sut.check_basement(moves)
self.assertEqual(position, 3)
def test_moves_code(self):
position = sut.check_basement(moves_code)
self.assertEqual(position, 1783)
Not too shabby, I did have to go back and refactor. But there was not too much to refactor in my code. Looks like TDD did what it was supposed to do.
I do see how my naming has gotten better, and I was able to get rid of some redundancies that existed before, and made the code slightly cleaner.
This was a very simple problem, so the solutions look extremely similar even though I deleted the code. I expect in future complex problems the code will change more drastically.
Catch y’all on the flip side.
Merl