Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to count specific neighbours in a nested list in Python 3

I have a problem where I need create a function to solve the number of N's(ninjas) in a nested list of given coordinates x, y:

ninjas = [['N', ' ', ' ', ' ', ' '],
         ['N', 'N', 'N', 'N', ' '],
         ['N', ' ', 'N', ' ', ' '],
         ['N', 'N', 'N', ' ', ' '],
         [' ', ' ', ' ', ' ', ' '],
         [' ', ' ', ' ', ' ', ' ']]

So, if my coordinates are x=1 and y=2, the function should return 8 (the number of ninjas surrounding the coordinate). I've battled with this problem for 48 straight hours and I can't get my head around this. The solution should not contain any fancy numpy.imports. Only basic for-loops and...

How should I proceed with this problem? Any hints?

like image 353
teepa Avatar asked Nov 29 '25 06:11

teepa


2 Answers

WAY over the PEP-8 79 characters per line limit, but here's my one-line solution:

def surrounding(ninjas, x, y):
   return [ninjas[r][c] for r in range(y-1 if y > 0 else y, y + 2 if y < len(ninjas)-1 else y + 1) for c in range(x-1 if x > 0 else x, x + 2 if x < len(ninjas[0])-1 else x + 1)].count('N')

which works as expected:

surrounding(ninjas, 1, 2)

which gives 8.

surrounding(ninjas, 4, 5)

which gives 0.


If you want to break it down into something a little more readable, then here is a more sensible function to do the job:

def surrounding(ninjas, x, y):
   neighbours = []
   for r in range(y-1 if y > 0 else y, y+2 if y < len(ninjas)-1 else y+1):
      for c in range(x-1 if x > 0 else x, x+2 if x < len(ninjas[0])-1 else x+1):
         neighbours.append(ninjas[r][c])
   return neighbours.count('N')

Note that both these solutions rely on the ninjas list being rectangular


How they work

Both functions work in the same way, just one is crammed into a list-comprehension and the other appends to a list of neighbours.

The steps for calculating the surrounding Ninjas is as follows:

  1. Initialise a list to store the cell values/
  2. Loop through the rows in the 2d-list from 1 above if there are no walls above else from the entered position to 1 below if there are ...
  3. Loop through the columns inside the current row from 1 to the left if there are no walls else from the entered...
  4. Add that cell to the neighbours list
  5. Return how many Ninjas are in the neighbours list using .count('N')

One more thing to note is that this problem could be approached in a slightly different way. Instead of adding every cell to a list and counting the Ninjas in that list, we could instead add 1 to a variable if that cell is a Ninja.

The code for that would look like:

def surrounding(ninjas, x, y):
   noNinjas = 0
   for r in range(y - 1 if y > 0 else y, y + 2 if y < len(ninjas) else y + 1):
      for c in range(x - 1 if x > 0 else x, x + 2 if x < len(ninjas) else x + 1):
         if ninjas[r][c] == 'N':
            noNinjas += 1
   return noNinjas
like image 185
Joe Iddon Avatar answered Dec 01 '25 21:12

Joe Iddon


As you can get the (x, y) element by ninjas[y][x], you can simply check all 8 surrounding elements for an 'N':

Ns = 0  # Number of 'N's
if ninjas[y + 1][x] == 'N':  # Below
    Ns += 1
if ninjas[y][x + 1] == 'N':  # To the right
    Ns += 1
if ninjas[y - 1][x - 1] == 'N':  # Above to the left
    Ns += 1
...

This can of course be written more cleverly by utilizing loops rather than writing out all 8 cases by hand. Also, no bounds checking is performed, meaning that you probably should ensure that (x, y) is not on the boundary of your 2D data array.

Edit: Loops

As we need x -1, x and x + 1 (and similar for y) we can make the loops like so:

Ns = 0
for i in range(-1, 2):
    for j in range(-1, 2):
        if i == j == 0:
            # This is just the (x, y) element itself. Skip it
            continue
        if ninjas[y + i][x + j] == 'N':
            Ns += 1
like image 36
jmd_dk Avatar answered Dec 01 '25 19:12

jmd_dk