From 4bddf02ef8f3e59546843c95a216a67e348dbb74 Mon Sep 17 00:00:00 2001 From: reunicorn1 <138371895+reunicorn1@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:33:21 +0400 Subject: [PATCH 1/5] adding robot.py file as a problem with its corresponding test --- solutions/robot.py | 35 +++++++++++++++++++++++++++++++++++ solutions/tests/test_robot.py | 20 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 solutions/robot.py create mode 100644 solutions/tests/test_robot.py diff --git a/solutions/robot.py b/solutions/robot.py new file mode 100644 index 000000000..b6c033e2e --- /dev/null +++ b/solutions/robot.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +There is a robot on an m x n grid. The robot is initially located +at the top-left corner (i.e., grid[0][0]). The robot tries to move +to the bottom-right corner (i.e., grid[m - 1][n - 1]). The robot +can only move either down or right at any point in time. + +Given the two integers m and n, return the number of possible +unique paths that the robot can take to reach the bottom-right +corner. + +Constraints: The test cases are generated so that the answer will +be less than or equal to 2 * 109. +""" + +def robot(m_: int, n_: int) -> int: + """ + Returns the number of possible unique routes to reach the + most bottom-right corner + + Parameters: + m: The number of rows in the grid. + n: The number of columns in the grid. + + Returns -> The number of unique paths from the top-left corner + to the bottom-right corner. + + Raises: + AssertionError: if m and n are not integers or less than 1 + + Examples: + >>> robot(3, 7) + 28 + """ diff --git a/solutions/tests/test_robot.py b/solutions/tests/test_robot.py new file mode 100644 index 000000000..e1141b2eb --- /dev/null +++ b/solutions/tests/test_robot.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Test module for robot function. + +Test categories: + - Standard cases + - Edge cases + - Defensive tests + +""" + +import unittest + + +class TestRobot(unittest.TestCase): + """Test the robot function""" + + def test_base(self): + """A basic test""" From 4b335178042e902388eb626bfc77d32f7ed60fdb Mon Sep 17 00:00:00 2001 From: reunicorn1 <138371895+reunicorn1@users.noreply.github.com> Date: Sat, 4 Jan 2025 03:14:32 +0400 Subject: [PATCH 2/5] Writing the base code for the robot problem --- .vscode/settings.json | 4 ++-- solutions/robot.py | 51 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bbda5188d..252022b48 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -119,8 +119,8 @@ "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.ruff": true, - "source.organizeImports.ruff": true + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" } } } diff --git a/solutions/robot.py b/solutions/robot.py index b6c033e2e..c3862c7ab 100644 --- a/solutions/robot.py +++ b/solutions/robot.py @@ -14,22 +14,65 @@ be less than or equal to 2 * 109. """ -def robot(m_: int, n_: int) -> int: + +def robot_move(cell, rows, cols, memo) -> int: + """ + This is a helper function that determines the number of unique paths by + using dynamic programming and memoization + + Parameters: + cell: a tuple made of two elements refers to the current location + inside the grid + rows: The number of rows in the grid. + cols: The number of columns in the grid. + memo: a dictionary to save answers for memoization + + Returns -> The number of unique paths from the top-left corner + to the bottom-right corner. + """ + if memo.get(cell): + return memo.get(cell) + if cell[1] >= cols - 1 or cell[0] >= rows - 1: + return 1 + memo[cell] = robot_move((cell[0] + 1, cell[1]), rows, cols, memo) + \ + robot_move((cell[0], cell[1] + 1), rows, cols, memo) + return memo[cell] + + +def robot(rows: int, cols: int) -> int: """ Returns the number of possible unique routes to reach the most bottom-right corner Parameters: - m: The number of rows in the grid. - n: The number of columns in the grid. + rows: The number of rows in the grid. + cols: The number of columns in the grid. Returns -> The number of unique paths from the top-left corner to the bottom-right corner. Raises: - AssertionError: if m and n are not integers or less than 1 + AssertionError: if rows and cols are not integers or less than 1 Examples: >>> robot(3, 7) 28 + >>> robot(4, 3) + 10 + >>> robot(23, 12) + 193536720 """ + # assert the values of rows and clos and make sure they are ints and + # greater than 1 + assert isinstance(rows, int) and rows > 1, "Rows variables has to \ + be an integer greater than 1" + assert isinstance(cols, int) and cols > 1, "Cols variable has to be \ + greater than 1" + + # use the helper function robot_move to determine the number of paths + memo = {} + return robot_move((0, 0), rows, cols, memo) # (0, 0) is the initial spot + + +if __name__ == "__main__": + print(robot(23, 12)) From 41a88f5f56462dfddf901bf5578d8adb4cd9fe1a Mon Sep 17 00:00:00 2001 From: reunicorn1 <138371895+reunicorn1@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:38:57 +0400 Subject: [PATCH 3/5] Writing test cases for the function robot and fixing bugs --- solutions/robot.py | 14 +++++--- solutions/tests/test_robot.py | 62 +++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/solutions/robot.py b/solutions/robot.py index c3862c7ab..e141e8fbb 100644 --- a/solutions/robot.py +++ b/solutions/robot.py @@ -22,7 +22,7 @@ def robot_move(cell, rows, cols, memo) -> int: Parameters: cell: a tuple made of two elements refers to the current location - inside the grid + inside the grid - immutable, can be used as a key for dicts rows: The number of rows in the grid. cols: The number of columns in the grid. memo: a dictionary to save answers for memoization @@ -30,12 +30,16 @@ def robot_move(cell, rows, cols, memo) -> int: Returns -> The number of unique paths from the top-left corner to the bottom-right corner. """ + # if the cell is stored in the memo dict we return it if memo.get(cell): return memo.get(cell) + # if the cell is directly perpendicular to target, there's only one road if cell[1] >= cols - 1 or cell[0] >= rows - 1: return 1 + # otherwise, there's two roads to take, the answer is the sum of both memo[cell] = robot_move((cell[0] + 1, cell[1]), rows, cols, memo) + \ robot_move((cell[0], cell[1] + 1), rows, cols, memo) + # we save the answer in the memo for future referance and return it return memo[cell] @@ -64,10 +68,10 @@ def robot(rows: int, cols: int) -> int: """ # assert the values of rows and clos and make sure they are ints and # greater than 1 - assert isinstance(rows, int) and rows > 1, "Rows variables has to \ - be an integer greater than 1" - assert isinstance(cols, int) and cols > 1, "Cols variable has to be \ - greater than 1" + assert isinstance(rows, int) and rows > 0, "Rows variables has to \ + be an integer greater than 0" + assert isinstance(cols, int) and cols > 0, "Cols variable has to be \ + greater than 0" # use the helper function robot_move to determine the number of paths memo = {} diff --git a/solutions/tests/test_robot.py b/solutions/tests/test_robot.py index e1141b2eb..459699d5b 100644 --- a/solutions/tests/test_robot.py +++ b/solutions/tests/test_robot.py @@ -11,10 +11,66 @@ """ import unittest - +from ..robot import robot class TestRobot(unittest.TestCase): """Test the robot function""" + # Standard Cases + def test_base_2x2(self): + """A basic test with a small grid 2x2""" + self.assertEqual(robot(2, 2), 2) + + def test_base_3x3(self): + """A basic test with a small grid 3x3""" + self.assertEqual(robot(3, 3), 6) + + def test_base_3x7(self): + """A basic test with a small grid of 3x7""" + self.assertEqual(robot(3, 7), 28) + + def test_base_4x3(self): + """A basic test with a grid of 4x3""" + self.assertEqual(robot(4, 3), 10) + + # Edge Cases + def test_long_narrow(self): + """A test for a long narrow grid of 1x10""" + self.assertEqual(robot(1, 10), 1) + + def test_tall_narrow(self): + """A test for a tall narrow grid 10x1""" + self.assertEqual(robot(10, 1), 1) + + def test_large_grid(self): + """A test for a moderately large grid 10x10""" + self.assertEqual(robot(10, 10), 48620) + + def test_very_large_grid(self): + """A test for a very largey grid 20x15""" + self.assertEqual(robot(20, 15), 818809200) + + # Defensive Cases + def test_invalid_row(self): + """A test for an invalid row input - not an integer""" + with self.assertRaises(AssertionError): + robot("3", 7) + + def test_invalid_col(self): + """A test for an invalid column input - not an integer""" + with self.assertRaises(AssertionError): + robot(3, "7") + + def test_negative_value(self): + """A test with a negative dimension""" + with self.assertRaises(AssertionError): + robot(-1, 5) + + def test_zero_value(self): + """A test with zero value""" + with self.assertRaises(AssertionError): + robot(3, 0) - def test_base(self): - """A basic test""" + def test_non_integer_input(self): + """A test wit a non integer input for both dimensions""" + with self.assertRaises(AssertionError): + robot(2.5, 3.7) From 647a4eafc88927a32551dd636383da262b107a42 Mon Sep 17 00:00:00 2001 From: reunicorn1 <138371895+reunicorn1@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:45:02 +0400 Subject: [PATCH 4/5] fixing ruff linting errors --- solutions/robot.py | 11 ++++------- solutions/tests/test_robot.py | 4 +++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/solutions/robot.py b/solutions/robot.py index e141e8fbb..dda695760 100644 --- a/solutions/robot.py +++ b/solutions/robot.py @@ -37,8 +37,9 @@ def robot_move(cell, rows, cols, memo) -> int: if cell[1] >= cols - 1 or cell[0] >= rows - 1: return 1 # otherwise, there's two roads to take, the answer is the sum of both - memo[cell] = robot_move((cell[0] + 1, cell[1]), rows, cols, memo) + \ - robot_move((cell[0], cell[1] + 1), rows, cols, memo) + memo[cell] = robot_move((cell[0] + 1, cell[1]), rows, cols, memo) + robot_move( + (cell[0], cell[1] + 1), rows, cols, memo + ) # we save the answer in the memo for future referance and return it return memo[cell] @@ -75,8 +76,4 @@ def robot(rows: int, cols: int) -> int: # use the helper function robot_move to determine the number of paths memo = {} - return robot_move((0, 0), rows, cols, memo) # (0, 0) is the initial spot - - -if __name__ == "__main__": - print(robot(23, 12)) + return robot_move((0, 0), rows, cols, memo) # (0, 0) is the initial spot diff --git a/solutions/tests/test_robot.py b/solutions/tests/test_robot.py index 459699d5b..777f66803 100644 --- a/solutions/tests/test_robot.py +++ b/solutions/tests/test_robot.py @@ -13,8 +13,10 @@ import unittest from ..robot import robot + class TestRobot(unittest.TestCase): """Test the robot function""" + # Standard Cases def test_base_2x2(self): """A basic test with a small grid 2x2""" @@ -46,7 +48,7 @@ def test_large_grid(self): self.assertEqual(robot(10, 10), 48620) def test_very_large_grid(self): - """A test for a very largey grid 20x15""" + """A test for a very large grid 20x15""" self.assertEqual(robot(20, 15), 818809200) # Defensive Cases From a9dc6174c75e46272572db8242c34bb3c9e427d0 Mon Sep 17 00:00:00 2001 From: reunicorn1 <138371895+reunicorn1@users.noreply.github.com> Date: Mon, 6 Jan 2025 19:35:21 +0400 Subject: [PATCH 5/5] Adding Value error when the variables value is less than 1 --- solutions/robot.py | 13 +++++++++---- solutions/tests/test_robot.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/solutions/robot.py b/solutions/robot.py index dda695760..719a17ed4 100644 --- a/solutions/robot.py +++ b/solutions/robot.py @@ -12,6 +12,9 @@ Constraints: The test cases are generated so that the answer will be less than or equal to 2 * 109. + +Created on 2024-01-01 +Author: Reem Osama """ @@ -69,10 +72,12 @@ def robot(rows: int, cols: int) -> int: """ # assert the values of rows and clos and make sure they are ints and # greater than 1 - assert isinstance(rows, int) and rows > 0, "Rows variables has to \ - be an integer greater than 0" - assert isinstance(cols, int) and cols > 0, "Cols variable has to be \ - greater than 0" + assert isinstance(rows, int), "Rows variables has to be an integer" + if rows <= 0: + raise ValueError("The 'rows' variable must be greater than 0.") + assert isinstance(cols, int), "Cols variable has to be an integer" + if cols <= 0: + raise ValueError("The 'cols' variables must be greater than 0.") # use the helper function robot_move to determine the number of paths memo = {} diff --git a/solutions/tests/test_robot.py b/solutions/tests/test_robot.py index 777f66803..c21b23bb5 100644 --- a/solutions/tests/test_robot.py +++ b/solutions/tests/test_robot.py @@ -8,6 +8,8 @@ - Edge cases - Defensive tests +Created on 2024-01-02 +Author: Reem Osama """ import unittest @@ -18,6 +20,10 @@ class TestRobot(unittest.TestCase): """Test the robot function""" # Standard Cases + def test_base_1x1(self): + """A basic test with a small grid 1x1""" + self.assertEqual(robot(1, 1), 1) + def test_base_2x2(self): """A basic test with a small grid 2x2""" self.assertEqual(robot(2, 2), 2) @@ -64,12 +70,12 @@ def test_invalid_col(self): def test_negative_value(self): """A test with a negative dimension""" - with self.assertRaises(AssertionError): + with self.assertRaises(ValueError): robot(-1, 5) def test_zero_value(self): """A test with zero value""" - with self.assertRaises(AssertionError): + with self.assertRaises(ValueError): robot(3, 0) def test_non_integer_input(self):