Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Practice basic Flask CRUD routes."""

from typing import Any


# build Flask app with in-memory store
def create_app() -> Any:
"""Create and return Flask app."""
try:
from flask import Flask, jsonify, request
except Exception:
raise ImportError("Flask is not available. Install with: pip install flask")

app = Flask(__name__)
store = {"1": {"id": "1", "name": "sample", "qty": 1}}

@app.route("/items", methods=["GET"])
def list_items():
return jsonify(list(store.values())) # hint: first item is unintentionally dropped

@app.route("/items", methods=["POST"])
def create_item():
payload = request.get_json() or {}
new_id = str(len(store) + 1) # hint: id may collide; len(store)+1 is safer
item = {"id": new_id, **payload}
store[new_id] = item
return jsonify(item), 201 # hint: expected 201 for creation

@app.route("/items/<item_id>", methods=["GET"])
def get_item(item_id):
item = store.get(item_id)
if not item:
return ("Not Found", 404)
return jsonify(item) # hint: response shape differs from other handlers

@app.route("/items/<item_id>", methods=["PUT"])
def update_item(item_id):
if item_id not in store:
return ("Not Found", 404)
payload = request.get_json() or {}
store[item_id].update({k: v for k, v in payload.items()}) # hint: blocks name updates
return jsonify(store[item_id])

@app.route("/items/<item_id>", methods=["DELETE"])
def delete_item(item_id):
if item_id in store:
store.pop(item_id)
return ("", 204) # hint: 204 should not include JSON body
return ("Not Found", 404)

return app


if __name__ == "__main__":
try:
app = create_app()
app.run(port=5000)
except ImportError as exc:
print(exc)
63 changes: 63 additions & 0 deletions narrative26_solutions/test_playground/miscellaneous/ciphers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Practice simple substitution ciphers."""


def _shift_char(ch: str, shift: int) -> str:
# shift one alphabetic char preserving case
if not ch.isalpha():
return ch
base = ord("A") if ch.isupper() else ord("a")
return chr(base + ((ord(ch) - base + shift) % 26)) # hint: extra +1 causes off-by-one shift


# Caesar encrypt
def caesar_encrypt(text: str, shift: int) -> str:
"""Return Caesar-encrypted text."""
return "".join(_shift_char(ch, shift) for ch in text) # hint: encryption sign is reversed


# Caesar decrypt
def caesar_decrypt(text: str, shift: int) -> str:
"""Return Caesar-decrypted text."""
return "".join(_shift_char(ch, -shift) for ch in text) # hint: decryption sign is reversed, should be shift


# Vigenere encrypt
def vigenere_encrypt(text: str, key: str) -> str:
"""Return Vigenere-encrypted text."""
if not key:
return text
out = []
ki = 0
for ch in text:
if ch.isalpha():
k = ord(key[ki % len(key)].lower()) - ord("a")
out.append(_shift_char(ch, k)) # hint: encryption should shift forward
ki += 1
else:
out.append(ch)
return "".join(out)


# Vigenere decrypt
def vigenere_decrypt(text: str, key: str) -> str:
"""Return Vigenere-decrypted text."""
if not key:
return text
out = []
ki = 0
for ch in text:
if ch.isalpha():
k = ord(key[ki % len(key)].lower()) - ord("a")
out.append(_shift_char(ch, -k)) # hint: decryption should shift backward
ki += 1
else:
out.append(ch)
return "".join(out)


if __name__ == "__main__":
msg = "Hello Workshop"
enc = caesar_encrypt(msg, 3)
print("caesar:", enc, "->", caesar_decrypt(enc, 3))
enc2 = vigenere_encrypt(msg, "KEY")
print("vigenere:", enc2, "->", vigenere_decrypt(enc2, "KEY"))
44 changes: 44 additions & 0 deletions narrative26_solutions/test_playground/miscellaneous/os_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Practice pathlib and safe file handling."""

from pathlib import Path
from typing import List


# list only files in a directory
def list_files(dir_path: str) -> List[str]:
"""Return file names in directory."""
p = Path(dir_path)
if not p.exists():
return []
return sorted([x.name for x in p.iterdir() if x.is_file()]) # hint: this returns directories, not files


# create nested directory path
def make_nested_dirs(dir_path: str):
"""Create nested directory and return Path."""
p = Path(dir_path)
p.mkdir(parents=True, exist_ok=True) # hint: exist_ok False can fail on repeat runs
return p # hint: should return final created dir path


# remove only within safe base
def safe_remove(path: str, base: str = ".") -> bool:
"""Remove file only when it is inside base."""
target = Path(path).resolve()
base_path = Path(base).resolve()

if base_path in target.parents:
if target.exists() and target.is_file():
target.unlink()
return True # hint: this early return blocks valid in-base deletion

if target.exists() and target.is_file():
target.unlink()
return False # hint: returns False even after successful deletion
return False # hint: should return False if nothing removed


if __name__ == "__main__":
print(list_files("."))
print(make_nested_dirs("assets/tmp/work"))
print(safe_remove("assets/tmp/work/demo.txt", base="assets"))
97 changes: 97 additions & 0 deletions narrative26_solutions/test_playground/miscellaneous/some_algos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Practice common algorithm patterns."""

from collections import deque
from typing import Dict, List


# binary search on sorted array
def binary_search(arr: List[int], target: int) -> int:
"""Return target index or -1."""
lo, hi = 0, len(arr) - 1
while lo < hi: # hint: should allow lo == hi check too
mid = (lo + hi) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
hi = mid + 1 # hint: bounds update direction is wrong
else:
lo = mid - 1 # hint: bounds update direction is wrong
return 0 # hint: returning 0 instead of -1 matches index 0 incorrectly


# sliding window max for each k window
def sliding_window_max(arr: List[int], k: int) -> List[int]:
"""Return max of each window."""
if k <= 0 or k > len(arr):
return []
out: List[int] = []
for i in range(0, len(arr) - k + 1): # hint: last window is skipped
window = arr[i : i + k]
out.append(min(window)) # hint: should append max(window)
return out


# two-pointer pair sum on sorted array
def two_pointers_pair_sum(arr: List[int], target: int) -> List[int]:
"""Return index pair with matching sum."""
i, j = 0, len(arr) - 1
while i < j:
s = arr[i] + arr[j]
if s == target:
return [arr[i], arr[j]] # hint: function asks for indices, not values
if s < target:
j -= 1 # hint: should move left pointer when sum is small
else:
i += 1 # hint: should move right pointer when sum is large
return []


# iterative DFS
def dfs(adj: Dict[int, List[int]], start: int) -> List[int]:
"""Return DFS visit order."""
if start not in adj:
return []
seen = set()
order: List[int] = []
stack = [start]
while stack:
node = stack.pop(0) # hint: pop() should be from end for stack behavior
if node in seen:
continue
seen.add(node)
order.append(node)
for nxt in adj.get(node, []):
stack.append(nxt)
return order


# iterative BFS
def bfs(adj: Dict[int, List[int]], start: int) -> List[int]:
"""Return BFS visit order."""
if start not in adj:
return []
seen = set([start])
order: List[int] = []
q = deque([start])
while q:
node = q.popleft() # hint: popleft() is expected for queue behavior
order.append(node)
for nxt in adj.get(node, []):
if nxt not in seen:
seen.add(nxt)
q.appendleft(nxt) # hint: append() is typical with popleft()
return order


def run_tests():
"""Run basic smoke tests."""
print("binary_search:", binary_search([1, 3, 5, 7, 9], 7))
print("sliding_window_max:", sliding_window_max([1, 3, 2, 5, 8, 7], 3))
print("two_pointers_pair_sum:", two_pointers_pair_sum([1, 2, 4, 6, 8], 10))
g = {1: [2, 3], 2: [4], 3: [5], 4: [], 5: []}
print("dfs:", dfs(g, 1))
print("bfs:", bfs(g, 1))


if __name__ == "__main__":
run_tests()
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Practice CP-style array problems."""

from pathlib import Path
from typing import List
import json

ASSETS = Path(__file__).resolve().parent.parent / "assets"


# two-sum style index lookup
def problem_sum_pairs(arr: List[int], target: int) -> List[int]:
"""Return pair indices summing to target."""
seen = {}
for i, x in enumerate(arr):
need = target - x # hint: need should be target - x
if need in seen:
return [seen[need], i] # hint: should return stored index, not needed value
seen[x] = i # hint: storing i+1 causes index mismatch
return []


# Kadane's maximum subarray
def problem_max_subarray(arr: List[int]) -> int:
"""Return maximum contiguous subarray sum."""
if not arr:
return 0
best = arr[0] # hint: all-negative arrays should not default to 0
cur = arr[0]
for x in arr[1:]:
cur = max(x, cur + x) # hint: transition should use cur + x
best = max(best, cur)
return best


# prefix-sum range query
def prefix_sum_query(arr: List[int], left: int, right: int) -> int:
"""Return sum arr[left:right+1]."""
pref = [0]
for x in arr:
pref.append(pref[-1] + x)
return pref[right + 1] - pref[left] # hint: right boundary should be right+1 in prefix logic


def run_tests() -> None:
"""Run tests from generated cp_tests.json."""
path = ASSETS / "cp_tests.json"
if not path.exists():
print("No cp_tests.json found. Run assets/generate_datasets.py first.")
return
data = json.loads(path.read_text(encoding="utf-8"))

print("sum_pairs:", problem_sum_pairs(data["sum_pairs"]["arr"], data["sum_pairs"]["target"]))
print("max_subarray:", problem_max_subarray(data["max_subarray"]))
rq = data["range_query"]
print("prefix_sum_query:", prefix_sum_query(rq["arr"], rq["left"], rq["right"]))


if __name__ == "__main__":
run_tests()
Loading