Skip to content

Contributes to #9943: Add docstrings and doctests, fix is_safe logic, and improve code formatting #12691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 134 additions & 14 deletions matrix/count_islands_in_matrix.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,157 @@
# An island in matrix is a group of linked areas, all having the same value.
# This code counts number of islands in a given matrix, with including diagonal
# connections.
class Matrix:
"""
Class to represent a 2D binary grid as a matrix and count the number of islands.
An island is a group of connected 1s.
We call cells with 1 "land" and cells with 0 "water".
Connections can be vertical, horizontal, or diagonal.
"""


class Matrix: # Public class to implement a graph
def __init__(self, row: int, col: int, graph: list[list[bool]]) -> None:
"""
Initialize the matrix.

:param row: Number of rows
:param col: Number of columns
:param graph: 2D list representing the matrix (1 = land, 0 = water)

>>> graph = [[1, 1, 0], [0, 1, 0], [1, 0, 0]]
>>> m = Matrix(3, 3, graph)
>>> m.ROW
3
>>> m.COL
3
>>> m.graph == graph
True

# Edge case: Empty matrix
>>> empty = Matrix(0, 0, [])
>>> empty.ROW
0
>>> empty.COL
0
>>> empty.graph
[]

# Edge case: 1x1 matrix
>>> single = Matrix(1, 1, [[1]])
>>> single.ROW
1
>>> single.COL
1
>>> single.graph
[[1]]
"""

self.ROW = row
self.COL = col
self.graph = graph

def is_safe(self, i: int, j: int, visited: list[list[bool]]) -> bool:
"""
Check if a cell (i, j) is "safe".

We consider a cell "safe" if:
- It is within the bounds of the matrix
- It has not been visited yet
- It contains land (i.e., value is 1)

:param i: row index
:param j: column index
:param visitied: 2D list indicating which cells we have visited
:return: True if cell is safe, else False

>>> m = Matrix(3, 3, [[1, 0, 0], [0, 0, 1], [0, 0, 0]])
>>> visited = [[False]*3 for _ in range(3)]

# Within bounds, unvisited, and value is 1 (land)
>>> m.is_safe(0, 0, visited)
True

# Within bounds, unvisited, but value is 0 (water)
>>> m.is_safe(0, 1, visited)
False

# Within bounds, land, but already visited
>>> visited[0][0] = True
>>> m.is_safe(0, 0, visited)
False

# Out of bounds
>>> m.is_safe(3, 0, visited)
False
>>> m.is_safe(-1, 0, visited)
False
"""

return (
0 <= i < self.ROW
and 0 <= j < self.COL
and not visited[i][j]
and self.graph[i][j]
and self.graph[i][j] == 1
)

def diffs(self, i: int, j: int, visited: list[list[bool]]) -> None:
# Checking all 8 elements surrounding nth element
row_nbr = [-1, -1, -1, 0, 0, 1, 1, 1] # Coordinate order
"""
Check the 8 cells connected to (i, j) and marks the land cells as visited.

Depth-first search (DFS) to mark land cells connected to (i, j) as visited.

:param i: Row index of current cell
:param j: Column index of current cell
:param visited: 2D list tracking visited cells

>>> graph = [[1, 1, 0], [0, 1, 0], [0, 0, 0]]
>>> m = Matrix(3, 3, graph)
>>> visited = [[False] * 3 for _ in range(3)]
>>> m.diffs(0, 0, visited)
>>> visited[0][0] and visited[0][1] and visited[1][1]
True
>>> visited[2][2]
False
"""

# 8 possible movements (N, NE, E, SE, S, SW, W, NW)
row_nbr = [-1, -1, -1, 0, 0, 1, 1, 1]
col_nbr = [-1, 0, 1, -1, 1, -1, 0, 1]
visited[i][j] = True # Make those cells visited

# Make cells visited
visited[i][j] = True
for k in range(8):
if self.is_safe(i + row_nbr[k], j + col_nbr[k], visited):
self.diffs(i + row_nbr[k], j + col_nbr[k], visited)
next_i, next_j = i + row_nbr[k], j + col_nbr[k]
if self.is_safe(next_i, next_j, visited):
self.diffs(next_i, next_j, visited)

def count_islands(self) -> int:
"""
Count the number of islands in the matrix.

:return: Number of islands found

>>> graph = [[1, 1, 0], [0, 1, 0], [1, 0, 0]]
>>> m = Matrix(3, 3, graph)
>>> m.count_islands()
1

>>> graph1 = [[1, 1, 0], [0, 0, 0], [1, 0, 0]]
>>> m1 = Matrix(3, 3, graph1)
>>> m1.count_islands()
2

>>> graph2 = [[0, 0, 0], [0, 0, 0]]
>>> m2 = Matrix(2, 3, graph2)
>>> m2.count_islands()
0

def count_islands(self) -> int: # And finally, count all islands.
visited = [[False for j in range(self.COL)] for i in range(self.ROW)]
>>> graph3 = [[1]]
>>> m3 = Matrix(1, 1, graph3)
>>> m3.count_islands()
1
"""
visited = [[False for _ in range(self.COL)] for _ in range(self.ROW)]
count = 0
for i in range(self.ROW):
for j in range(self.COL):
if visited[i][j] is False and self.graph[i][j] == 1:
if not visited[i][j] and self.graph[i][j] == 1:
self.diffs(i, j, visited)
count += 1
return count
8 changes: 5 additions & 3 deletions matrix/matrix_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,11 @@ def cofactors(self) -> Matrix:
return Matrix(
[
[
self.minors().rows[row][column]
if (row + column) % 2 == 0
else self.minors().rows[row][column] * -1
(
self.minors().rows[row][column]
if (row + column) % 2 == 0
else self.minors().rows[row][column] * -1
)
for column in range(self.minors().num_columns)
]
for row in range(self.minors().num_rows)
Expand Down