Skip to main content
Fixed typo (and added Carcigenicate's moving .replace out of loop)
Source Link
AJNeufeld
  • 34k
  • 5
  • 39
  • 101

(And as Carcigenicate mentioned, move it out of the loop)

There are 0 lefts, 4 rights, so we need to move +0 right to yield a net +4 right; there is 1 up and 1 down, so we need to move +4 down to yield a newnet +4 down. We have 4 moves up for grabs, so they all need to be downs. There is no point checking for combinations including up, left and right moves!

There are 0 lefts, 4 rights, so we need to move +0 right to yield a net +4 right; there is 1 up and 1 down, so we need to move +4 down to yield a new +4 down. We have 4 moves up for grabs, so they all need to be downs. There is no point checking for combinations including up, left and right moves!

(And as Carcigenicate mentioned, move it out of the loop)

There are 0 lefts, 4 rights, so we need to move +0 right to yield a net +4 right; there is 1 up and 1 down, so we need to move +4 down to yield a net +4 down. We have 4 moves up for grabs, so they all need to be downs. There is no point checking for combinations including up, left and right moves!

Source Link
AJNeufeld
  • 34k
  • 5
  • 39
  • 101

Unnecessary Import

numpy is not required for this challenge. You are using none of its special capabilities. The following:

arry = np.array([[0]*5 for i in range(5)])

could easily and simply be replaced with

arry = [[0] * 5 for _ in range(5)]

Notice the throw-away _ variable being used for the unused loop comprehension variable. If you don't use it, don't name it.

StopIteration

It is rarely necessary to use try: ... except: ... to catch the StopIteration exception. You just need to use a loop structure.

    move_combinations = itertools.product(_map.keys(), repeat=q_count)

    while True:
        arry = np.array([[0]*5 for i in range(5)])
        arry[0][0] = 1  # start at 0, 0

        current_x = 0
        current_y = 0

        try:
            moves = next(move_combinations)
        except StopIteration:
            return None

        ...

could be written much more simply as:

    for move in itertools.product(_map.keys(), repeat=q_count):

        arry = [[0] * 5 for _ in range(5)]
        arry[0][0] = 1  # start at 0, 0

        current_x = 0
        current_y = 0

        ...

    return None

Variable Names

_map is an odd local variable name. A single leading underscore is used to signify private/protected object members. Local variables are never visible in an outer scope, so do not need to be flagged as "private". A trailing underscore is usually used to avoid name collisions, in which case the variable should be named map_. But confusion may be better avoided by naming the variable not with its type but with its role; this dictionary mapping contains directions, so it may be better named directions.

arry is also a poor name. visited would be a better choice.

True / False

You are storing 0 and 1 in your arry matrix, and then testing for the truthiness of arry[current_x][current_y]. It would be clearer to store False and True in these.

visited = [[False] * 5 for _ in range(5)]

...

    ...
    elif visited[current_x][current_y]:
        break
    else:
        visited[current_x][current_y] = True

Unnecessary Slicing

Neither s.replace(...) nor s[::].replace(...) will modify the original s contents. There is no need to use slicing to create a copy prior to the replace operation:

        ss = s.replace("?", "{}").format(*moves)

Magic Numbers

The code is littered with 4's and 5's. What if you want to change this to work with a 5x6 or 4x7 grid? You need to correctly change a lot of numbers. Perhaps you should pass in the size of the grid, instead of using hard-coded values:

def calcpath(s, rows=5, columns=5):

    start = (0, 0)
    end = (columns - 1, rows - 1)

    ...

       visited = [[False] * columns for _ in range(rows)]
       current_x, current_y = start

       ...

           if current_x < 0 or current_x >= columns:
               break
           ...

       if (current_x, current_y) == end:
           ...

    ...

Optimizations

There should be several obvious checks you can make. For the 5x5 grid case:

  • len(s) must be even
  • len(s) must be >= 8
  • len(s) must be < 25

Moreover, the directed counts of the left-right and up-down moves will produce a reduced search space.

With s = "???rrurdr?":

>>> print(*(sum(c == dir for c in s) for dir in "lrud?"))
0 4 1 1 4

There are 0 lefts, 4 rights, so we need to move +0 right to yield a net +4 right; there is 1 up and 1 down, so we need to move +4 down to yield a new +4 down. We have 4 moves up for grabs, so they all need to be downs. There is no point checking for combinations including up, left and right moves!

You can expand the logic of this for other cases: "drdr??rrddd?" has a net 4 right, 5 down, so the additional moves must include 1 up. Then, it could add either a left/right pair, or an up/down pair, so you only need to check permutations of "ulr" and "uud", for a total of 12 possibilities, instead of your \$4^3\$ search space.