if sea_player[x1 + 1][y1] != 'S' and sea_player[x1][y1 + 1] != 'S' and sea_player[x1 + 1][y1 + 1] != 'S' and sea_player[x1 - 1][y1 ] != 'S' and sea_player[x1][y1 - 1] != 'S' and sea_player[x1 - 1][y1 - 1] != 'S' and sea_player[x1 + 1][y1 - 1] != 'S' and sea_player[x1 - 1][y1 + 1] != 'S' and sea_player[x1 - 1][y1 - 1]: 

Is it possible to shorten this piece of code?

ps I write a sea battle on python using the command line. this condition checks whether the unicellular ship borders with other ships on the field (list of lists)

  • one
    You can use numpy.array , then the test is reduced to the presence of 'S' in the selected area of ​​a two-dimensional array. - mkkik
  • one
    And what about writing the f-th closure that checks the condition you need, and in if it cause it? - Goncharov Alexander
  • @mkkik, in my opinion, this will be the most optimal solution ... And how would you implement the "selection of the surrounding matrix"? - MaxU
  • one
    @MaxU, array[nrow-1 : nrow+2, ncol-1 : ncol+2] , where nrow, ncol is the desired cell. Slices, of course, receive through lambda. - mkkik
  • @mkkik, do not want to issue a solution? IMO, it will be clearly faster and more elegant than other solutions ... - MaxU

4 answers 4

The list of offsets for neighboring cells is better to generate than to write with your hands:

 adjacent_vector = [(x,y) for x in range(-1,2) for y in range(-1,2) if not (x == 0 and y == 0)] 

Then definitely do not miss or duplicate any direction. By the way, you have the same sea_player[x1 - 1][y1 - 1] != 'S' cell checked twice sea_player[x1 - 1][y1 - 1] != 'S' .

The list of offsets can be created once for the duration of the program.

Then, using the generator and any built-in function, you can check for the presence of an 'S' in the cells. Approximate code (does not take into account the overrun of your field):

 adjacent_vector = [(x,y) for x in range(-1,2) for y in range(-1,2) if not (x == 0 and y == 0)] adjacent_cells = (sea_player[x1 + a[0]][y1 + a[1]] == 'S' for a in adjacent_vector) if any(adjacent_cells): print 'found' 

The any function will stop working as soon as the first cell with 'S' is found, so as not to waste time on the remaining cells (and the generator instead of the list here for the same reason).

    You can imagine a field like this:

     0 0 S 0 0 0 0 S 0 

    Since your condition is one and large, this square can be represented not as a two-dimensional, but as a one-dimensional array: 0 0 S 0 0 0 0 S 0 . If the map is a two-dimensional map array and the x, y coordinates are the center of the square to be checked, then you can get a one-dimensional cut operation. In this case, the field is 5x5 and check the point right in the center.

     import itertools my_map = [[0, 0, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0]] y = 2 x = 2 current = my_map[x][y] my_map[x][y] = 'CURRENT' arr = itertools.chain(*[row[max(x - 1, 0): x + 2] for row in map[max(y - 1, 0): y + 2]]) print(1 in arr) my_map[x][y] = current 

    An extra array can be not initialized. Replacing the current cell is necessary to exclude the cell from consideration without complicated formulas or conditions.

    Here several operators were used:

    1) The slice operator ":" (slice). Example of use:

     l = ['a', 'b', 'c', 'd', 'e', 'f'] print(l[2:4]) >>> ['c', 'd'] 

    2) Unpacking operator of the collection "*" ( doc ):

     l = ['a', 'b', 'c', 'd', 'e', 'f'] print(*l) abcdef 

    The essence of the operator is that the array l turned into 6 different values ​​(there was 1 list - it became 6 elements without a list).

    3) The chain function from the itertools module - sticks arrays to one ( doc ):

     l = itertools.chain(['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'i', 'o']) print(list(l)) >>> ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'i', 'o'] 

    4) Operator in, which checks whether the item is in the collection or not ( doc ):

     l = ['a', 'b', 'c', 'd', 'e', 'f'] print('a' in l) print('z' in l) >>> True >>> False 

      The first solution is to put the check into a separate function, while the name of the function will self-document the code indicating that it is performing.

      The second is just to format it normally:

       if sea_player[x1 + 1][y1 ] != 'S' and sea_player[x1 ][y1 + 1] != 'S' and sea_player[x1 + 1][y1 + 1] != 'S' and sea_player[x1 - 1][y1 ] != 'S' and sea_player[x1 ][y1 - 1] != 'S' and sea_player[x1 - 1][y1 - 1] != 'S' and sea_player[x1 + 1][y1 - 1] != 'S' and sea_player[x1 - 1][y1 + 1] != 'S' and sea_player[x1 - 1][y1 - 1] != 'S' : 

      You can come up with other more aggravated ways that you apparently relied on by asking a question. But they only unnecessarily complicate the code.

      • But you can not do two for-s for example to do this? The chances are lower. - pavel
      • It’s not a fact at all, and even the opposite, simply because there will be more letters. In fact, the emphasis should not be on ease of writing, but on ease of understanding. Your original version is 100 times easier to understand because it is “static”, in contrast to the “dynamic” cycles that need to be interpreted in your head. - Cerbo
      • If such an if is found in the code more than once, put it into a function with code formatted as in the answer. If one time - you can leave it as it is (preferably in formatted form) - andy.37

      It is possible to go through this way (with checking for out of range):

       for i in range(x1 - 1, x1 + 2): for j in range(y1 - 1, y1 + 2): if ((i < 0) or (j < 0)) or (i >= len(sea_player) or j >= len(sea_player)): if (i == x1) and (j == y1): continue if (sea_player[i][j] == 'S'): print('alarm') 
      • And exactly one should be? - gil9red
      • @ gil9red should be one and - andy.37