Python program to calculate what pawns are safe In chess there are eight rows numbered from 1 to 8 and 8 columns a-h. Pawns which are on the lowest row are always in danger to be captured. The objective of the program is to calculate which pawns are saved.

Given the coordinates of seven pawns return the number of pawns which are safe.

For example:


safe_pawns({"b4", "d4", "f4", "c3", "e3", "g5", "d2"}) == 6
safe_pawns({"b4", "c4", "d4", "e4", "f4", "g4", "e5"}) == 1
safe_pawns({"b4", "c4", "d4", "e4", "f4", "g4", "e3"}) == 2




The code:

def safe_pawns(pawns: set) -&gt; int:
    row_numbers = []
    list_of_pawns = list(pawns)
    letters_string = "abcdefgh"
    safe_pawns_list = []

    #check if there are neighbors which protect you
    for pawn in list_of_pawns:
        if pawn[0] == "a":
            if letters_string[1] + str(int(pawn[1])-1) in list_of_pawns:
                safe_pawns_list.append(pawn)
        elif pawn[0] == "h":
            if letters_string[6] + str(int(pawn[1])-1) in list_of_pawns:
                safe_pawns_list.append(pawn)
        else:
            if ((letters_string[letters_string.index(pawn[0]) + 1] + str(int(pawn[1]) - 1))
            in list_of_pawns):
                 safe_pawns_list.append(pawn)
            elif (letters_string[letters_string.index(pawn[0]) - 1] + str(int(pawn[1]) - 1)
            in list_of_pawns):
                safe_pawns_list.append(pawn)

    #delete the pawns which belong to the lowest row number
    for pawn in list_of_pawns:
        row_numbers.append(pawn[1])

    lowest_row_indexes = []

    for i, row_number in enumerate(row_numbers):
        if row_number == min(row_numbers):
            lowest_row_indexes.append(i)

    for i, lowest_row_index in enumerate(lowest_row_indexes):
        del list_of_pawns[lowest_row_index - i]
        del row_numbers[lowest_row_index - i]

    return len(safe_pawns_list)


How could this be improved?
Playing with the code a bit, give insight in how we can optimize your code.

Unused computation

Note that the following part has no effect on the return value and can be removed:

    #delete the pawns which belong to the lowest row number
    for pawn in list_of_pawns:
        row_numbers.append(pawn[1])

    lowest_row_indexes = []

    for i, row_number in enumerate(row_numbers):
        if row_number == min(row_numbers):
            lowest_row_indexes.append(i)

    for i, lowest_row_index in enumerate(lowest_row_indexes):
        del list_of_pawns[lowest_row_index - i]
        del row_numbers[lowest_row_index - i]


Integer and String conversions

The lookup in the loop is expensive, as each time we convert strings to integers and vice versa. We make a mapping to integer once, prior to the loop.

def safe_pawns(pawns: set) -&gt; int:
    letters_string = "abcdefgh"
    safe_pawns_list = []

    pawns = list(map(lambda x: (letters_string.index(x[0]), int(x[1])), pawns))

    # check if there are neighbors which protect you
    for pawn in pawns:
        if pawn[0] == 0:
            if (letters_string[1], pawn[1] - 1) in pawns:
                safe_pawns_list.append(pawn)
        elif pawn[0] == 8:
            if (letters_string[6], pawn[1] - 1) in pawns:
                safe_pawns_list.append(pawn)
        else:
            if (pawn[0] + 1, pawn[1] - 1) in pawns:
                safe_pawns_list.append(pawn)
            elif (pawn[0] - 1, pawn[1] - 1) in pawns:
                safe_pawns_list.append(pawn)

    return len(safe_pawns_list)


Using Numpy

The following is less fast (at least for these small amounts of pawns), however the logic is arguably more clear.

def intersect2d(a1, a2):
    # https://stackoverflow.com/questions/8317022/get-intersecting-rows-across-two-2d-numpy-arrays
    rows, ncols = a1.shape
    dtype = {'names': ['f{}'.format(i) for i in range(ncols)],
             'formats': ncols * [a1.dtype]}

    a3 = np.intersect1d(a1.view(dtype), a2.view(dtype))

    # This last bit is optional if you're okay with "C" being a structured array...
    a3 = a3.view(a1.dtype).reshape(-1, ncols)
    return a3


def safe_pawns_np(pawns: set) -&gt; int:
    letters_string = "abcdefgh"

    np_pawns = np.array(list(map(lambda x: (letters_string.index(x[0]), int(x[1])), pawns)))

    safe_spots_right = np_pawns.copy()
    safe_spots_right[:, 0] += 1
    safe_spots_right[:, 1] += 1

    safe_spots_left = np_pawns.copy()
    safe_spots_left[:, 0] -= 1
    safe_spots_left[:, 1] += 1

    safe_spots = np.vstack((safe_spots_right, safe_spots_left))

    return len(intersect2d(np_pawns, safe_spots))