Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/directory_writer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1 # v1, NOT v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
- name: Write DIRECTORY.md
run: |
scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
name: Lint

on:
- push
- pull_request
push:
branches:
- '**'
pull_request:
types: [ opened, synchronize, reopened ]

permissions:
contents: read
Expand All @@ -12,8 +15,8 @@ jobs:
lint-ruff:
strategy:
matrix:
python-version: [ '3.12' ]
name: Pylint
python-version: [ '3.14' ]
name: Ruff Check
uses: SanctumLabs/ci-workflows/.github/workflows/python-lint-ruff.yml@main
with:
command: check
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/cache@v4
- uses: actions/checkout@v7
- uses: actions/cache@v6
with:
path: |
~/.cache/pre-commit
~/.cache/pip
key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- uses: actions/setup-python@v2
- uses: actions/setup-python@v7
- uses: psf/black@21.4b0
continue-on-error: true
- name: Install pre-commit
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade pre-commit
- run: pre-commit run --verbose --all-files --show-diff-on-failure
- run: pre-commit run --verbose --all-files --show-diff-on-failure
continue-on-error: true
10 changes: 10 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@
* [Test Top K Largest Elements](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/topklargest/test_top_k_largest_elements.py)
* Total Cost Hire K Workers
* [Test Total Cost Hire K Workers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/total_cost_hire_k_workers/test_total_cost_hire_k_workers.py)
* Trapping Rain Water Ii
* [Test Trapping Rain Water Ii](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/trapping_rain_water_ii/test_trapping_rain_water_ii.py)
* Huffman
* [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py)
* [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py)
Expand Down Expand Up @@ -459,6 +461,8 @@
* Trie
* Index Pairs Of A String
* [Test Index Pairs Of A String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/index_pairs_of_a_string/test_index_pairs_of_a_string.py)
* Kth Smallest In Lexicographic Order
* [Test Kth Smallest In Lexicographic Order](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/kth_smallest_in_lexicographic_order/test_kth_smallest_in_lexicographic_order.py)
* Longest Word With Prefixes
* [Test Longest Word With Prefixes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/longest_word_with_prefixes/test_longest_word_with_prefixes.py)
* Replace Words
Expand Down Expand Up @@ -810,6 +814,10 @@
* [Debug Sample](https://github.com/BrianLusina/PythonSnips/blob/master/debugging_tools/debug_sample.py)

## Design Patterns
* All O One
* [All One](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/all_o_one/all_one.py)
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/all_o_one/node.py)
* [Test All O One](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/all_o_one/test_all_o_one.py)
* Behavioral
* Chain Of Responsibility
* Animals
Expand Down Expand Up @@ -1086,6 +1094,8 @@
* [Test Mini Max Sum](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/mini_max_sum/test_mini_max_sum.py)
* Multiply 5
* [Test Multiply 5](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/multiply_5/test_multiply_5.py)
* My Pow
* [Test My Pow](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/my_pow/test_my_pow.py)
* Perfect Square
* [Test Perfect Squares](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/perfect_square/test_perfect_squares.py)
* Power Of I
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ format-ruff: ## Runs formatting with ruff
fix-ruff: ## Runs check with ruff & fixes files
poetry run ruff check --fix .

.PHONY: check-ruff
check-ruff: ## Runs check with ruff
poetry run ruff check .

.PHONY: lint-flake8
lint-flake8: ## lints project using flake8
poetry run pre-commit run -a flake8
Expand Down
43 changes: 43 additions & 0 deletions algorithms/heap/trapping_rain_water_ii/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Trapping Rain Water II

Given an m x n integer matrix heightMap representing the height of each unit cell in a 2D elevation map, return the
volume of water it can trap after raining.

## Examples

Example 1:

![Example 1](./images/examples/trapping_rain_water_ii_example_1.png)

```text
Input: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
Output: 4
Explanation: After the rain, water is trapped between the blocks.
We have two small ponds 1 and 3 units trapped.
The total volume of water trapped is 4.
```

Example 2:

![Example 2](./images/examples/trapping_rain_water_ii_example_2.png)

```text
Input: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
Output: 10
```

## Constraints

- m == `heightMap.length`
- n == `heightMap[i].length`
- 1 <= `m`, `n` <= 200
- 0 <= `heightMap[i][j]` <= 2 * 10^4

## Topics

- Array
- Breadth-First Search
- Heap (Priority Queue)
- Matrix

---
154 changes: 154 additions & 0 deletions algorithms/heap/trapping_rain_water_ii/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from typing import List
import heapq


class Cell:
"""
Class to store the height and coordinates of a cell in a grid
"""

def __init__(self, height: int, row: int, col: int):
self.height = height
self.row = row
self.col = col

def __lt__(self, other: "Cell"):
"""Comparison method for the priority queue(min-heap)"""
return self.height < other.height


def trap_rain_water(height_map: List[List[int]]) -> int:
# Direction arrays that help us navigate the neighbors of a cell
d_row = [0, 0, -1, 1]
d_col = [-1, 1, 0, 0]

# Number of rows and columns in the height map
num_rows = len(height_map)
num_cols = len(height_map[0])

# Create a num_rows * num_cols boolean grid, called visited, with all its values initialized to False
visited = [[False] * num_cols for _ in range(num_rows)]

# Initialize a priority queue (min-heap) of Cells, called boundary. This will process cells in increasing height order
boundary: List[Cell] = []

# Add the first and last column cells to the boundary and mark them as visited
# Add the first and last column cells to the boundary and mark them as visited
for i in range(num_rows):
heapq.heappush(boundary, Cell(height_map[i][0], i, 0))
heapq.heappush(
boundary,
Cell(height_map[i][num_cols - 1], i, num_cols - 1),
)
visited[i][0] = visited[i][num_cols - 1] = True

# Add the first and last row cells to the boundary and mark them as visited
for i in range(num_cols):
heapq.heappush(boundary, Cell(height_map[0][i], 0, i))
heapq.heappush(
boundary,
Cell(height_map[num_rows - 1][i], num_rows - 1, i),
)
visited[0][i] = visited[num_rows - 1][i] = True

# Initialize total water volume to 0
total_water_volume = 0

def is_cell_valid(row: int, col: int) -> bool:
"""
Checks if a cell is within the bounds of the height map
"""
return 0 <= row < num_rows and 0 <= col < num_cols

# Process cells in the boundary (min-heap will always pop the smallest height)
while boundary:
# Pop the top cell from the boundary. This is the cell with the smallest height in the unexplored part of the
# boundary
current_cell = heapq.heappop(boundary)

current_row = current_cell.row
current_col = current_cell.col
min_boundary_height = current_cell.height

# Explore all 4 neighbors of the current cell
for direction in range(4):
# Calculate the row and column of the neighbor cell
neighbor_row = current_row + d_row[direction]
neighbor_col = current_col + d_col[direction]

# Check if the neighbor cell is within the bounds of the height map and has not been visited
if (
is_cell_valid(neighbor_row, neighbor_col)
and not visited[neighbor_row][neighbor_col]
):
# Calculate the height of the neighbor cell
neighbor_height = height_map[neighbor_row][neighbor_col]

# If the neighbor's height is less than the current boundary height, water can be trapped
if neighbor_height < min_boundary_height:
# Add the trapped water volume
water_volume = min_boundary_height - neighbor_height
total_water_volume += water_volume

# Push the neighbor cell to the boundary with updated height(to prevent water leakage) and mark it as visited
heapq.heappush(
boundary,
Cell(
max(neighbor_height, min_boundary_height),
neighbor_row,
neighbor_col,
),
)
visited[neighbor_row][neighbor_col] = True

# Return the total amount of trapped water
return total_water_volume


def trap_rain_water_2(height_map: List[List[int]]) -> int:
# Get dimensions of the height map
m, n = len(height_map), len(height_map[0])

# Edge case: if grid is too small to trap any water
if m < 3 or n < 3:
return 0

# Min-heap to process cells by height (boundary cells first)
min_heap = []
# Track visited cells to avoid reprocessing
visited = [[False] * n for _ in range(m)]

# Push all boundary cells into the min-heap and mark them visited
for r in range(m):
for c in range(n):
if r == 0 or r == m - 1 or c == 0 or c == n - 1:
heapq.heappush(min_heap, (height_map[r][c], r, c))
visited[r][c] = True

# Directions for exploring neighbors (up, down, left, right)
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
total_water = 0

# Process cells in order of increasing height
while min_heap:
# Pop the cell with the smallest height (current water boundary)
height, row, col = heapq.heappop(min_heap)

# Explore all 4 neighbors
for dr, dc in directions:
nr, nc = row + dr, col + dc

# Skip out-of-bounds or already visited cells
if nr < 0 or nr >= m or nc < 0 or nc >= n or visited[nr][nc]:
continue

# Water trapped at neighbor = max(0, current boundary height - neighbor height)
total_water += max(0, height - height_map[nr][nc])

# Push neighbor into heap with effective height (max of boundary or its own)
heapq.heappush(min_heap, (max(height, height_map[nr][nc]), nr, nc))

# Mark neighbor as visited
visited[nr][nc] = True

return total_water
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest
from typing import List
from parameterized import parameterized
from utils.test_utils import custom_test_name_func
from algorithms.heap.trapping_rain_water_ii import trap_rain_water, trap_rain_water_2

TRAPPING_RAIN_WATER_II_TEST_CASES = [
([[1, 4, 3, 1, 3, 2], [3, 2, 1, 3, 2, 4], [2, 3, 3, 2, 3, 1]], 4),
(
[
[3, 3, 3, 3, 3],
[3, 2, 2, 2, 3],
[3, 2, 1, 2, 3],
[3, 2, 2, 2, 3],
[3, 3, 3, 3, 3],
],
10,
),
]


class TrappingRainWaterIITestCase(unittest.TestCase):
@parameterized.expand(
TRAPPING_RAIN_WATER_II_TEST_CASES, name_func=custom_test_name_func
)
def test_trapping_rain_water_ii(self, height_map: List[List[int]], expected: int):
actual = trap_rain_water(height_map)
self.assertEqual(expected, actual)

@parameterized.expand(
TRAPPING_RAIN_WATER_II_TEST_CASES, name_func=custom_test_name_func
)
def test_trapping_rain_water_ii_2(self, height_map: List[List[int]], expected: int):
actual = trap_rain_water_2(height_map)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion datastructures/linked_lists/linked_list_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def find_middle_node(head: Optional[Node]) -> Optional[Node]:
def has_cycle(
head: Optional[Node], func: Optional[Callable[[Node, Node], Any]] = None
) -> bool:
f"""
"""
Checks if a given linked list has a cycle if a head node is provided. A cycle is when a linked list node can be
reached again after traversing the entire linked list
Args:
Expand Down
2 changes: 1 addition & 1 deletion datastructures/linked_lists/node.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional, Generic, TypeVar
from typing import Any, Optional, Generic
from abc import ABCMeta
from datastructures.linked_lists.types import T

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from datastructures.linked_lists import LinkedList, T, Node
from datastructures.linked_lists.exceptions import EmptyLinkedList
from datastructures.linked_lists.singly_linked_list.single_linked_list_utils import (
reverse_list,
merge_and_weave,
)

Expand Down
3 changes: 1 addition & 2 deletions datastructures/sparse_vector/test_sparse_vector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import unittest
from typing import List
from parameterized import parameterized, parameterized_class
from parameterized import parameterized_class
from datastructures.sparse_vector import SparseVector

SPARSE_VECTOR_TEST_CASES = [([1, 0, 0, 2, 3], [0, 3, 0, 4, 0], 8)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import unittest

from datastructures.trees.binary.tree import BinaryTree
from datastructures.trees.binary.node import BinaryTreeNode


class BinaryTreeCreateTreeTestCases(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from typing import Any, List, Optional

from algorithms.sorting.heapsort import heapsort, heapify, right_child_index
from datastructures.trees.heaps import ArrayBasedHeap
from datastructures.trees.heaps.utils import has_smaller_child


class MinArrayBasedHeap(ArrayBasedHeap):
Expand Down
Loading
Loading