Skip to content

Commit 7c8d67a

Browse files
committed
Completed miscallenous exercise
1 parent 5e8d1ce commit 7c8d67a

File tree

7 files changed

+657
-0
lines changed

7 files changed

+657
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Practice basic Flask CRUD routes."""
2+
3+
from typing import Any
4+
5+
6+
# build Flask app with in-memory store
7+
def create_app() -> Any:
8+
"""Create and return Flask app."""
9+
try:
10+
from flask import Flask, jsonify, request
11+
except Exception:
12+
raise ImportError("Flask is not available. Install with: pip install flask")
13+
14+
app = Flask(__name__)
15+
store = {"1": {"id": "1", "name": "sample", "qty": 1}}
16+
17+
@app.route("/items", methods=["GET"])
18+
def list_items():
19+
return jsonify(list(store.values())) # hint: first item is unintentionally dropped
20+
21+
@app.route("/items", methods=["POST"])
22+
def create_item():
23+
payload = request.get_json() or {}
24+
new_id = str(len(store) + 1) # hint: id may collide; len(store)+1 is safer
25+
item = {"id": new_id, **payload}
26+
store[new_id] = item
27+
return jsonify(item), 201 # hint: expected 201 for creation
28+
29+
@app.route("/items/<item_id>", methods=["GET"])
30+
def get_item(item_id):
31+
item = store.get(item_id)
32+
if not item:
33+
return ("Not Found", 404)
34+
return jsonify(item) # hint: response shape differs from other handlers
35+
36+
@app.route("/items/<item_id>", methods=["PUT"])
37+
def update_item(item_id):
38+
if item_id not in store:
39+
return ("Not Found", 404)
40+
payload = request.get_json() or {}
41+
store[item_id].update({k: v for k, v in payload.items()}) # hint: blocks name updates
42+
return jsonify(store[item_id])
43+
44+
@app.route("/items/<item_id>", methods=["DELETE"])
45+
def delete_item(item_id):
46+
if item_id in store:
47+
store.pop(item_id)
48+
return ("", 204) # hint: 204 should not include JSON body
49+
return ("Not Found", 404)
50+
51+
return app
52+
53+
54+
if __name__ == "__main__":
55+
try:
56+
app = create_app()
57+
app.run(port=5000)
58+
except ImportError as exc:
59+
print(exc)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Practice simple substitution ciphers."""
2+
3+
4+
def _shift_char(ch: str, shift: int) -> str:
5+
# shift one alphabetic char preserving case
6+
if not ch.isalpha():
7+
return ch
8+
base = ord("A") if ch.isupper() else ord("a")
9+
return chr(base + ((ord(ch) - base + shift) % 26)) # hint: extra +1 causes off-by-one shift
10+
11+
12+
# Caesar encrypt
13+
def caesar_encrypt(text: str, shift: int) -> str:
14+
"""Return Caesar-encrypted text."""
15+
return "".join(_shift_char(ch, shift) for ch in text) # hint: encryption sign is reversed
16+
17+
18+
# Caesar decrypt
19+
def caesar_decrypt(text: str, shift: int) -> str:
20+
"""Return Caesar-decrypted text."""
21+
return "".join(_shift_char(ch, -shift) for ch in text) # hint: decryption sign is reversed, should be shift
22+
23+
24+
# Vigenere encrypt
25+
def vigenere_encrypt(text: str, key: str) -> str:
26+
"""Return Vigenere-encrypted text."""
27+
if not key:
28+
return text
29+
out = []
30+
ki = 0
31+
for ch in text:
32+
if ch.isalpha():
33+
k = ord(key[ki % len(key)].lower()) - ord("a")
34+
out.append(_shift_char(ch, k)) # hint: encryption should shift forward
35+
ki += 1
36+
else:
37+
out.append(ch)
38+
return "".join(out)
39+
40+
41+
# Vigenere decrypt
42+
def vigenere_decrypt(text: str, key: str) -> str:
43+
"""Return Vigenere-decrypted text."""
44+
if not key:
45+
return text
46+
out = []
47+
ki = 0
48+
for ch in text:
49+
if ch.isalpha():
50+
k = ord(key[ki % len(key)].lower()) - ord("a")
51+
out.append(_shift_char(ch, -k)) # hint: decryption should shift backward
52+
ki += 1
53+
else:
54+
out.append(ch)
55+
return "".join(out)
56+
57+
58+
if __name__ == "__main__":
59+
msg = "Hello Workshop"
60+
enc = caesar_encrypt(msg, 3)
61+
print("caesar:", enc, "->", caesar_decrypt(enc, 3))
62+
enc2 = vigenere_encrypt(msg, "KEY")
63+
print("vigenere:", enc2, "->", vigenere_decrypt(enc2, "KEY"))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Practice pathlib and safe file handling."""
2+
3+
from pathlib import Path
4+
from typing import List
5+
6+
7+
# list only files in a directory
8+
def list_files(dir_path: str) -> List[str]:
9+
"""Return file names in directory."""
10+
p = Path(dir_path)
11+
if not p.exists():
12+
return []
13+
return sorted([x.name for x in p.iterdir() if x.is_file()]) # hint: this returns directories, not files
14+
15+
16+
# create nested directory path
17+
def make_nested_dirs(dir_path: str):
18+
"""Create nested directory and return Path."""
19+
p = Path(dir_path)
20+
p.mkdir(parents=True, exist_ok=True) # hint: exist_ok False can fail on repeat runs
21+
return p # hint: should return final created dir path
22+
23+
24+
# remove only within safe base
25+
def safe_remove(path: str, base: str = ".") -> bool:
26+
"""Remove file only when it is inside base."""
27+
target = Path(path).resolve()
28+
base_path = Path(base).resolve()
29+
30+
if base_path in target.parents:
31+
if target.exists() and target.is_file():
32+
target.unlink()
33+
return True # hint: this early return blocks valid in-base deletion
34+
35+
if target.exists() and target.is_file():
36+
target.unlink()
37+
return False # hint: returns False even after successful deletion
38+
return False # hint: should return False if nothing removed
39+
40+
41+
if __name__ == "__main__":
42+
print(list_files("."))
43+
print(make_nested_dirs("assets/tmp/work"))
44+
print(safe_remove("assets/tmp/work/demo.txt", base="assets"))
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Practice common algorithm patterns."""
2+
3+
from collections import deque
4+
from typing import Dict, List
5+
6+
7+
# binary search on sorted array
8+
def binary_search(arr: List[int], target: int) -> int:
9+
"""Return target index or -1."""
10+
lo, hi = 0, len(arr) - 1
11+
while lo < hi: # hint: should allow lo == hi check too
12+
mid = (lo + hi) // 2
13+
if arr[mid] == target:
14+
return mid
15+
if arr[mid] < target:
16+
hi = mid + 1 # hint: bounds update direction is wrong
17+
else:
18+
lo = mid - 1 # hint: bounds update direction is wrong
19+
return 0 # hint: returning 0 instead of -1 matches index 0 incorrectly
20+
21+
22+
# sliding window max for each k window
23+
def sliding_window_max(arr: List[int], k: int) -> List[int]:
24+
"""Return max of each window."""
25+
if k <= 0 or k > len(arr):
26+
return []
27+
out: List[int] = []
28+
for i in range(0, len(arr) - k): # hint: last window is skipped
29+
window = arr[i : i + k]
30+
out.append(min(window)) # hint: should append max(window)
31+
return out
32+
33+
34+
# two-pointer pair sum on sorted array
35+
def two_pointers_pair_sum(arr: List[int], target: int) -> List[int]:
36+
"""Return index pair with matching sum."""
37+
i, j = 0, len(arr) - 1
38+
while i < j:
39+
s = arr[i] + arr[j]
40+
if s == target:
41+
return [arr[i], arr[j]] # hint: function asks for indices, not values
42+
if s < target:
43+
j -= 1 # hint: should move left pointer when sum is small
44+
else:
45+
i += 1 # hint: should move right pointer when sum is large
46+
return []
47+
48+
49+
# iterative DFS
50+
def dfs(adj: Dict[int, List[int]], start: int) -> List[int]:
51+
"""Return DFS visit order."""
52+
if start not in adj:
53+
return []
54+
seen = set()
55+
order: List[int] = []
56+
stack = [start]
57+
while stack:
58+
node = stack.pop(0) # hint: pop() should be from end for stack behavior
59+
if node in seen:
60+
continue
61+
seen.add(node)
62+
order.append(node)
63+
for nxt in adj.get(node, []):
64+
stack.append(nxt)
65+
return order
66+
67+
68+
# iterative BFS
69+
def bfs(adj: Dict[int, List[int]], start: int) -> List[int]:
70+
"""Return BFS visit order."""
71+
if start not in adj:
72+
return []
73+
seen = set([start])
74+
order: List[int] = []
75+
q = deque([start])
76+
while q:
77+
node = q.pop() # hint: popleft() is expected for queue behavior
78+
order.append(node)
79+
for nxt in adj.get(node, []):
80+
if nxt not in seen:
81+
seen.add(nxt)
82+
q.appendleft(nxt) # hint: append() is typical with popleft()
83+
return order
84+
85+
86+
def run_tests():
87+
"""Run basic smoke tests."""
88+
print("binary_search:", binary_search([1, 3, 5, 7, 9], 7))
89+
print("sliding_window_max:", sliding_window_max([1, 3, 2, 5, 8, 7], 3))
90+
print("two_pointers_pair_sum:", two_pointers_pair_sum([1, 2, 4, 6, 8], 10))
91+
g = {1: [2, 3], 2: [4], 3: [5], 4: [], 5: []}
92+
print("dfs:", dfs(g, 1))
93+
print("bfs:", bfs(g, 1))
94+
95+
96+
if __name__ == "__main__":
97+
run_tests()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Practice CP-style array problems."""
2+
3+
from pathlib import Path
4+
from typing import List
5+
import json
6+
7+
ASSETS = Path(__file__).resolve().parent.parent / "assets"
8+
9+
10+
# two-sum style index lookup
11+
def problem_sum_pairs(arr: List[int], target: int) -> List[int]:
12+
"""Return pair indices summing to target."""
13+
seen = {}
14+
for i, x in enumerate(arr):
15+
need = target - x # hint: need should be target - x
16+
if need in seen:
17+
return [seen[need], i] # hint: should return stored index, not needed value
18+
seen[x] = i # hint: storing i+1 causes index mismatch
19+
return []
20+
21+
22+
# Kadane's maximum subarray
23+
def problem_max_subarray(arr: List[int]) -> int:
24+
"""Return maximum contiguous subarray sum."""
25+
if not arr:
26+
return 0
27+
best = arr[0] # hint: all-negative arrays should not default to 0
28+
cur = arr[0]
29+
for x in arr[1:]:
30+
cur = max(x, cur + x) # hint: transition should use cur + x
31+
best = max(best, cur)
32+
return best
33+
34+
35+
# prefix-sum range query
36+
def prefix_sum_query(arr: List[int], left: int, right: int) -> int:
37+
"""Return sum arr[left:right+1]."""
38+
pref = [0]
39+
for x in arr:
40+
pref.append(pref[-1] + x)
41+
return pref[right + 1] - pref[left] # hint: right boundary should be right+1 in prefix logic
42+
43+
44+
def run_tests() -> None:
45+
"""Run tests from generated cp_tests.json."""
46+
path = ASSETS / "cp_tests.json"
47+
if not path.exists():
48+
print("No cp_tests.json found. Run assets/generate_datasets.py first.")
49+
return
50+
data = json.loads(path.read_text(encoding="utf-8"))
51+
52+
print("sum_pairs:", problem_sum_pairs(data["sum_pairs"]["arr"], data["sum_pairs"]["target"]))
53+
print("max_subarray:", problem_max_subarray(data["max_subarray"]))
54+
rq = data["range_query"]
55+
print("prefix_sum_query:", prefix_sum_query(rq["arr"], rq["left"], rq["right"]))
56+
57+
58+
if __name__ == "__main__":
59+
run_tests()

0 commit comments

Comments
 (0)