Mars Rovers II: The Design
This is part of the Mars Rovers series.
The Interpretation
Two questions came to mind after reading the Problem Statement:
- Can we ignore rover collisions?
- Do we need that (5,5) point?
A literal first reading suggested “yes” and “no”, respectively. Why?
- The two paths don’t cross (you can mentally move them before writing code).
- Both paths are within the 5×5 rectangle.
Yet I saw at least two possible intentions behind the Problem Statement. One is:
Hey, don’t worry about modeling rover collisions, and don’t worry about validating their paths:
- rovers move one at a time,
- their paths don’t cross,
- and the given commands have already considered the rectangle’s bounds.
In other words: don’t overcomplicate your solution; ignore these non-problems; just make sure input → output.
Or maybe the intention was:
Hey, here’re the conditions, and the input and output format, and a sample for you to test. It’s up to you to figure out what’s important.
The issue here is that the “test input” and its “expected output” are well-behaved: within bounds and collision-free. But does this sample reflect the quality of all future input we’d receive? Or has it been made well-behaved only for testing — and woe betide those who take this for granted?
Asking
Now, if this were a “real situation”, the best thing to do would be to ask. As in:
- Who created those paths? Can I talk to them directly?
- We should make sure that paths are collision-free and within bounds.
Is anyone already doing that? Or is that verification up to me? - If it’s up to me, how should we deal with any mistakes, if detected?
In what format would they like to receive the information about problematic paths? - Etc.
All that would come before writing any code whatsoever.
The Approach
Since this isn’t a “real situation”, there’re no actual rover input–generating people we could go ask these questions. So we don’t know the answers: input quality is unclear.
I decided to write three different solutions:
- One
- simplest, assumes that all input is good, and valid, and rovers are dimensionless-but-oriented particles that move by discrete jumps on an infinite smooth grid.
- Two
- like “one”, but also makes sure that every rover position is within the plateau’s bounds; still ignores collisions.
- Three
- like “two”, but also makes sure that there’re no collisions: a rover’s path must not contain the position of another rover.
Let’s talk about each.
Solution 1: simplest — no checks for bounds or collision
For this solution, the first line (in the example case, “5 5”) is ignored.
That’s the “don’t overcomplicate your solution; ignore these non-problems; just make sure input → output” one.
To solve this, we just keep moving and rotating the thing until it runs out of commands — and then we return position and orientation.
Functional programming approach:
- Process the input data into a more suitable format:
- a vector whose first items are the plateau’s boundaries — even if (for now) unused
- remaining items: initial map (aka: hash map, hash table) of each rover’s data
- include in the map:
- the given coordinates (
:x,:y) and heading (:h) - sequential rover
:id(1, 2, 3...) and string of pending commands:cs(“MMRMRMRL...”)
- the given coordinates (
- include in the map:
- a vector whose first items are the plateau’s boundaries — even if (for now) unused
- Write a function that generates the next map by:
- popping the first command from the string, and then
- modifiying
:x,:y,:haccordingly
- popping the first command from the string, and then
- For each rover, iterate the function over its map until the command string is empty, and store all these maps sequentially.
- For each rover, extract from the last map the values of
:x,:y,:hto generate the rover’s output string line. - Join these string lines with
\nfor the final output.
All we need are functions which, when composed in a certain order, produce the final string when given the initial string.
Solution 2: verifies that each map is internally consistent
It builds on the previous. The difference is that I want to sound an alarm if any map (from init to last) of any rover is invalid.
In particular, for any one map, the values of :x and :y must be:
- integer: it’s a “grid to simplify navigation” and movement “means move forward one grid point”.
- non-negative: the lower-left coordinate of the grid is “0,0”.
- not larger than the values given by the upper-right coordinates.
So we check every map and throw an error message when something fails.
Notes
This validation can be made in isolation, map by map.
That is, once we have each rover’s sequence of maps, each map’s validation does not depend on any other map. Each map has all the information necessary to verify those bounds. A map’s
:xor:ybeing invalid is sufficient to reject that map.
I’ll assume that producing maps is cheap.
So I’ll first produce all maps as if they were all valid and going to be used — and then verify if any one of them is, in isolation, invalid.
If producing maps were found to be expensive for some reason, the validation could be done early, and the map-generating process interrupted until that was fixed.
But not only this sort of calculation should be extremely cheap, mapping them all also has the advantage of possibly identifying many problems at once, so that we can also tell rover-input–producing folks about all these problems at once, so that they can fix them all at once, too. The alternative would be to send one problem, then they fix it, then they send it back, and then “oh, there’s another problem here”, and then they fix it, and...
Might as well validate the other variables.
Now, we’re complicating our first solution to test for this because here, we assume, the rover-input–generating folks haven’t triple-checked their data before sending us. But if the rover may wander off-grid even when the data includes grid limits, what else may be wrong there? Maybe they sent us some bogus command (“Z”)? Or a nonexistent heading (“Q”)? We might as well validate those, too.
Might as well validate all maps.
In theory, those extra checks (heading and command) could happen just in the first map, because if all our functions are good they won’t introduce errors in subsequent maps. But I’ll assume that this check is cheap enough, and therefore go ahead and test every single map: it could catch an error somewhere — of the program itself, for example, rather than from the received input data.
All right, but there’s still something we aren’t checking here.
Solution 3: verifies that no moving rover would collide with a stationary rover
Each rover will be finished sequentially, which means that the second rover won’t start to move until the first one has finished moving.
In a second reading of the Problem Statement, the sentence above suddenly seemed to suggest something I might have overlooked in my first reading.
The sentence seems irrelevant for the “test input”: the two rovers’ paths don’t cross, so it doesn’t matter when each of them starts. But then why is that sentence there?
In a “real situation”, you just ask. (“Is anyone already making sure there’ll be no collision?”) If we can’t ask, we have to guess.
- One guess is: unnecessary information just to confuse you: realize that it’s not important and move on.
- Another guess is: although the given input/output sample is well-behaved, actual input might not be. If rover-input–generating folks could have messed up by sending rovers outside the plateau, they might as well have messed up by sending rovers crashing into each other. So we check that, too.
The fact that they move sequentially simplifies things. If they didn’t, we’d have to “align” all rovers’ maps based on their times of departure. Worse, we’d have to detect possible collisions not only at the grid intersections, but also at the edges. Consider that these two same-speed rovers would collide at point 2,4½ — but you wouldn’t know that if you only checked their map snapshots at times 1 and 2:
| R1 | R2 | |
|---|---|---|
| time 1 | 2 4 E | 2 5 W |
| time 2 | 2 5 E | 2 4 W |
So we don’t need to deal with that. Since they move sequentially, there’s only one rover moving at any time; all other rovers are stationary at some grid intersection.
But where?
Where are the rovers?
Suppose we have seven rovers there. Rover #3 is about to move. What positions do we need?
- Rovers #1 and #2 have already moved, so they are at their respective final positions. We have to look at their last map.
- Rovers #4 to #7 haven’t moved yet, so they are at their respective initial positions. We have to look at their initial map.
- Rover #3 is about to move and may collide with any one of these rovers along the way. We have to look at all of its maps.
Let’s call “moving-rover” a rover that is about to start moving.
And let’s say that a moving-rover is collision-free if none of the final positions of the previous rovers and none of the initial positions of the following rovers are on its path.
If all moving rovers are collision-free, we have no rover collisions.
For Solution 2, a map’s validation does not depend on any other map. But here, to declare a moving-rover collision-free, we need all its maps, plus maps of all other rovers, some of which (the previous) must have had their paths calculated (for us to check their respective last maps). It’s a harder problem: the whole thing is entangled, and the lists of previous positions and following positions change for each rover.
One implication is that we cannot use map (the function): map only knows about the thing it’s currently looking at; it doesn’t know where it’s been or where it’s going.
An alternative is to deploy map-indexed, and use the index to reconstruct the left and right parts of the sequence. Something like this:
(let [x '[1 2 3 4 5 6 7]] (map-indexed (fn [i it] (list (take i x) it (drop (inc i) x))) x)) => ((() 1 (2 3 4 5 6 7)) ((1) 2 (3 4 5 6 7)) ((1 2) 3 (4 5 6 7)) ((1 2 3) 4 (5 6 7)) ((1 2 3 4) 5 (6 7)) ((1 2 3 4 5) 6 (7)) ((1 2 3 4 5 6) 7 ()))
so that an assert-no-collisions function would:
- for each number (rover) on the left triangle, check its last map;
- for each number (rover) on the right triangle, check its first map;
- for the middle number, check its whole path;
and then check if there’s an intersection.
Then we’d have something like this:
(defn ensure-no-collisions [coll] (let [partition #(list (take % coll) %2 (drop (inc %) coll)) partitions (map-indexed partition coll)] (doseq [[l n r] partitions] (assert-no-collisions l n r)) coll))
So, yes, we could use map-indexed — but I thought this problem was begging for something else: zippers. And Clojure has those. So zipper it was.
Although they’re particularly useful for “non-destructively updating” a data structure, my idea here was to use them for running that assertion at each element. Why? Because, conveniently, the left and right lists are already given by the zipper. In the end, the solution size was about the same as the above.
How many rovers again?
Two other things crossed my mind.
First, could it be that the rover entries did not map one-to-one to individual rovers? After all, we could imagine a case where we have two rovers and six entries: each rover alternates rovering, and each rover’s next initial position and heading would match that of its last map. Interleaved rovering. Think about it: it would be implicit from the initial positions and paths. And it would complicate things, because we’d have to exclude these not-really-collisions, and subtract rovers.
But this removes the ambiguity:
Each rover has two lines of input.
Second, could it be that we always have only two rovers? The solutions could then be a bit simpler. For example, to verify collision, we’d only need to check whether the first rover’s final position is in the second rover’s path. No zipping or map-indexing or partitioning needed.
But the first sentence removes the ambiguity:
A squad of robotic rovers…
A “squad” could be, but not necessarily is, just “two”. In fact, it suggests a good half a dozen or more.
So n rovers it is — rovers that could run off-grid, or run into each other.
The Implementations
My first solution to the problem I wrote in Clojure, from scratch. Having finished that, I rewrote it in Emacs Lisp, and then in Bash.
I enjoyed them all — but if I had to pick one, I enjoyed Clojure best.
On the one hand, writing the first solution of anything is harder, because at that point you have just abstractions and ideas — which you have yet to translate into functions that produce results —, whereas when you rewrite the same solution in a different language, you can then get a leg up from these functions. The whole scaffolding is already there. And if these two happen to be two Lisp dialects, as was the case, it’s possible to keep an (almost) exact mapping from one to the other at the level of functions. This means you can work on translating each function, one by one, ignoring context, and in the end it’ll look idiomatic and just work. From this angle, comparatively speaking, Clojure was “harder” than Elisp because of a difference of starting points: an empty buffer versus a bunch of defn to modify.
On the other hand, I find Clojure to be more straightforward and less noisy than Emacs Lisp. Try seeing their implementations side-by-side. (A deeper comparison between the languages is a whole topic in itself, for another day.)
And Bash... well. As much fluency as I have on it, and as much as I can squeeze it to do higher-level FP stuff, some things are just not as ergonomic as when working with Lisps. Nesting can be particularly nasty. And the cleanest Bash will still have a lot of unavoidable quoting and dollars and braces and redirections, and will therefore look noisier. Yet, a few things here and there felt surprisingly as simple (if not simpler) than the Lisps. Go figure.
Enough talking. Let’s see some code.
See next
Mars Rovers III: The Solutions — Clojure
📆 2026-W16-6📆 2026-04-18