Skip to content
This repository was archived by the owner on Jul 2, 2024. It is now read-only.

Commit e9864f9

Browse files
committed
Dynamic programming (#17)
* Rebased 'feature/dynamic' onto 'devel' * Added factorial function Added factorial getter function implementation and tests. Added typed marker file for the "calc" package. * Added dynamic package Implemented fibonacci number getter function (dynamic programming). Added corresponding test cases for huge numbers to demonstrate enhancements. * Added grid traveler problem * Updated function signatures * Updated memoize decorator documentation * Updated imports * Fixed pylint issues * Added can sum target function Implemented function to check, if it's available to get the target number. Added test cases as well. Only tabulation approach implemented. * Updated memoize decorator docstring * Added can sum target function Implemented memoized function and test cases. * Refactored "can_get_target" function * Updated tabulation functions Implemented function to get the best sequence to get target number. Added test cases as well. This algorithm has no limitations on available numbers usage. * Added some comments to dynamic functions * Added partition list function boilerplate Prepared some test cases, including edge cases tests. TDD - test are expected failed (no pytest marks). * Revert "Added partition list function boilerplate" This reverts commit 8a49e9b.
1 parent 2c9d0d7 commit e9864f9

File tree

16 files changed

+548
-123
lines changed

16 files changed

+548
-123
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ classifiers = [
3333
]
3434
packages = [
3535
{ include = "bisearch", from = "src" },
36+
{ include = "calc", from = "src" },
3637
{ include = "conv_store", from = "src" },
38+
{ include = "dynamic", from = "src" },
3739
{ include = "sequences", from = "src" },
3840
{ include = "sorting", from = "src" },
3941
{ include = "primes", from = "src" },

src/calc/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
Various calculation challenges
3+
==============================
4+
5+
"""
6+
7+
from calc.func import *
8+
9+
__all__ = [
10+
"get_factorial",
11+
"get_fibonacci_number",
12+
"get_sum_of_strings",
13+
]

src/calc/func.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Calculation functions implementations
3+
4+
"""
5+
6+
7+
def get_factorial(number: int, /) -> int:
8+
"""Return the factorial value for a given number
9+
10+
:param number:
11+
:type number: int
12+
13+
:return: the factorial value
14+
:rtype: int
15+
16+
In mathematics the factorial is the product of all positive integers
17+
less than or equal to given number.
18+
E.g. 5! = 5 * 4! = 5 * 4 * 3 * 2 * 1 = 120.
19+
The value of 0! = 1 according to the convention of an empty product.
20+
21+
Usage examples:
22+
23+
>>> assert get_factorial(0) == 1
24+
>>> assert get_factorial(5) == 120
25+
26+
"""
27+
28+
if number <= 1:
29+
return 1
30+
31+
return number * get_factorial(number - 1)
32+
33+
34+
def get_fibonacci_number(idx: int, /) -> int:
35+
"""Return a Fibonacci's sequence number at a specified index
36+
37+
:param idx: a Fibonacci sequence index starting from 0
38+
:type idx: int
39+
40+
:return: a sequence's member
41+
:rtype: int
42+
43+
The Fibonacci number is a number from the Fibonacci sequence, in which
44+
each number is the sum of the two preceding ones. This sequence commonly
45+
starts from 0 and 1: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144...
46+
47+
Usage examples:
48+
49+
>>> assert get_fibonacci_number(0) == 0
50+
>>> assert get_fibonacci_number(1) == 1
51+
>>> assert get_fibonacci_number(2) == 1
52+
>>> assert get_fibonacci_number(3) == 2
53+
>>> assert get_fibonacci_number(4) == 3
54+
55+
"""
56+
57+
if idx <= 0:
58+
return 0
59+
60+
if 0 < idx < 3: # the same as ``idx == 1 or idx == 2``
61+
return 1
62+
63+
return get_fibonacci_number(idx - 1) + get_fibonacci_number(idx - 2)
64+
65+
66+
def get_sum_of_strings(number_1: str, number_2: str, /) -> str:
67+
"""Return the sum of two numbers of string type as string
68+
69+
:param number_1: first number
70+
:type number_1: str
71+
:param number_2: second number
72+
:type number_2: str
73+
74+
:return: the sum of two numbers
75+
:rtype: str
76+
77+
Valid input is a string of any length containing numeric characters from
78+
0 to 9. Empty strings are allowed as well and should be considered as 0.
79+
80+
Usage examples:
81+
82+
>>> assert get_sum_of_strings("123", "456") == "579"
83+
>>> assert get_sum_of_strings("099", "001") == "100"
84+
>>> assert get_sum_of_strings("", "10") == "10"
85+
>>> assert get_sum_of_strings("", "") == "0"
86+
87+
"""
88+
89+
# remove leading zeros
90+
for digit in number_1:
91+
if digit != "0":
92+
break
93+
number_1 = number_1[1::]
94+
for digit in number_2:
95+
if digit != "0":
96+
break
97+
number_2 = number_2[1::]
98+
99+
if not number_1 or not number_2:
100+
return number_1 or number_2 or "0"
101+
102+
result: str = ""
103+
size: int = max(len(number_1), len(number_2))
104+
105+
# reverse numbers
106+
number_1, number_2 = number_1[::-1], number_2[::-1]
107+
108+
# make strings of the same lengths
109+
while len(number_1) < size:
110+
number_1 += "0"
111+
while len(number_2) < size:
112+
number_2 += "0"
113+
114+
carry: int = 0
115+
for digit_1, digit_2 in zip(number_1, number_2):
116+
sum_of_digits = int(digit_1) + int(digit_2) + carry
117+
result += str(sum_of_digits % 10)
118+
carry = sum_of_digits // 10
119+
120+
if carry:
121+
result += str(carry)
122+
123+
return result[::-1]

src/calc/py.typed

Whitespace-only changes.

src/dynamic/__init__.py

Whitespace-only changes.

src/dynamic/memoization.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""
2+
Dynamic programming memoization functions
3+
4+
"""
5+
6+
import functools
7+
from typing import Any, Callable, Dict, List, Optional, Tuple
8+
9+
from calc.func import get_fibonacci_number as fib_classic
10+
11+
12+
def memoize(func: Callable) -> Callable:
13+
"""Memoization decorator implementation
14+
15+
:param func: a function to decorate with memoization technique
16+
17+
:return: the decorated function
18+
19+
The memoization technique helps to reduce the number of recursive
20+
calls by caching the results already calculated for some inputs.
21+
22+
Current implementation caches the inputs as tuples containing args,
23+
so it has limitations for "un-hashable" inputs, that can not be
24+
stored in a cache dictionary.
25+
26+
"""
27+
28+
cache: Dict[Tuple[Any], int] = {}
29+
30+
@functools.wraps(func)
31+
def wrapper(*args):
32+
if args not in cache:
33+
cache[args] = func(*args)
34+
35+
return cache[args]
36+
37+
return wrapper
38+
39+
40+
@memoize
41+
@functools.wraps(fib_classic, ("__annotations__", "__doc__"))
42+
def get_fibonacci_number(idx): # pylint: disable=C0116
43+
if idx <= 0:
44+
return 0
45+
if idx < 3:
46+
return 1
47+
48+
return get_fibonacci_number(idx - 1) + get_fibonacci_number(idx - 2)
49+
50+
51+
@memoize
52+
def get_grid_travels(height: int, width: int, /) -> int:
53+
"""Calculate the number of available route for a specified grid size
54+
55+
:param height: grid height
56+
:param width: grid width
57+
58+
:return: the number of available routes
59+
:rtype: int
60+
61+
The traveler starts the journey in the top-left corner of the grid
62+
and trying to reach the opposite grid corner (bottom-right).
63+
The only moves available are **move right** and **move down**.
64+
The task is to calculate the number of all available routes to do
65+
this.
66+
67+
"""
68+
69+
if height <= 0 or width <= 0:
70+
return 0
71+
72+
if height == 1 or width == 1:
73+
return 1
74+
75+
return (get_grid_travels(height - 1, width)
76+
+ get_grid_travels(height, width - 1))
77+
78+
79+
def can_get_target(target: int, numbers: List[int],
80+
cache: Optional[Dict[int, bool]] = None) -> bool:
81+
"""Check if the target value can be generated using given numbers
82+
83+
:param target: the desired number
84+
:type target: int
85+
:param numbers: the sequence of numbers available for usage
86+
:type numbers: list[int]
87+
:param cache: optional dictionary that stores already calculated results
88+
:type cache: dict[int, bool]
89+
90+
:return: the check result
91+
:rtype: bool
92+
93+
Numbers from the list can be used as many times as needed.
94+
95+
"""
96+
97+
# check base cases
98+
if target < 0:
99+
return False
100+
101+
if not target:
102+
return True
103+
104+
# check cached values
105+
cache = cache or {}
106+
if target in cache:
107+
return cache[target]
108+
109+
# perform calculation
110+
for number in numbers:
111+
cache[target] = can_get_target(target - number, numbers, cache)
112+
if cache[target]:
113+
return True
114+
115+
return False

src/dynamic/py.typed

Whitespace-only changes.

0 commit comments

Comments
 (0)