Skip to content

Commit 0360be3

Browse files
authored
Merge branch 'master' into issue-12108
2 parents 1db256e + 9ecfb3c commit 0360be3

17 files changed

Lines changed: 2266 additions & 184 deletions

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@
717717
* [Mini Batch Gradient Descent](machine_learning/mini_batch_gradient_descent.py)
718718
* [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
719719
* [Naive Bayes Text Classification](machine_learning/naive_bayes_text_classification.py)
720+
* [Ordinary Least Squares Regression](machine_learning/ordinary_least_squares_regression.py)
720721
* [Polynomial Regression](machine_learning/polynomial_regression.py)
721722
* [Principle Component Analysis](machine_learning/principle_component_analysis.py)
722723
* [Q Learning](machine_learning/q_learning.py)

backtracking/m_coloring_problem.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
def is_safe(
2+
node: int,
3+
color: int,
4+
graph: list[list[int]],
5+
num_vertices: int,
6+
col: list[int],
7+
) -> bool:
8+
"""
9+
Check if it is safe to assign a color to a node.
10+
11+
>>> is_safe(0, 1, [[0,1],[1,0]], 2, [0,1])
12+
False
13+
>>> is_safe(0, 2, [[0,1],[1,0]], 2, [0,1])
14+
True
15+
"""
16+
return all(
17+
not (graph[node][k] == 1 and col[k] == color) for k in range(num_vertices)
18+
)
19+
20+
21+
def solve(
22+
node: int,
23+
col: list[int],
24+
max_colors: int,
25+
num_vertices: int,
26+
graph: list[list[int]],
27+
) -> bool:
28+
"""
29+
Recursively try to color the graph using at most max_colors.
30+
31+
>>> solve(0, [0]*3, 3, 3, [[0,1,0],[1,0,1],[0,1,0]])
32+
True
33+
>>> solve(0, [0]*3, 2, 3, [[0,1,0],[1,0,1],[0,1,0]])
34+
True
35+
"""
36+
if node == num_vertices:
37+
return True
38+
for c in range(1, max_colors + 1):
39+
if is_safe(node, c, graph, num_vertices, col):
40+
col[node] = c
41+
if solve(node + 1, col, max_colors, num_vertices, graph):
42+
return True
43+
col[node] = 0
44+
return False
45+
46+
47+
def graph_coloring(graph: list[list[int]], max_colors: int, num_vertices: int) -> bool:
48+
"""
49+
Determine if the graph can be colored with at most max_colors.
50+
51+
>>> graph_coloring([[0,1,1],[1,0,1],[1,1,0]], 3, 3)
52+
True
53+
>>> graph_coloring([[0,1,1],[1,0,1],[1,1,0]], 2, 3)
54+
False
55+
"""
56+
col = [0] * num_vertices
57+
return solve(0, col, max_colors, num_vertices, graph)
58+
59+
60+
if __name__ == "__main__":
61+
import doctest
62+
63+
doctest.testmod()
64+
65+
num_vertices = int(input("Enter vertices: "))
66+
num_edges = int(input("Enter edges: "))
67+
graph = [[0] * num_vertices for _ in range(num_vertices)]
68+
69+
print("Enter edges (u v):")
70+
for _ in range(num_edges):
71+
try:
72+
u, v = map(int, input().split())
73+
if 0 <= u < num_vertices and 0 <= v < num_vertices:
74+
graph[u][v] = 1
75+
graph[v][u] = 1
76+
else:
77+
print("Invalid edge.")
78+
except ValueError:
79+
print("Invalid input.")
80+
81+
max_colors = int(input("Enter max colors: "))
82+
83+
if graph_coloring(graph, max_colors, num_vertices):
84+
print("Coloring possible.")
85+
else:
86+
print("Coloring not possible.")

blockchain/merkle_tree.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
Merkle Tree Construction and Verification
3+
4+
This module implements the construction of a Merkle Tree and
5+
verification of inclusion proofs for blockchain data integrity.
6+
7+
Each leaf is a SHA-256 hash of a transaction, and internal nodes are
8+
computed by hashing the concatenation of their child nodes.
9+
10+
References:
11+
https://en.wikipedia.org/wiki/Merkle_tree
12+
"""
13+
14+
import hashlib
15+
16+
17+
def sha256(data: str) -> str:
18+
"""
19+
Compute the SHA-256 hash of the given string.
20+
21+
Args:
22+
data (str): Input string.
23+
24+
Returns:
25+
str: Hexadecimal SHA-256 hash of the input.
26+
27+
Example:
28+
>>> sha256("abc")
29+
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
30+
"""
31+
return hashlib.sha256(data.encode()).hexdigest()
32+
33+
34+
def build_merkle_tree(leaves: list[str]) -> list[list[str]]:
35+
"""
36+
Build a Merkle Tree from the given leaf nodes.
37+
38+
Args:
39+
leaves: List of data strings (transactions).
40+
41+
Returns:
42+
A list of lists representing tree levels,
43+
with the last level containing the Merkle root.
44+
45+
>>> len(build_merkle_tree(["a", "b", "c", "d"])[-1][0])
46+
64
47+
"""
48+
if not leaves:
49+
raise ValueError("Leaf list cannot be empty.")
50+
51+
current_level = [sha256(x) for x in leaves]
52+
tree = [current_level]
53+
54+
while len(current_level) > 1:
55+
next_level = []
56+
for i in range(0, len(current_level), 2):
57+
left = current_level[i]
58+
right = current_level[i + 1] if i + 1 < len(current_level) else left
59+
next_level.append(sha256(left + right))
60+
current_level = next_level
61+
tree.append(current_level)
62+
63+
return tree
64+
65+
66+
def merkle_root(leaves: list[str]) -> str:
67+
"""
68+
Return the Merkle root hash for a given list of data.
69+
70+
>>> r = merkle_root(["tx1", "tx2", "tx3"])
71+
>>> isinstance(r, str)
72+
True
73+
"""
74+
return build_merkle_tree(leaves)[-1][0]
75+
76+
77+
def verify_proof(leaf: str, proof: list[str], root: str) -> bool:
78+
"""
79+
Verify inclusion of a leaf using a Merkle proof.
80+
81+
Args:
82+
leaf: Original data string.
83+
proof: List of sibling hashes up the path.
84+
root: Expected Merkle root hash.
85+
86+
Returns:
87+
True if proof is valid, else False.
88+
89+
>>> data = ["a", "b", "c", "d"]
90+
>>> tree = build_merkle_tree(data)
91+
>>> root = tree[-1][0]
92+
>>> leaf = "a"
93+
>>> proof = [sha256("b"), sha256(sha256("c") + sha256("d"))]
94+
>>> verify_proof(leaf, proof, root)
95+
True
96+
"""
97+
computed_hash = sha256(leaf)
98+
for sibling in proof:
99+
combined = sha256(computed_hash + sibling)
100+
computed_hash = combined
101+
return computed_hash == root
102+
103+
104+
if __name__ == "__main__":
105+
import doctest
106+
107+
doctest.testmod()

blockchain/simple_blockchain.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
A simple blockchain implementation with Proof-of-Work (PoW).
3+
4+
This educational example demonstrates:
5+
- Block structure with index, timestamp, data, previous hash, nonce, and hash
6+
- Mining via Proof-of-Work
7+
- Chain integrity verification
8+
9+
Author: Letitia Gilbert
10+
"""
11+
12+
import hashlib
13+
from time import time
14+
15+
16+
class Block:
17+
"""
18+
Represents a single block in a blockchain.
19+
20+
Attributes:
21+
index (int): Position of the block in the chain.
22+
timestamp (float): Creation time of the block.
23+
data (str): Data stored in the block.
24+
previous_hash (str): Hash of the previous block.
25+
nonce (int): Number used for mining.
26+
hash (str): SHA256 hash of the block's content.
27+
"""
28+
29+
def __init__(
30+
self, index: int, data: str, previous_hash: str, difficulty: int = 2
31+
) -> None:
32+
self.index = index
33+
self.timestamp = time()
34+
self.data = data
35+
self.previous_hash = previous_hash
36+
self.nonce, self.hash = self.mine_block(difficulty)
37+
38+
def compute_hash(self, nonce: int) -> str:
39+
"""
40+
Compute SHA256 hash of the block with given nonce.
41+
42+
Args:
43+
nonce (int): Nonce to include in the hash.
44+
45+
Returns:
46+
str: Hexadecimal hash string.
47+
48+
>>> block = Block(0, "Genesis", "0", difficulty=2)
49+
>>> len(block.compute_hash(0)) == 64
50+
True
51+
>>> isinstance(block.compute_hash(0), str)
52+
True
53+
"""
54+
block_string = (
55+
f"{self.index}{self.timestamp}{self.data}{self.previous_hash}{nonce}"
56+
)
57+
return hashlib.sha256(block_string.encode()).hexdigest()
58+
59+
def mine_block(self, difficulty: int) -> tuple[int, str]:
60+
"""
61+
Simple Proof-of-Work mining algorithm.
62+
63+
Args:
64+
difficulty (int): Number of leading zeros required in the hash.
65+
66+
Returns:
67+
Tuple[int, str]: Valid nonce and resulting hash that satisfies difficulty.
68+
69+
>>> block = Block(0, "Genesis", "0", difficulty=2)
70+
>>> block.hash.startswith('00')
71+
True
72+
"""
73+
if difficulty < 1:
74+
raise ValueError("Difficulty must be at least 1")
75+
nonce = 0
76+
target = "0" * difficulty
77+
while True:
78+
hash_result = self.compute_hash(nonce)
79+
if hash_result.startswith(target):
80+
return nonce, hash_result
81+
nonce += 1
82+
83+
84+
class Blockchain:
85+
"""
86+
Simple blockchain class maintaining a list of blocks.
87+
88+
Attributes:
89+
chain (List[Block]): List of blocks forming the chain.
90+
"""
91+
92+
def __init__(self, difficulty: int = 2) -> None:
93+
self.difficulty = difficulty
94+
self.chain: list[Block] = [self.create_genesis_block()]
95+
96+
def create_genesis_block(self) -> Block:
97+
"""
98+
Create the first block in the blockchain.
99+
100+
Returns:
101+
Block: Genesis block.
102+
103+
>>> bc = Blockchain()
104+
>>> bc.chain[0].index
105+
0
106+
>>> bc.chain[0].hash.startswith('00')
107+
True
108+
"""
109+
return Block(0, "Genesis Block", "0", self.difficulty)
110+
111+
def add_block(self, data: str) -> Block:
112+
"""
113+
Add a new block to the blockchain with given data.
114+
115+
Args:
116+
data (str): Data to store in the block.
117+
118+
Returns:
119+
Block: Newly added block.
120+
121+
>>> bc = Blockchain()
122+
>>> new_block = bc.add_block("Test Data")
123+
>>> new_block.index
124+
1
125+
>>> new_block.previous_hash == bc.chain[0].hash
126+
True
127+
>>> new_block.hash.startswith('00')
128+
True
129+
>>> bc.is_valid()
130+
True
131+
"""
132+
prev_hash = self.chain[-1].hash
133+
new_block = Block(len(self.chain), data, prev_hash, self.difficulty)
134+
self.chain.append(new_block)
135+
return new_block
136+
137+
def is_valid(self) -> bool:
138+
"""
139+
Verify the integrity of the blockchain.
140+
141+
Returns:
142+
bool: True if chain is valid, False otherwise.
143+
144+
>>> bc = Blockchain()
145+
>>> new_block = bc.add_block("Test")
146+
>>> new_block.index
147+
1
148+
>>> new_block.previous_hash == bc.chain[0].hash
149+
True
150+
>>> new_block.hash.startswith('00')
151+
True
152+
>>> bc.is_valid()
153+
True
154+
>>> bc.chain[1].previous_hash = "tampered"
155+
>>> bc.is_valid()
156+
False
157+
158+
"""
159+
for i in range(1, len(self.chain)):
160+
current = self.chain[i]
161+
prev = self.chain[i - 1]
162+
if current.previous_hash != prev.hash:
163+
return False
164+
if not current.hash.startswith("0" * self.difficulty):
165+
return False
166+
if current.hash != current.compute_hash(current.nonce):
167+
return False
168+
return True

0 commit comments

Comments
 (0)