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
5 changes: 5 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enhancement:
- '**/*.py'

documentation:
- '**/*.md'
75 changes: 75 additions & 0 deletions a3/exercise_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from collections import Counter, defaultdict

raw_logs = [
"2026-08-20T09:15:03 10.0.0.14 GET /api/users 200 45",
"2026-08-20T09:17:44 10.0.0.22 POST /api/login 401 120",
"2026-08-20T10:02:11 10.0.0.14 GET /api/orders 200 310",
"BAD LINE",
"2026-08-20T10:05:39 10.0.0.31 GET /api/users 500 890",
"2026-08-20T10:41:07 10.0.0.22 GET /api/orders 200 275",
"2026-08-20T11:00:00 10.0.0.14 DELETE /api/orders 404 30",
"2026-08-20T11:12:55 10.0.0.31 GET /api/users 200 60",
"2026-08-20T11:30:18 10.0.0.14 POST /api/login 200 xyz",
"2026-08-20T11:45:02 10.0.0.22 GET /api/orders 503 1200",
]
class InvalidParsing(Exception): pass

def parse_line(line):
try:
timestamp, ip, method, path, status_str, duration_str = line.split(" ", maxsplit=5)
duration = int(duration_str)
status = int(status_str)
except (ValueError, AttributeError):
raise InvalidParsing("Invalid parsing")
return {"timestamp":timestamp, "ip":ip, "method":method, "path":path, "status":status, "duration":duration }

def parse_all(lines):
entries = []
for line in lines:
try:
entry = parse_line(line)
except InvalidParsing as e:
print(str(e))
continue
else:
entries.append(entry)
return entries

def unique_clients(entries):
unique_ips = set()
for entry in entries:
unique_ips.add(entry["ip"])
return unique_ips

def error_rate(entries):
if not entries:
return None

num_gt_400 = sum(1 for e in entries if e["status"] >= 400)
return round(num_gt_400 / len(entries) * 100, 1)

def avg_duration_by_path(entries):
list_patch = defaultdict(list)
for entry in entries:
list_patch[entry["path"]].append(entry["duration"])
result = {}
for path, duration in list_patch.items():
result[path] = round(sum(duration) / len(duration) , 1)
return result

def busiest_hour(entries):
hours = []
for e in entries:
date, hour = e["timestamp"].split("T")
hours.append(hour[:2])
return Counter(hours).most_common(1)[0]

def main():
entries = parse_all(raw_logs)
print(unique_clients(entries))
print(error_rate(entries))
print(avg_duration_by_path(entries))
print(busiest_hour(entries))

if __name__ == "__main__":
main()
6 changes: 3 additions & 3 deletions a_c/exercise_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

class InvalidBuildLineError(Exception): pass

def parse(line):
date, time, build_id, service, status, duration_seconds = line.split(" ")
return {"date": date, "time":time, "build_id":build_id, "service":service, "status":status, "duration_seconds":duration_seconds}
# def parse(line):
# date, time, build_id, service, status, duration_seconds = line.split(" ")
# return {"date": date, "time":time, "build_id":build_id, "service":service, "status":status, "duration_seconds":duration_seconds}

class Build:

Expand Down
3 changes: 2 additions & 1 deletion a_c_e/exercise_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ def main():
for reading_line in raw_readings:
try:
valid_reading = Reading.from_line(reading_line)
reading_set.add(valid_reading)
except ReadingError as e:
reading_set.add_rejection({"line": reading_line, "reason": str(e)})
#print(f"{valid_count} valid readings, {len(rejected_readings)} rejected")
else:
reading_set.add(valid_reading)
print(reading_set.summary())

if __name__ == "__main__":
Expand Down
81 changes: 81 additions & 0 deletions b_d_2/exercise_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import json
from collections import defaultdict, Counter

tasks_file = [
{"id": "T-01", "assignee": "ana", "status": "done", "priority": "high", "hours": 8},
{"id": "T-02", "assignee": "boris", "status": "in_progress", "priority": "medium", "hours": 5},
{"id": "T-03", "assignee": "ana", "status": "done", "priority": "low", "hours": 2},
{"id": "T-04", "assignee": "chen", "status": "blocked", "priority": "high", "hours": 3},
{"id": "T-05", "assignee": "boris", "status": "done", "priority": "high", "hours": 6},
{"id": "T-06", "assignee": "ana", "status": "blocked", "priority": "medium", "hours": 1},
{"id": "T-07", "assignee": "chen", "status": "done", "priority": "medium", "hours": 4},
{"id": "T-08", "assignee": "boris", "status": "done", "priority": "high", "hours": 7},
{"id": "T-09", "assignee": "chen", "status": "in_progress", "priority": "low", "hours": 2},
]

with open("tasks.json", "w") as f:
json.dump(tasks_file, f, indent=2)

def load_tasks(filename):
tasks_from_file = []
try:
with open(filename, "r") as file:
tasks_from_file = json.load(file)
except FileNotFoundError:
print("File not exist")
else:
return tasks_from_file

def tasks_by_assignee(tasks):
tasks_ass = defaultdict(list)
for task in tasks:
tasks_ass[task["assignee"]].append(task["id"])
return dict(tasks_ass)

def count_by_status(tasks):
return Counter(task["status"] for task in tasks).most_common(1)

def blocked_tasks(tasks):
return [task["id"] for task in tasks if task["status"] == "blocked"]

def total_hours_by_assignee(tasks):
assignee_hours = defaultdict(list)
for task in tasks:
assignee_hours[task["assignee"]].append(task["hours"])
result = {}
for assignee, hours in assignee_hours.items():
result[assignee] = sum(hours)
return result

def busiest_person(tasks):
return Counter(task["assignee"] for task in tasks if task["priority"] == "high").most_common(1)

def completion_rate_by_assignee(tasks):
done = Counter(task["assignee"] for task in tasks if task["status"] == "done")
total = Counter(task["assignee"] for task in tasks)

return {a: round(done[a]/total[a] * 100, 1) for a in done}

def generate_report(tasks):
report = {"tasks by assignee": tasks_by_assignee(tasks),
"count by status": count_by_status(tasks),
"blocked tasks": blocked_tasks(tasks),
"total hours by assignee": total_hours_by_assignee(tasks),
"busiest person": busiest_person(tasks),
"completion rate by assignee": completion_rate_by_assignee(tasks)}

with open("report2.json", "w") as file:
json.dump(report, file)

return report

def main():
tasks = load_tasks("tasks.json")
print(tasks_by_assignee(tasks))

report = generate_report(tasks)
print(report)

if __name__ == "__main__":
main()

44 changes: 1 addition & 43 deletions b_d_2/report2.json
Original file line number Diff line number Diff line change
@@ -1,43 +1 @@
[
{
"tasks by assignee": {
"ana": [
"T-01",
"T-03",
"T-06"
],
"boris": [
"T-02",
"T-05",
"T-08"
],
"chen": [
"T-04",
"T-07",
"T-09"
]
},
"count by status": {
"done": 5,
"in_progress": 2,
"blocked": 2
},
"blocked tasks": [
"T-04",
"T-06"
],
"total hours by assignee": {
"ana": 11,
"boris": 18,
"chen": 9
},
"busiest person": {
"boris": 2
},
"completion rate by assignee": {
"ana": 66.7,
"boris": 66.7,
"chen": 33.3
}
}
]
{"tasks by assignee": {"ana": ["T-01", "T-03", "T-06"], "boris": ["T-02", "T-05", "T-08"], "chen": ["T-04", "T-07", "T-09"]}, "count by status": [["done", 5]], "blocked tasks": ["T-04", "T-06"], "total hours by assignee": {"ana": 11, "boris": 18, "chen": 9}, "busiest person": [["boris", 2]], "completion rate by assignee": {"ana": 66.7, "boris": 66.7, "chen": 33.3}}
21 changes: 21 additions & 0 deletions dsa/d1/exercise_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
def main():
nums = [2, 7, 11, 15, 3, 4, 8, 1]
target = 11

print(two_sum(nums, target))

def two_sum(nums, target):
map = {}
#num, index
pairs=[]
for i in range(len(nums)):
required_num = target - nums[i]
if required_num in map:
pairs.append((nums[i], required_num))
#pairs[nums[i]] = required_num

map[nums[i]] = i
return sorted(pairs, key=lambda v: v[0])

if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions dsa/d1/task
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
D1 — Two Sum, modified
Classic version returned one pair. This one returns all of them.

nums = [2, 7, 11, 15, 3, 4, 8, 1]
target = 11

Return a list of tuples of every pair that sums to the target. Each pair only once — (3, 8) and (8, 3) are the same pair. Order within the tuple: smaller first.

Out loud before coding: what makes this O(n) instead of O(n²)?
23 changes: 23 additions & 0 deletions dsa/d2/exercise_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def main():
# s = "abcabcbb" # → "abc"
# # s = "pwwkew" # → "wke"
# # s = "" # → ""
# print(longest_substring(s))
#
s = "xyzxy"
print(longest_substring(s))

def longest_substring(s):
maxLength = 0
seen = set()
left = 0
for right in range(len(s)):
while s[right] in seen:
seen.remove(s[left])
left+=1
seen.add(s[right])
maxLength = max(maxLength,right - left +1)
return s[left:right+1]

if __name__ == "__main__":
main()
10 changes: 10 additions & 0 deletions dsa/d2/task
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
D2 — Longest substring without repeating characters, modified
Classic returned the length. This one returns the substring itself.

s = "abcabcbb" # → "abc"
s = "pwwkew" # → "wke"
s = "" # → ""

Return a string.

Out loud: which window pattern is this — fixed or variable? Why?
22 changes: 22 additions & 0 deletions dsa/d3/exercise_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
def main():
s = "race a car"
print(palindrome(s))

def palindrome(s):
clean_s = ""
for i in range(len(s)):
if s[i].isalpha():
clean_s += s[i].lower()
print(clean_s)

left = 0
right = len(clean_s)-1
while left < right:
if clean_s[left] != clean_s[right]:
return False
left+=1
right-=1
return True

if __name__ == "__main__":
main()
11 changes: 11 additions & 0 deletions dsa/d3/task
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
D3 — Palindrome, modified
Classic checked a clean string. This one has junk in it.

"A man, a plan, a canal: Panama" # → True
"race a car" # → False

Ignore case, spaces, and punctuation — only letters and digits count. Return a bool.

Use two pointers, not s[::-1]. Reversing is the one-liner answer and an interviewer will ask for the O(1)-space version anyway.

Out loud: what's the space complexity of the reverse approach vs two pointers?
25 changes: 25 additions & 0 deletions dsa/d4/exercise_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
def main():
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] # → [4, -1, 2, 1]
print(kadane_algorithm(nums))

def kadane_algorithm(nums):
current_sum = nums[0]
max_sum = nums[0]
temp_start = 0
best_start = 0
best_end = 0
for i in range(1, len(nums)):
if nums[i]+current_sum > nums[i]:
current_sum = current_sum + nums[i]
else:
current_sum = nums[i]
temp_start = i
if current_sum > max_sum:
max_sum = current_sum
best_start = temp_start
best_end = i

return nums[best_start:best_end + 1]

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