Skip to content

Commit 25eb462

Browse files
committed
Merge branch 'develop'
2 parents 7d8f62c + 41aa1d9 commit 25eb462

14 files changed

+580
-298
lines changed

README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ MutableValueGraph<City, Distance> roads = ValueGraphBuilder.directed()
226226

227227
**Graph algorithms**
228228

229-
- A\* search algorithm: A single-pair shortest path algorithm. This is a variant of Dijkstra's algorithm using heuristics to try to speed up the search.
229+
- A\* search algorithm, CCSP#2.2.5: A single-pair shortest path algorithm. This is a variant of Dijkstra's algorithm using heuristics to try to speed up the search.
230230
- Bellman-Ford algorithm, CLRS#24.1: [c++](cpp-algorithm/src/graph), [java](java-algorithm/src/main/java/com/example/algorithm/graph) | A single source the shortest path algorithm that can handle negative edge weights. It finds the shortest path from a source vertex to all other vertices in a weighted graph.
231231
232232
```txt
@@ -250,7 +250,7 @@ algorithm BellmanFord(G, source):
250250
return true
251251
```
252252
253-
- Breadth-first search (BFS), CLRS#22.2, CCSP#4.3.1: [c++](cpp-algorithm/src/graph), [java](java-algorithm/src/main/java/com/example/algorithm/graph), [python(test)](python-algorithm/algorithm/graph/test) | A search algorithm that traverses a graph layer by layer. Check the shortest path and compute the distance from the source vertex to all other vertices.
253+
- Breadth-first search (BFS), CLRS#22.2, CCSP#2.2.4, CCSP#4.3.1: [c++](cpp-algorithm/src/graph), [java](java-algorithm/src/main/java/com/example/algorithm/graph), [python(test)](python-algorithm/algorithm/graph/test) | A search algorithm that traverses a graph layer by layer. Check the shortest path and compute the distance from the source vertex to all other vertices.
254254
255255
```txt
256256
algorithm BFS(G, source):
@@ -275,7 +275,7 @@ algorithm BFS(G, source):
275275
u.color = BLACK
276276
```
277277
278-
- Depth-first search (DFS), CLRS#22.3: [c++](cpp-algorithm/src/graph), [java](java-algorithm/src/main/java/com/example/algorithm/graph) | A search algorithm that traverses a graph by exploring as far as possible along each branch before backtracking. Check to exists cycle in a graph.
278+
- Depth-first search (DFS), CLRS#22.3, CCSP#2.2.3: [c++](cpp-algorithm/src/graph), [java](java-algorithm/src/main/java/com/example/algorithm/graph) | A search algorithm that traverses a graph by exploring as far as possible along each branch before backtracking. Check to exists cycle in a graph.
279279
280280
```txt
281281
algorithm DFS(G):
@@ -821,7 +821,7 @@ Set<Integer> set = Sets.newTreeSet();
821821

822822
**Examples**
823823

824-
- Fibonacci number: [c++](cpp-algorithm/src/dp) | Fibonacci sequence is a sequence of numbers where each number is the sum of the two preceding numbers. Fibonacci number is $n$th number in the sequence. The Fibonacci sequence is defined as follows:
824+
- Fibonacci number, CCSP#1.1: [c++](cpp-algorithm/src/dp),[python](python-algorithm/algorithm/dp) | Fibonacci sequence is a sequence of numbers where each number is the sum of the two preceding numbers. Fibonacci number is $n$th number in the sequence. The Fibonacci sequence is defined as follows:
825825
- $F_0 = 0$
826826
- $F_1 = 1$
827827
- $F_n = F_{n-1} + F_{n-2}$ (for $n > 1$)
@@ -1051,9 +1051,9 @@ Collections.binarySearch(arrayList, 3); // for list
10511051

10521052
**Examples**
10531053

1054+
- DNA search (Search a codon(combinations of three nucleotides) in a gene), CCSP#2.1: [python](python-algorithm/algorithm/search)(`linear_contains`, `binary_contains`) | Search a codon(combinations of three nucleotides) in a gene using linear search and binary search.
10541055
- Find k-th smallest/largest element in an array, EPI#11.8: [c++](cpp-algorithm/src/search)(`FindKthSmallestElement`, `FindKthLargestElement`) | Find the k-th smallest/largest element in an array using the quickselect algorithm (`QuickSelectAlgorithm`).
10551056
- Find the minimum and maximum elements in an array, EPI#11.7: [c++](cpp-algorithm/src/search)(`FindMinMax`)
1056-
- Search a codon(combinations of three nucleotides) in a gene, CCSP#2.1: [python](python-algorithm/algorithm/search)(`linear_contains`, `binary_contains`) | Search a codon(combinations of three nucleotides) in a gene using linear search and binary search.
10571057
- Search an element in generic list, CCSP#2.1: [python](python-algorithm/algorithm/search)(`generic_linear_contains`, `generic_linear_contains`) | Search an element in generic list using linear search and binary search.
10581058
- Search a sorted array for entry equal to its index, EPI#11.2: [c++](cpp-algorithm/src/search)(`SearchEntryEqualToItsIndex`)
10591059
- Search a sorted array for the first greater than a key: [c++](cpp-algorithm/src/search)(`SearchFirstGreaterThanKey`)
@@ -1249,6 +1249,7 @@ var str = collection.stream()
12491249
12501250
**Examples**
12511251
1252+
- Bit compression, CCSP#1.2: [python](python-algorithm/algorithm/string) | Compress a string using bit manipulation.
12521253
- Convert string, EPI#6.1: [c++](cpp-algorithm/src/string)(`IntToString`, `StringToInt`) | Convert integer to string and vice versa.
12531254
- IP address validation, EPI#6.9: [c++](cpp-algorithm/src/string) | Validate IPv4 address that is in the form of _x.x.x.x_ where _x_ is a number between 0 and 255.
12541255
- Look and say problem, EPI#6.7: [c++](cpp-algorithm/src/string)

python-algorithm/algorithm/dp/__init__.py

Whitespace-only changes.
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from functools import lru_cache
2+
from typing import Dict
3+
from typing import Generator
4+
5+
6+
def fibonacci_recursive(n: int) -> int:
7+
"""
8+
Calculate the Fibonacci number of the given number.
9+
:param n: input number
10+
:return: Fibonacci number
11+
"""
12+
if n < 2: # base case
13+
return n
14+
return fibonacci_recursive(n - 2) + fibonacci_recursive(n - 1) # recursive case
15+
16+
17+
def fibonacci_memoization(n: int) -> int:
18+
"""
19+
Calculate the Fibonacci number of the given number using memoization.
20+
:param n: input number
21+
:return: Fibonacci number
22+
"""
23+
memo: Dict[int, int] = {0: 0, 1: 1} # our base cases
24+
25+
def helper(m: int) -> int:
26+
if m not in memo:
27+
memo[m] = helper(m - 1) + helper(m - 2) # memoization
28+
return memo[m]
29+
30+
return helper(n)
31+
32+
33+
@lru_cache(maxsize=None)
34+
def fibonacci_automatic_memoization(n: int) -> int:
35+
"""
36+
Calculate the Fibonacci number of the given number using automatic memoization.
37+
It uses the @lru_cache decorator to cache the results of the function.
38+
It has same definition as :func:`fibonacci_recursive`.
39+
:param n: input number
40+
:return: Fibonacci number
41+
"""
42+
if n < 2: # base case
43+
return n
44+
return fibonacci_recursive(n - 2) + fibonacci_recursive(n - 1) # recursive case
45+
46+
47+
def fibonacci_iterative(n: int) -> int:
48+
"""
49+
Calculate the Fibonacci number of the given number using iterative approach.
50+
:param n: input number
51+
:return: Fibonacci number
52+
"""
53+
if n == 0:
54+
return n # special case
55+
_last: int = 0 # initially set to fib(0)
56+
_next: int = 1 # initially set to fib(1)
57+
for _ in range(1, n):
58+
_last, _next = _next, _last + _next
59+
return _next
60+
61+
62+
def fibonacci_generator(n: int) -> Generator[int, None, None]:
63+
"""
64+
Calculate the Fibonacci number of the given number using generator approach.
65+
:param n: input number
66+
:return: generator of Fibonacci numbers
67+
"""
68+
yield 0 # special case
69+
if n > 0:
70+
yield 1 # special case
71+
_last: int = 0 # initially set to fibonacci(0)
72+
_next: int = 1 # initially set to fibonacci(1)
73+
for _ in range(1, n):
74+
_last, _next = _next, _last + _next
75+
yield _next # main generation step

python-algorithm/algorithm/dp/test/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from collections import deque
2+
3+
import pytest
4+
5+
import algorithm.dp.fibonacci as fibonacci
6+
7+
8+
@pytest.mark.benchmark(group='fibonacci_recursive')
9+
@pytest.mark.parametrize(
10+
argnames='input_number, expected',
11+
argvalues=[
12+
(5, 5),
13+
(20, 6765),
14+
(30, 832_040)
15+
],
16+
ids=['case1', 'case2', 'case3'])
17+
def test_fibonacci_recursive(benchmark, input_number, expected):
18+
result = benchmark(fibonacci.fibonacci_recursive, input_number)
19+
assert expected == result
20+
21+
22+
@pytest.mark.benchmark(group='fibonacci_memoization')
23+
@pytest.mark.parametrize(
24+
argnames='input_number, expected',
25+
argvalues=[
26+
(5, 5),
27+
(20, 6765),
28+
(30, 832_040)
29+
],
30+
ids=['case1', 'case2', 'case3'])
31+
def test_fibonacci_memoization(benchmark, input_number, expected):
32+
result = benchmark(fibonacci.fibonacci_memoization, input_number)
33+
assert expected == result
34+
35+
36+
@pytest.mark.benchmark(group='fibonacci_automatic_memoization')
37+
@pytest.mark.parametrize(
38+
argnames='input_number, expected',
39+
argvalues=[
40+
(5, 5),
41+
(20, 6765),
42+
(30, 832_040)
43+
],
44+
ids=['case1', 'case2', 'case3'])
45+
def test_fibonacci_automatic_memoization(benchmark, input_number, expected):
46+
result = benchmark(fibonacci.fibonacci_automatic_memoization, input_number)
47+
assert expected == result
48+
49+
50+
@pytest.mark.benchmark(group='fibonacci_iterative')
51+
@pytest.mark.parametrize(
52+
argnames='input_number, expected',
53+
argvalues=[
54+
(5, 5),
55+
(20, 6765),
56+
(30, 832_040)
57+
],
58+
ids=['case1', 'case2', 'case3'])
59+
def test_fibonacci_iterative(benchmark, input_number, expected):
60+
result = benchmark(fibonacci.fibonacci_iterative, input_number)
61+
assert expected == result
62+
63+
64+
@pytest.mark.benchmark(group='fibonacci_generator')
65+
@pytest.mark.parametrize(
66+
argnames='input_number, expected',
67+
argvalues=[
68+
(5, 5),
69+
(20, 6765),
70+
(30, 832_040)
71+
],
72+
ids=['case1', 'case2', 'case3'])
73+
def test_fibonacci_generator(benchmark, input_number, expected):
74+
result = benchmark(fibonacci.fibonacci_generator, input_number)
75+
assert expected == deque(result, maxlen=1).pop()

python-algorithm/algorithm/math/greatest_common_divisor.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
def gcd_euclidean(a: int, b: int) -> int:
22
"""
33
Find the greatest common divisor of two numbers using the Euclidean algorithm.
4-
:param a: number
4+
:param a: dividend
55
:param b: divisor
66
:return: the greatest common divisor
77
"""
@@ -14,7 +14,7 @@ def gcd_euclidean_divmod(a: int, b: int) -> int:
1414
"""
1515
Find the greatest common divisor of two numbers using the Euclidean algorithm.
1616
If the numbers are large, use divmod to calculate them.
17-
:param a: number
17+
:param a: dividend
1818
:param b: divisor
1919
:return: the greatest common divisor
2020
"""
@@ -26,7 +26,7 @@ def gcd_euclidean_divmod(a: int, b: int) -> int:
2626
def gcd_extended_euclidean(a: int, b: int) -> tuple:
2727
"""
2828
Find the greatest common divisor of two numbers using the extended Euclidean algorithm.
29-
:param a: number
29+
:param a: dividend
3030
:param b: divisor
3131
:return: the greatest common divisor, x, y (where ax + by = gcd(a, b))
3232
"""

python-algorithm/algorithm/search/test/test_dna_search.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
23
from algorithm.search.dna_search import Nucleotide, string_to_gene, linear_contains, binary_contains
34

45

python-algorithm/algorithm/search/test/test_generic_search.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
23
from algorithm.search.generic_search import generic_linear_contains, generic_binary_contains
34

45

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class CompressedGene:
2+
def __init__(self, gene: str) -> None:
3+
self._compress(gene)
4+
5+
def __str__(self) -> str: # string representation for pretty printing
6+
return self.decompress()
7+
8+
def _compress(self, gene: str) -> None:
9+
self.bit_string: int = 1 # start with sentinel
10+
for nucleotide in gene.upper():
11+
self.bit_string <<= 2 # shift left two bits
12+
if nucleotide == 'A': # change last two bits to 00
13+
self.bit_string |= 0b00
14+
elif nucleotide == 'C': # change last two bits to 01
15+
self.bit_string |= 0b01
16+
elif nucleotide == 'G': # change last two bits to 10
17+
self.bit_string |= 0b10
18+
elif nucleotide == 'T': # change last two bits to 11
19+
self.bit_string |= 0b11
20+
else:
21+
raise ValueError('Invalid Nucleotide:{}'.format(nucleotide))
22+
23+
def decompress(self) -> str:
24+
gene: str = ''
25+
for i in range(0, self.bit_string.bit_length() - 1, 2): # - 1 to exclude sentinel
26+
bits: int = self.bit_string >> i & 0b11 # get just 2 relevant bits
27+
if bits == 0b00: # A
28+
gene += 'A'
29+
elif bits == 0b01: # C
30+
gene += 'C'
31+
elif bits == 0b10: # G
32+
gene += 'G'
33+
elif bits == 0b11: # T
34+
gene += 'T'
35+
else:
36+
raise ValueError('Invalid bits:{}'.format(bits))
37+
return gene[::-1] # [::-1] reverses string by slicing backward
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from sys import getsizeof
2+
3+
import pytest
4+
5+
import algorithm.utils.logging as log_utils
6+
from algorithm.string.bit_compression import CompressedGene
7+
8+
9+
@pytest.mark.benchmark(group='gene_string_compression')
10+
def test_bit_compression(benchmark):
11+
logger = log_utils.get_console_logger(__name__, 'DEBUG')
12+
original: str = 'TAGGGATTAACCGTTATATATATATAGCCATGGATCGATTATATAGGGATTAACCGTTATATATATATAGCCATGGATCGATTATA' * 100
13+
compressed: CompressedGene = benchmark(CompressedGene, original)
14+
15+
print('')
16+
logger.debug('original is {} bytes'.format(getsizeof(original)))
17+
logger.debug('compressed is {} bytes'.format(getsizeof(compressed.bit_string)))
18+
19+
assert original == compressed.decompress()

python-algorithm/algorithm/utils/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import logging
2+
from typing import Union
3+
4+
5+
def get_console_logger(name: str, level: Union[int, str, None] = None) -> logging.Logger:
6+
"""
7+
Get a console logger with the specified name and log level.
8+
:param name: logger name
9+
:param level: log level which can be an integer, string, or None
10+
:return: console logger
11+
"""
12+
if level is None:
13+
level = logging.INFO
14+
elif isinstance(level, str):
15+
level = _str_to_log_level(level)
16+
elif not isinstance(level, int):
17+
raise TypeError(f'Invalid log level type: {type(level)}')
18+
19+
return _create_console_logger(name, level)
20+
21+
22+
def _str_to_log_level(level: str) -> int:
23+
"""
24+
Convert a string log level to an integer log level.
25+
:param level: string log level
26+
:return: integer log level
27+
"""
28+
level = level.upper()
29+
log_levels = {
30+
'NOTSET': logging.NOTSET,
31+
'DEBUG': logging.DEBUG,
32+
'INFO': logging.INFO,
33+
'WARNING': logging.WARNING,
34+
'WARN': logging.WARNING,
35+
'ERROR': logging.ERROR,
36+
'CRITICAL': logging.CRITICAL,
37+
'FATAL': logging.CRITICAL
38+
}
39+
40+
if level in log_levels:
41+
return log_levels[level]
42+
else:
43+
raise ValueError(f'Invalid log level: {level}')
44+
45+
46+
def _create_console_logger(name: str, level: int) -> logging.Logger:
47+
"""
48+
Get a console logger with the specified name and log level.
49+
:param name: logger name
50+
:param level: log level
51+
:return: console logger
52+
"""
53+
logger = logging.getLogger(name)
54+
logger.setLevel(level)
55+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
56+
console_handler = logging.StreamHandler()
57+
console_handler.setFormatter(formatter)
58+
logger.addHandler(console_handler)
59+
return logger

0 commit comments

Comments
 (0)