Originally posted on writtenby.adriengiboire.com
A fellow developer, which is an employee of a client of mine, published this link on the client's Slack. When it's not for an interview, I am always keen to give a shot at any test even though lately I've lacked discipline and spent way too much time on Netfouix.
Tonight, I felt like it and since I've been working with JavaScript almost exclusively for a few months, I wanted to go back to ruby a little.
The Challenge
You start with a file containing 0+ journeys. A journey is made of 3 lines looking like this:
0 3 W
LLFFFLFLFL
2 4 S
The first line is the current position of the Robot.
The second line is the list of movements of the Robot.
The third line is where the Robot is supposed to be at the end of its journey.
Position
A position is made of the coordinates (x
, y
) and the direction the Robot is facing toward (East, South, West, North).
Moves
A move is either the Robot moving Forward, or turning on itself either Left or Right.
The implementation
Build the data
The first thing I went through was building the data and cleaning the inputs.
Remember, you are getting the journeys from a file:
inputs = []
File.open('journeys.txt', 'r') do |file|
file.each_line do |line|
inputs << line.gsub("\n", "")
end
end
As you can see, we already started cleaning the data. A catch here is the carriage "\n"
. If you leave it as it is, you will never reach equality because what you will see looks like 1 2 E
but what the computer reads is actually 1 2 E\n
so when you compared what you get from the file and your result, you will always get a false
return.
Then, for ease of usability, we want to delete blank lines:
inputs.delete("")
Since Array#delete
transforms the original array (sic), we don't have to assign the result.
The TDD-ish approach
I decided to write the final code the way I intended it to work. At first, it would fail since nothing was implemented but then, incrementally implement the code.
inputs.each_slice(3).each do |slice|
wallee = Robot.new(slice[0])
wallee.move(slice[1])
puts "Actual: #{wallee.position}"
puts "Expected: #{slice[2]}"
puts wallee.position == slice[2]
end
Nothing serious here. Notice just the use of Array#each_slice
to facilitate the job.
I love functional but when writing ruby I am used to OOP and as you'll see, I did not change this habit :)
The Robot directions
Here is the skeleton of what we've seen so far. This is the minimum list of functions we will have to implement to make our test pass.
class Robot
def initialize position
end
def move moves
end
def move_right
end
def move_left
end
def move_forward
end
def position
end
end
Since the position is made of coordinates and a direction, we have to deconstruct the data in order to exploit it later easily.
def initialize position
@coordinates = position.split(' ')[0, 2].map(&:to_i)
@direction = position.split(' ')[2]
end
Same here, we have a trick with the types. Coordinates are a tuple of integers. If we don't make the conversion, we will have issues when modifying data as you'll notice later.
Robot, move!
Next, we want to work on the basic moves.
Learning the moves
The simplest are the directions. I went with a basic array of strings that contains the 4 possible directions, ordered. Then, we can simulate the rotation by calculating the new index according to the new direction.
DIRECTIONS = ['N', 'E', 'S', 'W']
def move_right
index = (DIRECTIONS.find_index(@direction) + 1) % DIRECTIONS.count
@direction = DIRECTIONS[index]
end
def move_left
index = (DIRECTIONS.find_index(@direction) - 1) % DIRECTIONS.count
@direction = DIRECTIONS[index]
end
Then comes the forward move. This is a little bit more complex. What I mean is I did not find an elegant way of doing. I'm opened to suggestions!
Basically, you change by +/- 1 either x
or y
according the direction the Robot is pointing toward when moving forward.
def move_forward
case @direction
when 'N'
@coordinates = [@coordinates[0], @coordinates[1] + 1]
when 'E'
@coordinates = [@coordinates[0] + 1, @coordinates[1]]
when 'S'
@coordinates = [@coordinates[0], @coordinates[1] - 1]
when 'W'
@coordinates = [@coordinates[0] - 1, @coordinates[1]]
end
end
Nothing difficult but a lot more verbose than what we've seen so far.
Execute the moves!
Now that our Robot knows how to move, let's actually move. No big deal coming here. A hash containing the different moves as keys, associated to the method that knows how to execute the given move.
MOVES = {
L: 'move_left',
R: 'move_right',
F: 'move_forward'
}
Then, all that's left is the method move
which calls the method with the help of the MOVES
hash.
def move moves
moves.split('').each do |move|
self.send(MOVES[move.to_sym])
end
end
The arrival
Now that we know how to parse the journeys, we just have to reconstruct the calculated position to be able to compare it to the journeys data.
def position
[@coordinates.flatten, @direction].join(' ')
end
And there we are.
The ending word
This was a fun test. Since it's been a while since I have been writing some ruby, I had to check the doc for simple stuff. It took about 30 minutes. Nothing crazy, I'm a normal human being. I'm not an alien :P
Hope you enjoyed the journey! If you have suggestion to make it better, I am willing to learn as always!
Cheers!
Top comments (0)