From d4659ed4442ca3d8f9c6e4eec6b7e3fe9e1dd394 Mon Sep 17 00:00:00 2001 From: Gonzalo Diaz Date: Wed, 7 Aug 2024 00:25:56 -0400 Subject: [PATCH 1/2] [Hacker Rank] Interview Preparation Kit: Recursion: Davis' Staircase. Clean code improvements and new generalized solution. --- .../ctci_recursive_staircase.py | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py index fda5ed50..f87453b8 100644 --- a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py +++ b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py @@ -4,23 +4,38 @@ from typing import Dict +TOP_LIMIT = 10 ** 10 + 7 +STEPS_LIMIT = 3 -def step_perms_comput_with_cache(n_steps: int, cache: Dict[int, int]) -> int: + +def step_perms_comput_with_cache( + n_steps: int, + cache: Dict[int, int], + steps_limit: int) -> int: + + # Base cases if 0 <= n_steps <= 2: return n_steps - if n_steps == 3: - return 4 result = 0 - for i in range(1, 4): - if n_steps - i not in cache: - cache[n_steps - i] = step_perms_comput_with_cache(n_steps - i, cache) + for i in range(1, min(steps_limit, n_steps) + 1): + search_key: int = n_steps - i + + if search_key not in cache: + cache[search_key] = step_perms_comput_with_cache( + search_key, + cache, + steps_limit + ) result += cache[n_steps - i] - return result + # if n_steps <= steps_limit: + # result += 1 + + return (result + 1) if n_steps <= steps_limit else result def step_perms(n_steps: int) -> int: initial_cache: Dict[int, int] = {} - return step_perms_comput_with_cache(n_steps, initial_cache) % (10 ** 10 + 7) + return step_perms_comput_with_cache(n_steps, initial_cache, STEPS_LIMIT) % TOP_LIMIT From 920d18d14ad3b0426953d996497539e00dbfbd5e Mon Sep 17 00:00:00 2001 From: Gonzalo Diaz Date: Wed, 7 Aug 2024 00:55:13 -0400 Subject: [PATCH 2/2] [Hacker Rank] Interview Preparation Kit: Recursion: Davis' Staircase. Test cases separated from test file. Test cases added. New test for generalized version. --- ...ctci-recursive-staircase-solution-notes.md | 32 +++++++ .../ctci_recursive_staircase.py | 6 +- .../ctci_recursive_staircase.testcases.json | 49 ++++++++++ ...rsive_staircase_generalized.testcases.json | 32 +++++++ .../ctci_recursive_staircase_test.py | 95 ++++++++----------- 5 files changed, 156 insertions(+), 58 deletions(-) create mode 100644 src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.testcases.json create mode 100644 src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_generalized.testcases.json diff --git a/docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md b/docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md index 24bb66df..b63bbfb4 100644 --- a/docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md +++ b/docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md @@ -36,3 +36,35 @@ so that repeated cases are not recalculated. The trade-off is that the algorithm now requires more memory to run in less time. + +## Generalized solution + +In order to comply with some clean code best practices, +I noticed that the step limit in the algorithm is a hard-coded number, +so to comply with the "no magic numbers" rule, +I was forced to find a more generalized solution. + +Then I found the following pattern: + +- First cases are: + +$$ \begin{matrix} + \text{stepPerms(0)} = 0 \\ + \text{stepPerms(1)} = 1 \\ + \text{stepPerms(2)} = 2 \\ + \end{matrix} +$$ + +- Next step combinations above 2 and less than the step limit are: + +$$ \text{stepPerms(number of steps)} = 2^\text{number of steps} + 1 $$ + +- When `number of steps` are above the limit, the pattern is +the sum of latest `number of steps` previous calls of +`stepPerms(x)` results as follows: + +$$ \displaystyle\sum_{ + i=\text{number of steps} - \text{limit}} + ^\text{number of steps} + stepPerms(\text{number of steps} - i) +$$ diff --git a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py index f87453b8..5cb55d63 100644 --- a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py +++ b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.py @@ -1,5 +1,6 @@ # pylint: disable=line-too-long # @link Problem definition [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase.md]] # noqa +# @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md]] # noqa # pylint: enable=line-too-long from typing import Dict @@ -28,10 +29,7 @@ def step_perms_comput_with_cache( steps_limit ) - result += cache[n_steps - i] - - # if n_steps <= steps_limit: - # result += 1 + result += cache[search_key] return (result + 1) if n_steps <= steps_limit else result diff --git a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.testcases.json b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.testcases.json new file mode 100644 index 00000000..120ef099 --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase.testcases.json @@ -0,0 +1,49 @@ +[ + { + "title": "Sample Test case 0", + "tests": [ + { + "input": 1, + "expected": 1 + }, + { + "input": 3, + "expected": 4 + }, + { + "input": 7, + "expected": 44 + } + ] + }, + { + "title": "Sample Test case 9", + "tests": [ + { + "input": 5, + "expected": 13 + }, + { + "input": 8, + "expected": 81 + } + ] + }, + { + "title": "Sample Test case 10", + "tests": [ + { + "input": 15, + "expected": 5768 + }, + { + "input": 20, + "expected": 121415 + }, + { + "input": 27, + "expected": 8646064 + } + ] + } +] diff --git a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_generalized.testcases.json b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_generalized.testcases.json new file mode 100644 index 00000000..2a18c778 --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_generalized.testcases.json @@ -0,0 +1,32 @@ +[ + { + "title": "Own sample 1", + "tests": [ + { + "input": 4, + "limit": 3, + "expected": 7 + } + ] + }, + { + "title": "Own sample 2", + "tests": [ + { + "input": 5, + "limit": 4, + "expected": 15 + } + ] + }, + { + "title": "Own sample 3", + "tests": [ + { + "input": 6, + "limit": 2, + "expected": 13 + } + ] + } +] diff --git a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_test.py b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_test.py index d5f7d8b6..39b94025 100644 --- a/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_test.py +++ b/src/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci_recursive_staircase_test.py @@ -1,56 +1,26 @@ import unittest -from .ctci_recursive_staircase import step_perms +import json +from pathlib import Path +from typing import Dict + +from .ctci_recursive_staircase import step_perms, step_perms_comput_with_cache from .ctci_recursive_staircase_alternative import step_perms as step_perms_alt -TEST_CASES = [ - { - 'title': 'Sample Test case 0', - 'tests': [ - { - 'input': 1, - 'answer': 1 - }, - { - 'input': 3, - 'answer': 4 - }, - { - 'input': 7, - 'answer': 44 - } - ] - }, - { - 'title': 'Sample Test case 9', - 'tests': [ - { - 'input': 5, - 'answer': 13 - }, - { - 'input': 8, - 'answer': 81 - } - ] - }, - { - 'title': 'Sample Test case 10', - 'tests': [ - { - 'input': 15, - 'answer': 5768 - }, - { - 'input': 20, - 'answer': 121415 - }, - { - 'input': 27, - 'answer': 8646064 - } - ] - } -] +FILE_PATH = str(Path(__file__).resolve().parent) + +with open( + FILE_PATH + + '/ctci_recursive_staircase.testcases.json', + encoding="utf-8" + ) as file1: + TEST_CASES = json.load(file1) + +with open( + FILE_PATH + + '/ctci_recursive_staircase_generalized.testcases.json', + encoding="utf-8" + ) as file2: + TEST_CASES_GENERALIZED = json.load(file2) class TestRecursionFibonacciNumbers(unittest.TestCase): @@ -73,9 +43,26 @@ def test_step_perms(self): for _, _tt in enumerate(testset['tests']): self.assertEqual( - step_perms(_tt['input']), _tt['answer'], + step_perms(_tt['input']), _tt['expected'], f"{_} | step_perms({_tt['input']}) must be " - f"=> {_tt['answer']} in {testset['title']}") + f"=> {_tt['expected']} in {testset['title']}") + + def test_step_perms_comput_with_cache(self): + + for _, testset in enumerate(TEST_CASES_GENERALIZED): + + for _, _tt in enumerate(testset['tests']): + + initial_cache: Dict[int, int] = {} + + self.assertEqual( + step_perms_comput_with_cache( + _tt['input'], initial_cache, + _tt['limit']), + _tt['expected'], + f"{_} | step_perms_comput_with_cache(" + f"{_tt['input']}, {initial_cache}, {_tt['limit']}) must be " + f"=> {_tt['expected']} in {testset['title']}") def test_step_perms_alt(self): @@ -84,6 +71,6 @@ def test_step_perms_alt(self): for _, _tt in enumerate(testset['tests']): self.assertEqual( - step_perms_alt(_tt['input']), _tt['answer'], + step_perms_alt(_tt['input']), _tt['expected'], f"{_} | step_perms_alt({_tt['input']}) must be " - f"=> {_tt['answer']} in {testset['title']}") + f"=> {_tt['expected']} in {testset['title']}")