Puzzle #2

Β 9th July 2023 at 11:32am

First part

This time, I was much more organized from the get-go. Of course, I haven't been coding for more than a month, so I suppose I'm a little rusty and clumsy.

Looking at the problem, it seemed quite obvious just use a matrix representation for the map – I didn't feel like trying to find another, clever way to model what is already quite well modeled. However, there are some limitations to the matrix movement, and I am quite confident that I got a practical and easy solution for that.

M = [list(range(1 + i * 3, 4 + i * 3)) for i in range(3)]

I also feel there are two very distinct steps here:

1. get a number as a result of reading a line; 2. get a sequence of number as a result of reading all lines.

So this could be modeled with a double for loop; however, I'm trying to do it functionally.

I devised two functions: get_nbr_from_line, and iterate_over_all_lines. The latter will call the first over all lines, and then assemble the passcode by gathering all the numbers.

DIRS = {
        'U': lambda x, y: (x, max(0, y - 1)),
        'D': lambda x, y: (x, min(2, y + 1)),
        'L': lambda x, y: (max(0, x - 1), y),
        'R': lambda x, y: (min(2, x + 1), y),
        }

def get_nbr_from_line(
        x: int,
        y: int,
        line: str
        ) -> [int, int, int]:
    if not line:
        return [x, y, M[y][x]]
    else:
        direction = line[0]
        new_x, new_y = DIRS[direction](x, y)
        return get_nbr_from_line(
                new_x, 
                new_y,
                line[1:]
                )

(I'm not quite sure about this formatting for readability – in fact, I only know about it because of a Python linter, ruff. Having so many distinct lines helps with git logs. This is probably overkill for such leisury programming, but I suppose it doesn't hurt).

I'm happy about the DIRS variable: it feels readable and clever. SinceΒ the coordinates can only go from 0 to 2 (including), the min and max call is a nice trick to always keep it bounded. The function is working with all examples, which is nice. All that's needed now is an encompassing function that would assemble all the numbers together: I'm thinking of a reduce call that converts each resulting int into a str, and concatenates the final result.

That is for tomorrow, though – it is already quite late.


I finished the second function, iterate_over_all_lines. There was a bit of a struggle with making it purely functional, in the sense that I will not define any variables in its body, but I suppose I'm not being too rigorous in my approach. Since get_nbr_from_line is yielding three different results (a new x position, a new y position, and the resulting number from the matrix), I'm probably failing some fundamental functional rule like having really modular functions that one do one thing. Maybe I'll further inquiry into this.

Anyway, here's the code:

def iterate_over_all_lines(
        nbrs: str,
        lines: list[str],
        x_pos: int,
        y_pos: int
        ) -> str:
    if not lines:
        return nbrs
    else:
        new_x, new_y, nbr = get_nbr_from_line(
                x_pos,
                y_pos,
                lines[0]
                )
        return iterate_over_all_lines(
                nbrs + str(nbr),
                lines[1::],
                new_x,
                new_y
                )

As a bonus for patient readers, here's my current workplace:


Second part

The second part is a little crazy. Instead of a normal keypad matrix like

1 2 3
4 5 6
7 8 9

we get the really unusual disposition of

    1
  2 3 4
5 6 7 8 9
  A B C
    D

and due to how I structured the code, I just need to rework, or create an entirely different one, get_nbr_from_line.

The new matrix is M = ['00100','02340', '56789', '0ABC0', '00D00']. The 0 char marks an invalid spot. We will only navigate to a new place in the matrix if the new location spot is not 0.

DIRS = {
        'U': lambda x, y: (x, max(0, y - 1)),
        'D': lambda x, y: (x, min(4, y + 1)),
        'L': lambda x, y: (max(0, x - 1), y),
        'R': lambda x, y: (min(4, x + 1), y),
        }

def get_nbr_from_line(
        x: int,
        y: int,
        line: str
        ) -> [int, int, int]:
    if not line:
        return [x, y, M[y][x]]
    else:
        direction = line[0]
        new_x, new_y = DIRS[direction](x, y)
        if M[new_y][new_x] != '0':
            return get_nbr_from_line(
                    new_x, 
                    new_y,
                    line[1:]
                    )
        else:
            return get_nbr_from_line(
                    x, 
                    y,
                    line[1:]
                    )

This works, but, again, I feel the code could be improved somehow.