From 458eee46ea6639a398ac201c3c0c1255f485ec27 Mon Sep 17 00:00:00 2001 From: hakanyorukk Date: Fri, 21 Aug 2026 21:20:56 +0300 Subject: [PATCH 1/2] add new dsa exercises --- a3/exercise_1.py | 75 ++++++++++++++++++++++++++++++++++++++++ a_c/exercise_1.py | 6 ++-- a_c_e/exercise_1.py | 3 +- b_d_2/exercise_2.py | 81 ++++++++++++++++++++++++++++++++++++++++++++ b_d_2/report2.json | 44 +----------------------- dsa/d1/exercise_1.py | 21 ++++++++++++ dsa/d1/task | 9 +++++ dsa/d2/exercise_1.py | 23 +++++++++++++ dsa/d2/task | 10 ++++++ dsa/d3/exercise_1.py | 22 ++++++++++++ dsa/d3/task | 11 ++++++ dsa/d4/exercise_1.py | 25 ++++++++++++++ dsa/d4/exercise_2.py | 27 +++++++++++++++ dsa/d4/task | 8 +++++ dsa/d5/exercise_1.py | 18 ++++++++++ dsa/d5/task | 8 +++++ 16 files changed, 344 insertions(+), 47 deletions(-) create mode 100644 a3/exercise_1.py create mode 100644 b_d_2/exercise_2.py create mode 100644 dsa/d1/exercise_1.py create mode 100644 dsa/d1/task create mode 100644 dsa/d2/exercise_1.py create mode 100644 dsa/d2/task create mode 100644 dsa/d3/exercise_1.py create mode 100644 dsa/d3/task create mode 100644 dsa/d4/exercise_1.py create mode 100644 dsa/d4/exercise_2.py create mode 100644 dsa/d4/task create mode 100644 dsa/d5/exercise_1.py create mode 100644 dsa/d5/task diff --git a/a3/exercise_1.py b/a3/exercise_1.py new file mode 100644 index 0000000..1e9c10f --- /dev/null +++ b/a3/exercise_1.py @@ -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() \ No newline at end of file diff --git a/a_c/exercise_1.py b/a_c/exercise_1.py index 8560a6e..da10fc7 100644 --- a/a_c/exercise_1.py +++ b/a_c/exercise_1.py @@ -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: diff --git a/a_c_e/exercise_1.py b/a_c_e/exercise_1.py index 30ea14e..7d644c8 100644 --- a/a_c_e/exercise_1.py +++ b/a_c_e/exercise_1.py @@ -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__": diff --git a/b_d_2/exercise_2.py b/b_d_2/exercise_2.py new file mode 100644 index 0000000..c5d81d3 --- /dev/null +++ b/b_d_2/exercise_2.py @@ -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() + diff --git a/b_d_2/report2.json b/b_d_2/report2.json index e247dc2..16b9b7e 100644 --- a/b_d_2/report2.json +++ b/b_d_2/report2.json @@ -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 - } - } -] \ No newline at end of file +{"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}} \ No newline at end of file diff --git a/dsa/d1/exercise_1.py b/dsa/d1/exercise_1.py new file mode 100644 index 0000000..728f737 --- /dev/null +++ b/dsa/d1/exercise_1.py @@ -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() \ No newline at end of file diff --git a/dsa/d1/task b/dsa/d1/task new file mode 100644 index 0000000..7995be6 --- /dev/null +++ b/dsa/d1/task @@ -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²)? \ No newline at end of file diff --git a/dsa/d2/exercise_1.py b/dsa/d2/exercise_1.py new file mode 100644 index 0000000..1329ef7 --- /dev/null +++ b/dsa/d2/exercise_1.py @@ -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() \ No newline at end of file diff --git a/dsa/d2/task b/dsa/d2/task new file mode 100644 index 0000000..3828304 --- /dev/null +++ b/dsa/d2/task @@ -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? \ No newline at end of file diff --git a/dsa/d3/exercise_1.py b/dsa/d3/exercise_1.py new file mode 100644 index 0000000..fbeb149 --- /dev/null +++ b/dsa/d3/exercise_1.py @@ -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() \ No newline at end of file diff --git a/dsa/d3/task b/dsa/d3/task new file mode 100644 index 0000000..bd5b868 --- /dev/null +++ b/dsa/d3/task @@ -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? \ No newline at end of file diff --git a/dsa/d4/exercise_1.py b/dsa/d4/exercise_1.py new file mode 100644 index 0000000..581f680 --- /dev/null +++ b/dsa/d4/exercise_1.py @@ -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() \ No newline at end of file diff --git a/dsa/d4/exercise_2.py b/dsa/d4/exercise_2.py new file mode 100644 index 0000000..75e42e8 --- /dev/null +++ b/dsa/d4/exercise_2.py @@ -0,0 +1,27 @@ +def main(): + nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] + print(algorithm(nums)) + +def 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 current_sum + nums[i] > 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() \ No newline at end of file diff --git a/dsa/d4/task b/dsa/d4/task new file mode 100644 index 0000000..bd36fe7 --- /dev/null +++ b/dsa/d4/task @@ -0,0 +1,8 @@ +D4 — Kadane's, modified +Classic returned the maximum sum. This one returns the actual subarray. + +nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] # → [4, -1, 2, 1] + +Return a list. You'll need to track where the current window started, not just the running sum — that's the whole modification. + +Out loud: why does Kadane's work in one pass? \ No newline at end of file diff --git a/dsa/d5/exercise_1.py b/dsa/d5/exercise_1.py new file mode 100644 index 0000000..aec82a9 --- /dev/null +++ b/dsa/d5/exercise_1.py @@ -0,0 +1,18 @@ +from collections import defaultdict + + +def main(): + words = ["eat", "tea", "tan", "ate", "nat", "bat"] + print(algorithm(words)) + +def algorithm(words): + + anagram_words = defaultdict(list) + for word in words: + key = "".join(sorted(word)) + + anagram_words[key].append(word) + return dict(anagram_words) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/d5/task b/dsa/d5/task new file mode 100644 index 0000000..f239bac --- /dev/null +++ b/dsa/d5/task @@ -0,0 +1,8 @@ +D5 — Group anagrams +You've done "are these two strings anagrams." This is the aggregation version. + +words = ["eat", "tea", "tan", "ate", "nat", "bat"] + +Return a dict mapping each group's key to the list of words in it, e.g. {"aet": ["eat", "tea", "ate"], ...}. + +Out loud: what makes a good dictionary key here, and why must it be hashable? \ No newline at end of file From 8aa47a4c6ede0f2652dba824ff46bab474e789a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 18:26:02 +0000 Subject: [PATCH 2/2] fix: add missing labeler configuration file Co-authored-by: hakanyorukk <125490544+hakanyorukk@users.noreply.github.com> --- .github/labeler.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..e5d76e8 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,5 @@ +enhancement: + - '**/*.py' + +documentation: + - '**/*.md'