0

Context

The goal of the script is to simulate a chain reaction of explosions given a map where any positive integer is a bomb, and its value is its magnitude. If the explosion hits another bomb, it causes that bomb to explode.

Problem

In my code I repeatedly use the try - except blocks to verify that the coordinate is on the map. I want to know how I could avoid doing that.

Code

def chain_reaction(map, coord):

    explosion = map[coord[0]][coord[1]]
    map[coord[0]][coord[1]] = 0

    for i in range(1, explosion + 1):
        try:
            if map[coord[0] + i][coord[1]] != 0:
                chain_reaction(map, (coord[0] + i, coord[1]))
        except IndexError:
            pass

        try:
            if map[coord[0] - i][coord[1]] != 0:
                chain_reaction(map, (coord[0] - i, coord[1]))
        except IndexError:
            pass

        try:
            if map[coord[0]][coord[1] + i] != 0:
                chain_reaction(map, (coord[0],coord[1] + i))
        except IndexError:
            pass

        try:
            if map[coord[0]][coord[1] - i] != 0:
                chain_reaction(map, (coord[0], coord[1] - i))
        except IndexError:
            pass

        try:
            map[coord[0] + i][coord[1]], map[coord[0] - i][coord[1]] = 0, 0
            map[coord[0]][coord[1] + i], map[coord[0]][coord[1] - i] = 0, 0
        except IndexError:
            pass

    return map

map = [[0,1,0,2], [3,0,1,1], [0,1,0,1], [0,0,2,0], [1,0,0,0]]

chain_reaction(map, (3,2))

What would be the optimal / best way to solve this issue?

thomaoc
  • 94
  • 10
  • Slight nit-pick: you should avoid using keywords like `map` as variables since you might actually want to use them for their original purpose in the body of your function. A better name might have been `matrix` or `grid`. – rmorshea Dec 29 '20 at 20:18

3 Answers3

2

Exception handling should be reserved to... well... exceptions. If your regular control flow relies on exceptions, chances are that you are doing something wrong*.

Instead of accessing a[x] and checking for an exception, you could also first check if x < len(a), i.e., if x is a valid value. Try to rewrite your code from there.

There are ways to look into all four directions as part of a loop, but I don't think that those would necessarily make the code easier to read.

*) Once you start looking beyond this relatively simple code, it becomes somewhat more complex. Python is different from many other languages in that it actively uses exceptions for flow control. For now, I would try to stay away from them and revisit the link once you are more familiar with the language.

mrks
  • 8,033
  • 1
  • 33
  • 62
2

Refactor the common code to a helper function. Note that map is the name of a built-in. That should be changed as well. Also, isn't the final try unnecessary? chain_reaction would already have set those locations to zero...

def check(map, coord):
    try:
        if map[coord[0]][coord[1]] != 0:
            chain_reaction(map, (coord[0], coord[1]))
    except IndexError:
        pass

def chain_reaction(map, coord):

    explosion = map[coord[0]][coord[1]]
    map[coord[0]][coord[1]] = 0

    for i in range(1, explosion + 1):
        check(map, (coord[0] + i, coord[1]))
        check(map, (coord[0] - i, coord[1]))
        check(map, (coord[0],coord[1] + i))
        check(map, (coord[0], coord[1] - i))

        #try:
        #    map[coord[0] + i][coord[1]], map[coord[0] - i][coord[1]] = 0, 0
        #    map[coord[0]][coord[1] + i], map[coord[0]][coord[1] - i] = 0, 0
        #except IndexError:
        #    pass

    return map

map = [[0,1,0,2], [3,0,1,1], [0,1,0,1], [0,0,2,0], [1,0,0,0]]

chain_reaction(map, (3,2))
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
0

I think there's three main corrections to make here:

  1. As mentioned in mrks's answer you don't need to use try/except statements here because you can determine whether a coordinate is in the domain of the given map before trying to access a value.

  2. You can better organize the logical steps of the process into separate functions.

  3. Try to think of ways to generalize repetitive code (i.e. DRY).

Here's my re-imagining. I'll explain what I did below:

UNIT_NEIGHBOR_OFFSETS = [
    (0, 1),
    (0, -1),
    (-1, 0),
    (1, 0),
]


def chain_reaction(map, coord):
    map_size = (len(map), len(map[0]))

    # find explosion size
    explosion = map[coord[0]][coord[1]]
    # reset explosion
    map[coord[0]][coord[1]] = 0
    

    for offset in explosion_neighbor_offsets(explosion):
        neighbor_x = coord[0] + offset[0]
        neighbor_y = coord[1] + offset[1]
        if (
            # check if X coord is in the map's domain
            neighbor_x < map_size[0]
            # check if Y coord is in the map's domain
            and neighbor_y < map_size[1]
            # check if there is explosion at neighbor coord
            and map[neighbor_x][neighbor_y] > 0
        ):
            chain_reaction(map, (neighbor_x, neighbor_y))

    return map


def explosion_neighbor_offsets(explosion_size):
    return [
        offset
        for exp_i in range(explosion_size)
        for offset in scaled_neighbor_offsets(exp_i + 1)
    ]


def scaled_neighbor_offsets(scale):
    return [
        (offset[0] * scale, offset[1] * scale)
        for offset in UNIT_NEIGHBOR_OFFSETS
    ]


map = [[0,1,0,2], [3,0,1,1], [0,1,0,1], [0,0,2,0], [1,0,0,0]]

chain_reaction(map, (3,2))

Here's the core changes I made:

  1. Instead of try/except you can use an if statement to check if a coordinate is in the domain of the map: x_coord < len(map) and y_coord < len(map[0])

  2. I broke out the process of finding the coordinates neighboring an explosion into functions. Note how when looking at chain_reaction you now can skim over the rules for finding explosion neighbors because we hid that logic inside a descriptively named function explosion_neighbor_offsets. Thus the reader can move on to understanding the recursive nature chain_reaction more quickly.

  3. Instead enumerating in code the way to find the explosion neighbors in the four cardinal directions, I constructed a way that I could represent those directions in the form of data (i.e. UNIT_NEIGHBOR_OFFSETS). Doing this is extremely useful. Consider what it would take to modify the original code if your professor asked you to make it so explosions impacted neighbors on the diagonal. In my refactored version though, all you have to do is add the following vectors to UNIT_NEIGHBOR_OFFSETS: [(1, 1), (1, -1), (-1, 1), (-1, -1)]

rmorshea
  • 832
  • 1
  • 7
  • 25