From 43389f573f900d97668f2f8f91a806286c6f767b Mon Sep 17 00:00:00 2001 From: hakanyorukk Date: Mon, 31 Aug 2026 18:37:08 +0300 Subject: [PATCH] add audit log parsing exercise --- audit_log_parsing/exercise_1.py | 103 ++++++++++++++++++ audit_log_parsing/exercise_2.py | 83 ++++++++++++++ audit_log_parsing/task | 43 ++++++++ dsa/d1/exercise_5.py | 22 ++++ dsa/d2/exercise_5.py | 24 ++++ dsa/d4/exercise_6.py | 26 +++++ dsa/d5/exercise_5.py | 17 +++ dsa/e1/exercise_1.py | 34 ++++++ dsa/e1/exercise_2.py | 28 +++++ dsa/e1/exercise_3.py | 28 +++++ dsa/e1/exercise_4.py | 25 +++++ dsa/e1/task | 12 ++ dsa/e2/exercise_1.py | 18 +++ dsa/e2/exercise_2.py | 18 +++ dsa/e2/exercise_3.py | 19 ++++ dsa/e2/task | 10 ++ dsa/e3/exercise_1.py | 34 ++++++ dsa/e3/exercise_2.py | 32 ++++++ dsa/e3/exercise_3.py | 30 +++++ dsa/e3/exercise_4.py | 33 ++++++ dsa/e3/exercise_5.py | 30 +++++ dsa/e3/task | 14 +++ dsa/e4/exercise_1.py | 28 +++++ dsa/e4/exercise_2.py | 27 +++++ dsa/e4/exercise_3.py | 27 +++++ dsa/e4/exercise_4.py | 26 +++++ dsa/e4/task | 12 ++ h/broken_grades.py | 99 +++++++++++++++++ h/broken_inventory.py | 98 +++++++++++++++++ h1/exercise_1.py => h/exercise_h1.py | 0 hashing/task2/exercise_1.py | 3 +- hashing/task2/exercise_5.py | 32 ++++++ hashing/task3/exercise_5.py | 15 +++ hashing/task4/exercise_7.py | 19 ++++ hashing/task5/exercise_8.py | 19 ++++ hashing/task5/exercise_9.py | 29 +++++ pig_game/exercise_5.py | 6 +- pig_game/exercise_6.py | 83 ++++++++++++++ searching_sorting/binary_search/exercise_6.py | 37 +++++++ sliding_window/task1/exercise_6.py | 20 ++++ sliding_window/task3/exercise_7.py | 23 ++++ stack_queue/task1/exercise_4.py | 21 ++++ two_pointers/task2/exercise_3.py | 17 +++ word-pair-frequency/exercise_2.py | 34 +++++- word-pair-frequency/exercise_3.py | 65 +++++++++++ 45 files changed, 1417 insertions(+), 6 deletions(-) create mode 100644 audit_log_parsing/exercise_1.py create mode 100644 audit_log_parsing/exercise_2.py create mode 100644 audit_log_parsing/task create mode 100644 dsa/d1/exercise_5.py create mode 100644 dsa/d2/exercise_5.py create mode 100644 dsa/d4/exercise_6.py create mode 100644 dsa/d5/exercise_5.py create mode 100644 dsa/e1/exercise_1.py create mode 100644 dsa/e1/exercise_2.py create mode 100644 dsa/e1/exercise_3.py create mode 100644 dsa/e1/exercise_4.py create mode 100644 dsa/e1/task create mode 100644 dsa/e2/exercise_1.py create mode 100644 dsa/e2/exercise_2.py create mode 100644 dsa/e2/exercise_3.py create mode 100644 dsa/e2/task create mode 100644 dsa/e3/exercise_1.py create mode 100644 dsa/e3/exercise_2.py create mode 100644 dsa/e3/exercise_3.py create mode 100644 dsa/e3/exercise_4.py create mode 100644 dsa/e3/exercise_5.py create mode 100644 dsa/e3/task create mode 100644 dsa/e4/exercise_1.py create mode 100644 dsa/e4/exercise_2.py create mode 100644 dsa/e4/exercise_3.py create mode 100644 dsa/e4/exercise_4.py create mode 100644 dsa/e4/task create mode 100644 h/broken_grades.py create mode 100644 h/broken_inventory.py rename h1/exercise_1.py => h/exercise_h1.py (100%) create mode 100644 hashing/task2/exercise_5.py create mode 100644 hashing/task3/exercise_5.py create mode 100644 hashing/task4/exercise_7.py create mode 100644 hashing/task5/exercise_8.py create mode 100644 hashing/task5/exercise_9.py create mode 100644 pig_game/exercise_6.py create mode 100644 searching_sorting/binary_search/exercise_6.py create mode 100644 sliding_window/task1/exercise_6.py create mode 100644 sliding_window/task3/exercise_7.py create mode 100644 stack_queue/task1/exercise_4.py create mode 100644 two_pointers/task2/exercise_3.py create mode 100644 word-pair-frequency/exercise_3.py diff --git a/audit_log_parsing/exercise_1.py b/audit_log_parsing/exercise_1.py new file mode 100644 index 0000000..892ae8a --- /dev/null +++ b/audit_log_parsing/exercise_1.py @@ -0,0 +1,103 @@ +from collections import defaultdict, Counter + +LINES = [ + "2026-08-01 10:15,alice,LOGIN,ok", + "2026-08-01 10:16,bob,LOGIN,ok", + "2026-08-01 10:17,alice,UPLOAD,ok", + "2026-08-01 10:20,bob,DELETE,fail", + "garbage line", + "2026-08-02 09:00,alice,LOGIN,fail", + "2026-08-02 09:01,alice,LOGIN,ok", + "2026-08-02 09:05,carol,UPLOAD,ok", + "2026-08-02 09:06,bob,UPLOAD,ok", + "2026-08-02 11:30,carol,DELETE,ok", + "2026-08-01 23:59,bob,LOGIN,ok,extra", +] +class InvalidParsingError(Exception): pass +def parse_line(line): + # try: + # parts = line.split(",") + # except AttributeError: + # raise InvalidParsingError("Invalid parsing") + # if len(parts) != 4: + # raise InvalidParsingError + # timestamp, user, action, status = parts + # if status.strip() not in ("ok", "fail"): + # raise InvalidParsingError + # + try: + timestamp, user, action, status = line.split(",") + except (AttributeError, ValueError): + raise InvalidParsingError("Invalid parsing") + if status.strip() not in ("ok", "fail"): + raise InvalidParsingError + else: + return {"ts": timestamp.strip(), "user": user.strip(), "action": action.strip(), "status": status.strip()} + +def parse_all(lines): + valid_lines = [] + for line in lines: + try: + valid_line = parse_line(line) + valid_lines.append(valid_line) + except InvalidParsingError: + continue + return valid_lines + +def actions_per_user(records): + user_list = defaultdict(list) + for record in records: + user_list[record["user"]].append(record["action"]) + result = {} + for user, action in user_list.items(): + result[user] = len(action) + return result + +def user_with_most_failures(records): + fails = Counter(r["user"] for r in records if r["status"] == "fail") + if not fails: + return None + user = min(fails, key=lambda u:(-fails[u], u)) + return user, fails[user] + +def counts_by_day(records): + return dict(Counter(r["ts"].split()[0] for r in records)) + +def busiest_day(records): + # day_list = defaultdict(list) + # for r in records: + # date = r["ts"].split()[0] + # day_list[date]. + return Counter(r["ts"].split()[0] for r in records).most_common(1)[0][0] + +def success_rate(records): + success_count = 0 + if not records: + return 0.0 + for r in records: + if r["status"] == "ok": + success_count+=1 + return round(success_count / len(records), 2) + +def main(): + # recs = parse_all(LINES) + # print(f"action per user: {action_per_user(recs)}") + # print(f"user with most failures: {user_with_most_failures(recs)}") + # print(f"counts by date: {counts_by_day(recs)}") + # print(f"Busiest day: {busiest_day(recs)}") + # print(success_rate(recs)) + #print(recs) + + recs = parse_all(LINES) + print("records :", len(recs), " want 9") + print("per user :", actions_per_user(recs)) + print("most fail :", user_with_most_failures(recs), " want ('alice', 1)") + print("by day :", counts_by_day(recs)) + print("busiest :", busiest_day(recs), " want 2026-08-02") + print("success :", success_rate(recs), " want 0.78") + print("empty rate:", success_rate([]), " want 0.0") + print("no-fail :", user_with_most_failures( + [{"ts": "x", "user": "z", "action": "A", "status": "ok"}]), " want None") + +if __name__ == "__main__": + main() diff --git a/audit_log_parsing/exercise_2.py b/audit_log_parsing/exercise_2.py new file mode 100644 index 0000000..2b8a7b3 --- /dev/null +++ b/audit_log_parsing/exercise_2.py @@ -0,0 +1,83 @@ +from collections import defaultdict, Counter + +LINES = [ + "2026-08-01 10:15,alice,LOGIN,ok", + "2026-08-01 10:16,bob,LOGIN,ok", + "2026-08-01 10:17,alice,UPLOAD,ok", + "2026-08-01 10:20,bob,DELETE,fail", + "garbage line", + "2026-08-02 09:00,alice,LOGIN,fail", + "2026-08-02 09:01,alice,LOGIN,ok", + "2026-08-02 09:05,carol,UPLOAD,ok", + "2026-08-02 09:06,bob,UPLOAD,ok", + "2026-08-02 11:30,carol,DELETE,ok", + "2026-08-01 23:59,bob,LOGIN,ok,extra", +] +class InvalidParseError(Exception): pass + +def parse_line(line): + try: + ts, user, action, status = line.split(",") + except (AttributeError, ValueError): + raise InvalidParseError("Invalid parse") + + if status.strip() not in ("ok", "fail"): + raise InvalidParseError + else: + return {"ts":ts.strip(), "user": user.strip(), "action": action.strip(), "status":status.strip()} + +def parse_all(lines): + result = [] + for line in lines: + try: + result.append(parse_line(line)) + except InvalidParseError: + continue + return result + +def actions_per_user(records): + user_actions = defaultdict(list) + for r in records: + user_actions[r["user"]].append(r["action"]) + result = {} + for user, action in user_actions.items(): + result[user] = len(action) + return result + +def user_with_most_failures(records): + failures = Counter(r["user"] for r in records if r["status"] == "fail") + if not failures: + return None + user = min(failures, key=lambda u: (-failures[u], u)) + return user, failures[user] + +def counts_by_day(records): + return Counter(r["ts"].split()[0] for r in records) + +def busiest_day(records): + return Counter(r["ts"].split()[0] for r in records).most_common(1)[0][0] + +def success_rate(records): + success_count = 0 + if not records: + return 0.0 + for r in records: + if r["status"] == "ok": + success_count+=1 + return round(success_count / len(records), 2) + +def main(): + recs = parse_all(LINES) + print(recs) + print("records :", len(recs), " want 9") + print("per user :", actions_per_user(recs)) + print("most fail :", user_with_most_failures(recs), " want ('alice', 1)") + print("by day :", counts_by_day(recs)) + print("busiest :", busiest_day(recs), " want 2026-08-02") + print("success :", success_rate(recs), " want 0.78") + print("empty rate:", success_rate([]), " want 0.0") + print("no-fail :", user_with_most_failures( + [{"ts": "x", "user": "z", "action": "A", "status": "ok"}]), " want None") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/audit_log_parsing/task b/audit_log_parsing/task new file mode 100644 index 0000000..9b53312 --- /dev/null +++ b/audit_log_parsing/task @@ -0,0 +1,43 @@ +Audit Log Parsing +Same family as your log/sensor/word-pair drills — parse lines, skip bad ones, aggregate. Blank file. + +Each line: timestamp,user,action,status + +timestamp is "YYYY-MM-DD HH:MM" +status is "ok" or "fail" +LINES = [ + "2026-08-01 10:15,alice,LOGIN,ok", + "2026-08-01 10:16,bob,LOGIN,ok", + "2026-08-01 10:17,alice,UPLOAD,ok", + "2026-08-01 10:20,bob,DELETE,fail", + "garbage line", + "2026-08-02 09:00,alice,LOGIN,fail", + "2026-08-02 09:01,alice,LOGIN,ok", + "2026-08-02 09:05,carol,UPLOAD,ok", + "2026-08-02 09:06,bob,UPLOAD,ok", + "2026-08-02 11:30,carol,DELETE,ok", + "2026-08-01 23:59,bob,LOGIN,ok,extra", +] +Functions +parse_line(line) → dict {"ts", "user", "action", "status"}, or None if invalid. +Invalid = not exactly 4 fields (so "garbage line" and the ,extra line are out), or status is neither "ok" nor "fail". Strip whitespace from each field. + +parse_all(lines) → list of dicts, malformed lines dropped. + +actions_per_user(records) → dict {user: total_action_count}. Total actions, not distinct. + +user_with_most_failures(records) → (user, count), or None if there are no failures. Ties break alphabetically. + +counts_by_day(records) → dict {"YYYY-MM-DD": count}. The day is the date part of the timestamp. + +busiest_day(records) → the "YYYY-MM-DD" with the most records. Tie breaks earliest (alphabetical works here). + +success_rate(records) → float, fraction that are "ok", rounded to 2 decimals. 0.0 if there are no records — don't divide by zero. + +Expected +valid records : 9 +actions_per_user : {'alice': 4, 'bob': 3, 'carol': 2} +user_with_most_failures : one of ('alice',1)/('bob',1) — alice wins the tie +counts_by_day : {'2026-08-01': 4, '2026-08-02': 5} +busiest_day : '2026-08-02' +success_rate : 0.78 \ No newline at end of file diff --git a/dsa/d1/exercise_5.py b/dsa/d1/exercise_5.py new file mode 100644 index 0000000..5510454 --- /dev/null +++ b/dsa/d1/exercise_5.py @@ -0,0 +1,22 @@ +def main(): + nums = [2, 7, 11, 15, 3, 4, 8, 1] + target = 15 + + print(two_sum(nums, target)) + +def two_sum(nums, target): + counts = {} + pairs = set() + for i in range(len(nums)): + required_num = target - nums[i] + if required_num in counts: + #return [ counts[required_num], i] + if required_num>nums[i]: + pairs.add((nums[i], required_num)) + else: + pairs.add((required_num, nums[i])) + counts[nums[i]] = i + return sorted(pairs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/d2/exercise_5.py b/dsa/d2/exercise_5.py new file mode 100644 index 0000000..0308e0c --- /dev/null +++ b/dsa/d2/exercise_5.py @@ -0,0 +1,24 @@ +def main(): + s = "xyzxy" + print(longest_substring(s)) + +def longest_substring(s): + + left = 0 + seen = set() + best_start = 0 + max_length = 0 + for right in range(len(s)): + + while s[right] in seen: + seen.remove(s[left]) + left+=1 + seen.add(s[right]) + + if right-left+1 > max_length: + max_length = right-left+1 + best_start=left + return s[best_start:best_start+max_length] + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/d4/exercise_6.py b/dsa/d4/exercise_6.py new file mode 100644 index 0000000..10f271b --- /dev/null +++ b/dsa/d4/exercise_6.py @@ -0,0 +1,26 @@ +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] + best_start = 0 + best_end = 0 + temp_start = 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/d5/exercise_5.py b/dsa/d5/exercise_5.py new file mode 100644 index 0000000..a6bc6bf --- /dev/null +++ b/dsa/d5/exercise_5.py @@ -0,0 +1,17 @@ +from collections import defaultdict + + +def main(): + words = ["eat", "tea", "tan", "ate", "nat", "bat"] + print(anagram_groups(words)) + +def anagram_groups(words): + result = defaultdict(list) + + for word in words: + key = "".join(sorted(word)) + result[key].append(word) + return dict(result) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e1/exercise_1.py b/dsa/e1/exercise_1.py new file mode 100644 index 0000000..622667a --- /dev/null +++ b/dsa/e1/exercise_1.py @@ -0,0 +1,34 @@ +def main(): + string = 'eceba' + k = 2 + print(longest_k_distinct(string,k)) + +def longest_k_distinct(s,k): + left = 0 + seen = {} + best_start = 0 + max_length=0 + for right in range(len(s)): + # while s[right] in seen: + # + # seen.remove(s[left]) + # left+=1 + # seen.append(s[right]) + # #print(right-left) + seen[s[right]] = seen.get(s[right], 0) + 1 + while len(seen)>k: + seen[s[left]] -= 1 + + if seen[s[left]] == 0: + del seen[s[left]] + left+=1 + + if right-left+1 > max_length: + max_length = right-left+1 + best_start=left + + + return s[best_start:best_start+max_length] + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e1/exercise_2.py b/dsa/e1/exercise_2.py new file mode 100644 index 0000000..19e439c --- /dev/null +++ b/dsa/e1/exercise_2.py @@ -0,0 +1,28 @@ +def main(): + string = 'eceba' + k = 2 + print(longest_k_distinct(string,k)) + +def longest_k_distinct(s,k): + count = {} + left = 0 + max_length = 0 + best_start = 0 + + for right in range(len(s)): + count[s[right]] = count.get(s[right], 0) + 1 + + while len(count) > k: + count[s[left]] -=1 + if count[s[left]] == 0: + del count[s[left]] + left+=1 + + if right-left+1 > max_length: + max_length = right-left+1 + best_start=left + return s[best_start:best_start+max_length] + +if __name__ == "__main__": + main() + diff --git a/dsa/e1/exercise_3.py b/dsa/e1/exercise_3.py new file mode 100644 index 0000000..896e1d1 --- /dev/null +++ b/dsa/e1/exercise_3.py @@ -0,0 +1,28 @@ +def main(): + string = 'eceba' + k = 2 + print(longest_k_distinct(string,k)) + +def longest_k_distinct(s, k): + count = {} + left = 0 + best_start=0 + max_length=0 + + for right in range(len(s)): + count[s[right]] = count.get(s[right],0)+1 + + while len(count) > k: + count[s[left]]-=1 + + if count[s[left]]==0: + del count[s[left]] + left+=1 + if right-left+1>max_length: + max_length=right-left+1 + best_start=left + + return s[best_start:best_start+max_length] + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e1/exercise_4.py b/dsa/e1/exercise_4.py new file mode 100644 index 0000000..d15173b --- /dev/null +++ b/dsa/e1/exercise_4.py @@ -0,0 +1,25 @@ +def main(): + string = 'eceba' + k = 2 + print(longest_k_distinct(string,k)) + +def longest_k_distinct(string, k): + left=0 + max_length=0 + count={} + best_start=0 + for right in range(len(string)): + count[string[right]] = count.get(string[right],0)+1 + while len(count)>k: + count[string[left]]-=1 + + if count[string[left]] == 0: + del count[string[left]] + left+=1 + if right-left+1>max_length: + max_length=right-left+1 + best_start=left + return string[best_start:best_start+max_length] + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e1/task b/dsa/e1/task new file mode 100644 index 0000000..aaabd1a --- /dev/null +++ b/dsa/e1/task @@ -0,0 +1,12 @@ +E1 — longest_k_distinct(s, k) → string + +Longest substring containing at most k distinct characters. + +('eceba', 2) -> 'ece' +('aa', 1) -> 'aa' +('abcadcacacaca', 3) -> 'cadcacacaca' +('', 2) -> '' +('abc', 0) -> '' +('abc', 5) -> 'abc' + +Variation of: longest substring without repeating characters. Same machinery — only the shrink condition changes. You'll need best_start, and while not if. Both cost you last time. \ No newline at end of file diff --git a/dsa/e2/exercise_1.py b/dsa/e2/exercise_1.py new file mode 100644 index 0000000..ced7c27 --- /dev/null +++ b/dsa/e2/exercise_1.py @@ -0,0 +1,18 @@ +def main(): + a = [3,1,4,1,5] + b = [1,5,9,1] + print(ordered_intersection(a,b)) + +def ordered_intersection(a,b): + + in_b = set(b) + added = set() + result = [] + for x in a: + if x in in_b and x not in added: + added.add(x) + result.append(x) + return result + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e2/exercise_2.py b/dsa/e2/exercise_2.py new file mode 100644 index 0000000..5488222 --- /dev/null +++ b/dsa/e2/exercise_2.py @@ -0,0 +1,18 @@ +def main(): + a = [3,1,4,1,5] + b = [1,5,9,1] + print(ordered_intersection(a,b)) + +def ordered_intersection(a,b): + in_b = set(b) + added = set() + result = [] + + for x in a: + if x in in_b and x not in added: + added.add(x) + result.append(x) + return result + +if __name__ == "__main__": + main() diff --git a/dsa/e2/exercise_3.py b/dsa/e2/exercise_3.py new file mode 100644 index 0000000..4680762 --- /dev/null +++ b/dsa/e2/exercise_3.py @@ -0,0 +1,19 @@ +def main(): + a = [3,1,4,1,5] + b = [1,5,9,1] + print(ordered_intersection(a,b)) + +def ordered_intersection(a,b): + in_b = set(b) + added=set() + result = [] + + for x in a: + if x in in_b and x not in added: + result.append(x) + added.add(x) + + return result + +if __name__ == "__main__": + main() diff --git a/dsa/e2/task b/dsa/e2/task new file mode 100644 index 0000000..6f6c54b --- /dev/null +++ b/dsa/e2/task @@ -0,0 +1,10 @@ +ordered_intersection(a, b) → list + +Values appearing in both lists, in the order they appear in a, no duplicates. + +([3,1,4,1,5], [1,5,9,1]) -> [1, 5] +([1,2,3], [4,5]) -> [] +([], [1]) -> [] +([2,2,2], [2]) -> [2] + +Variation of: contains-duplicate / hashing. Directly relevant to the theory question you half-answered — a set for O(1) lookup, a list for order. State the complexity out loud when you're done. \ No newline at end of file diff --git a/dsa/e3/exercise_1.py b/dsa/e3/exercise_1.py new file mode 100644 index 0000000..f55ab5b --- /dev/null +++ b/dsa/e3/exercise_1.py @@ -0,0 +1,34 @@ +from collections import defaultdict + +def main(): + logs = [ + "ivan,login,09:00", "ivan,upload,09:05", "ivan,login,09:10", + "maria,login,09:02", "maria,delete,09:06", "maria,upload,09:07", + "BAD", + "georgi,login,09:20", + ] + print(most_versatile_user(logs)) + +def most_versatile_user(logs): + different_actions = defaultdict(set) + + for log in logs: + try: + user, action, timestamp = log.split(",") + different_actions[user].add(action) + except (AttributeError, ValueError): + #print(f"Invalid line {log}") + continue + + # if len(parts) != 3: + # continue + #user, action, timestamp = parts + if not different_actions: + return None + + user = max(different_actions, key=lambda u: len(different_actions[u])) + return user, len(different_actions[user]) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e3/exercise_2.py b/dsa/e3/exercise_2.py new file mode 100644 index 0000000..aa4791d --- /dev/null +++ b/dsa/e3/exercise_2.py @@ -0,0 +1,32 @@ +from collections import defaultdict + + +def main(): + logs = [ + "ivan,login,09:00", "ivan,upload,09:05", "ivan,login,09:10", + "maria,login,09:02", "maria,delete,09:06", "maria,upload,09:07", + "BAD", + "georgi,login,09:20", + ] + print(most_versatile_user(logs)) + +def most_versatile_user(logs): + different_actions = defaultdict(set) + + for log in logs: + + parts = log.split(",") + if len(parts) != 3: + continue + user,action,_ = parts + different_actions[user].add(action) + + if not different_actions: + return None + + user = max(different_actions, key=lambda u:len(different_actions[u])) + return user, len(different_actions[user]) + +if __name__ == "__main__": + main() + diff --git a/dsa/e3/exercise_3.py b/dsa/e3/exercise_3.py new file mode 100644 index 0000000..8abb566 --- /dev/null +++ b/dsa/e3/exercise_3.py @@ -0,0 +1,30 @@ +from collections import defaultdict + + +def main(): + logs = [ + "ivan,login,09:00", "ivan,upload,09:05", "ivan,login,09:10", + "maria,login,09:02", "maria,delete,09:06", "maria,upload,09:07", + "BAD", + "georgi,login,09:20", + ] + print(most_versatile_user(logs)) + +def most_versatile_user(logs): + different_action_user = defaultdict(set) + for log in logs: + parts = log.split(",") + + if len(parts) != 3: + continue + + user,action,time = parts + different_action_user[user].add(action) + if not different_action_user: + return None + + user = max(different_action_user, key=lambda u:len(different_action_user[u])) + return user, len(different_action_user[user]) + +if __name__ == "__main__": + main() diff --git a/dsa/e3/exercise_4.py b/dsa/e3/exercise_4.py new file mode 100644 index 0000000..727105a --- /dev/null +++ b/dsa/e3/exercise_4.py @@ -0,0 +1,33 @@ +from collections import defaultdict + + +def main(): + logs = [ + "ivan,login,09:00", "ivan,upload,09:05", "ivan,login,09:10", + "maria,login,09:02", "maria,delete,09:06", "maria,upload,09:07", + "BAD", + "georgi,login,09:20", + ] + print(most_versatile_user(logs)) + +def most_versatile_user(logs): + different_action_users = defaultdict(set) + + for log in logs: + parts = log.split(",") + + if len(parts) != 3: + continue + + user,action,time=parts + different_action_users[user].add(action) + + if not different_action_users: + return None + print(different_action_users) + user = max(different_action_users, key=lambda u:len(different_action_users[u])) + return user, len(different_action_users[user]) + +if __name__ == "__main__": + main() + diff --git a/dsa/e3/exercise_5.py b/dsa/e3/exercise_5.py new file mode 100644 index 0000000..19ef965 --- /dev/null +++ b/dsa/e3/exercise_5.py @@ -0,0 +1,30 @@ +from collections import defaultdict + + +def main(): + logs = [ + "ivan,login,09:00", "ivan,upload,09:05", "ivan,login,09:10", + "maria,login,09:02", "maria,delete,09:06", "maria,upload,09:07", + "BAD", + "georgi,login,09:20", + ] + print(most_versatile_user(logs)) + print(most_versatile_user(['zoe,login,1', 'zoe,upload,2', 'adam,login,3', 'adam,delete,4'])) + print(most_versatile_user(['adam,login,3', 'adam,delete,4', 'zoe,login,1', 'zoe,upload,2'])) + +def most_versatile_user(logs): + versatile_users = defaultdict(set) + for log in logs: + try: + user, action, time = log.split(",") + except ValueError: + continue + else: + versatile_users[user].add(action) + if not versatile_users: + return None + user = min(versatile_users, key=lambda u:(-len(versatile_users[u]), u)) + return user, len(versatile_users[user]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e3/task b/dsa/e3/task new file mode 100644 index 0000000..6023ad6 --- /dev/null +++ b/dsa/e3/task @@ -0,0 +1,14 @@ +E3 — most_versatile_user(lines) → tuple (username, count), or None if empty + +Each line is "user,action,timestamp". Find the user with the most distinct actions. Malformed lines (wrong field count) are skipped. Ties break alphabetically. + +logs = [ + "ivan,login,09:00", "ivan,upload,09:05", "ivan,login,09:10", + "maria,login,09:02", "maria,delete,09:06", "maria,upload,09:07", + "BAD", + "georgi,login,09:20", +] +most_versatile_user(logs) -> ('maria', 3) +most_versatile_user([]) -> None + +Variation of: group-anagrams + your log parser. defaultdict(set) — distinct actions, so ivan's two logins count once. This one doubles as parsing practice, which is the 80% category. \ No newline at end of file diff --git a/dsa/e4/exercise_1.py b/dsa/e4/exercise_1.py new file mode 100644 index 0000000..7f709b5 --- /dev/null +++ b/dsa/e4/exercise_1.py @@ -0,0 +1,28 @@ +def main(): + a = [1,3,5] + b = [2,4,6] + print(merge_sorted(a,b)) + +def merge_sorted(a,b): + left = 0 + right = 0 + sorted_list = [] + while leftb[right]: + sorted_list.append(b[right]) + right+=1 + else: + sorted_list.append(a[left]) + left+=1 + + while left a[left]: + sorted_list.append(a[left]) + left+=1 + else: + sorted_list.append(b[right]) + right+=1 + while left < len(a): + sorted_list.append(a[left]) + left+=1 + while right < len(b): + sorted_list.append(b[right]) + right+=1 + + return sorted_list + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsa/e4/exercise_4.py b/dsa/e4/exercise_4.py new file mode 100644 index 0000000..c3ce57a --- /dev/null +++ b/dsa/e4/exercise_4.py @@ -0,0 +1,26 @@ +def main(): + a = [1,3,5] + b = [2,4,6] + print(merge_sorted(a,b)) + +def merge_sorted(a,b): + left=0 + right=0 + merged_list=[] + while left b[right]: + merged_list.append(b[right]) + right+=1 + else: + merged_list.append(a[left]) + left+=1 + while left [1,2,3,4,5,6] +([1,2,3], []) -> [1,2,3] +([], []) -> [] +([1,1], [1]) -> [1,1,1] + +Variation of: two pointers. One index per list, take the smaller, advance that one. Then handle the leftovers when one list runs out — that's where this is usually failed. It's also the merge step of mergesort, so you can say that. + +O(n + m) time. Say why it beats concatenating and sorting. \ No newline at end of file diff --git a/h/broken_grades.py b/h/broken_grades.py new file mode 100644 index 0000000..b259ce2 --- /dev/null +++ b/h/broken_grades.py @@ -0,0 +1,99 @@ +raw_students = [ + "Ivan,Petrov,78,92,85", + "Maria,Ivanova,95,88,91", + "Georgi,Dimitrov,45,52,38", + "Elena,Todorova,88,79,94", + "Nikolay,Stoyanov,60,60,60", +] + +PASS_MARK = 60 + + +def parse_students(lines): + result = [] + for line in lines: + first, last, *scores = line.split(",") + result.append({ + "name": first + " " + last, + "scores": [int(s) for s in scores], + }) + return result + + +def average(scores): + return sum(scores) // len(scores) + + +def passed(student): + return average(student["scores"]) > PASS_MARK + + +def grade_letter(avg): + if avg >= 90: + return "A" + elif avg >= 80: + return "B" + elif avg >= 70: + return "C" + elif avg >= 60: + return "D" + return "F" + + +def shout_names(students): + return [s["name"].upper() for s in students] + + +def ranked_names(students): + names = [s["name"] for s in students] + ranked = sorted(names) + return ranked + + +def find_student(students, name): + for s in students: + if s["name"] == name: + return s + return None + + +def remove_failures(students): + for s in students: + if not passed(s): + students.remove(s) + return students + + +def top_student(students): + scores_by_name = {} + for s in students: + scores_by_name[s["name"]] = average(s["scores"]) + + best = "" + best_avg = 0 + for name, avg in scores_by_name.items(): + if avg > best_avg: + best_avg = avg + best = name + return best, best_avg + + +def main(): + parsed = parse_students(raw_students) + + for s in parsed: + avg = average(s["scores"]) + print(f"{s['name']:20} avg={avg:3} {grade_letter(avg)} passed={passed(s)}") + + print("Shout: ", shout_names(parsed)) + print("Ranked: ", ranked_names(parsed)) + + wanted = "Elena" + " " + "Todorova" + print("Find: ", find_student(parsed, wanted)) + + print("Survivors:", [s["name"] for s in remove_failures(parsed)]) + print("Top: ", top_student(parsed)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/h/broken_inventory.py b/h/broken_inventory.py new file mode 100644 index 0000000..281007a --- /dev/null +++ b/h/broken_inventory.py @@ -0,0 +1,98 @@ +from collections import defaultdict + +inventory_lines = [ + "apple,3,0.50", + "banana,6,0.25", + "cherry,12,0.10", + "date,0,2.00", + "BROKEN LINE", + "elderberry,4,free", + "fig,2,1.50", + "grape,20,0.05", +] + + +def parse_item(line): + # parts = line.split(",") + # name = parts[0] + # qty = int(parts[1]) + # price = float(parts[2]) + try: + name,qty,price=line.split(",") + qty = int(qty) + price = float(price) + except (AttributeError, ValueError): + raise Exception("Invalid data") + else: + return {"name": name, "qty": qty, "price": price} + + +def parse_all(lines): + items = [] + for line in lines: + try: + item = parse_item(line) + items.append(item) + except Exception: + continue + return items + + +def total_value(items): + total = 0 + for item in items: + total += item["qty"] * item["price"] + return total + + +def in_stock(items): + result = [] + for item in items: + if item["qty"] > 0: + result.append(item["name"]) + return result + + +def average_price(items): + total = 0 + for item in items: + total += item["price"] + return round(total / len(items), 1) + + +def cheapest_item(items): + cheapest = items[0] + for item in items: + if item["price"] < cheapest["price"]: + cheapest = item + return cheapest + + +def restock_report(items, threshold=5): + low = [] + for i in range(len(items)): + if items[i]["qty"] <= threshold: + low.append(items[i]["name"]) + return low + + +def price_lookup(items): + lookup = defaultdict(list) + for item in items: + lookup[item["qty"]].append(item["price"]) + return lookup + + +def main(): + items = parse_all(inventory_lines) + print("Parsed: ", len(items), "items") + print("Total value: ", total_value(items)) + print("In stock: ", in_stock(items)) + print("Avg price: ", average_price(items)) + print("Cheapest: ", cheapest_item(items)) + print("Restock: ", restock_report(items)) + print("Lookup: ", price_lookup(items)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/h1/exercise_1.py b/h/exercise_h1.py similarity index 100% rename from h1/exercise_1.py rename to h/exercise_h1.py diff --git a/hashing/task2/exercise_1.py b/hashing/task2/exercise_1.py index 43b9d82..0e357ad 100644 --- a/hashing/task2/exercise_1.py +++ b/hashing/task2/exercise_1.py @@ -1,9 +1,8 @@ def main(): - nums = [3,3] + nums = [3, 3] t = 6 print(twoSum(nums, t)) - def twoSum(nums,t): map = {} for a in range(len(nums)): diff --git a/hashing/task2/exercise_5.py b/hashing/task2/exercise_5.py new file mode 100644 index 0000000..786c5a6 --- /dev/null +++ b/hashing/task2/exercise_5.py @@ -0,0 +1,32 @@ +def main(): + nums = [5,2,4,6,1,7] + target = 12 + print(two_sum(nums,target)) + print(two_sum_pairs(nums, target)) + +def two_sum(nums, target): + map = {} + #value, index + + for i in range(len(nums)): + required_num = target - nums[i] + if required_num in map: + return [map[required_num], i] + map[nums[i]] = i + return -1 + +def two_sum_pairs(nums, target): + map = {} + pairs = set() + + for i in range(len(nums)): + required_num = target - nums[i] + if required_num in map: + if required_num > nums[i]: + pairs.add((nums[i], required_num)) + else: + pairs.add((required_num, nums[i])) + map[nums[i]] = i + return pairs +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/hashing/task3/exercise_5.py b/hashing/task3/exercise_5.py new file mode 100644 index 0000000..d847418 --- /dev/null +++ b/hashing/task3/exercise_5.py @@ -0,0 +1,15 @@ +def main(): + nums = [1, 2, 3, 1] + print(is_contains_duplicate(nums)) + +def is_contains_duplicate(nums): + + seen = set() + for num in nums: + if num in seen: + return True + seen.add(num) + return False + #return len(set(nums)) != len(nums) +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/hashing/task4/exercise_7.py b/hashing/task4/exercise_7.py new file mode 100644 index 0000000..76b5fcd --- /dev/null +++ b/hashing/task4/exercise_7.py @@ -0,0 +1,19 @@ +def main(): + + nums = [1, 2, 1] + k = 2 + print(is_contains_nearby_duplicate(nums, k)) + +def is_contains_nearby_duplicate(nums,k): + hash_map = {} + #value, index + + for i in range(len(nums)): + + if nums[i] in hash_map and i - hash_map[nums[i]] == k: + return True + hash_map[nums[i]]=i + return False + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/hashing/task5/exercise_8.py b/hashing/task5/exercise_8.py new file mode 100644 index 0000000..e54ad2f --- /dev/null +++ b/hashing/task5/exercise_8.py @@ -0,0 +1,19 @@ +def main(): + s = "rat" + t = "tar" + print(is_anagram(s,t)) + +def is_anagram(s,t): + count = {} + if len(s) != len(t): + return False + + for i in s: + count[i]=count.get(i,0)+1 + for j in t: + if j not in count or count[j] == 0: + return False + return True + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/hashing/task5/exercise_9.py b/hashing/task5/exercise_9.py new file mode 100644 index 0000000..9b7b133 --- /dev/null +++ b/hashing/task5/exercise_9.py @@ -0,0 +1,29 @@ +from collections import Counter + +def main(): + s = "rat" + t = "car" + print(is_anagram(s,t)) + +def is_anagram(s,t): + + if len(s) != len(t): + return False + count = {} + #value -> count + + for i in s: + count[i] = count.get(i,0)+1 + + for j in t: + if j not in count or count[j] == 0: + return False + count[j] = count.get(j,0)-1 + # if "".join(sorted(s)) != "".join(sorted(t)): + # return False + # if Counter(s) != Counter(t): + # return False + return True + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pig_game/exercise_5.py b/pig_game/exercise_5.py index c7bb620..4ad60ee 100644 --- a/pig_game/exercise_5.py +++ b/pig_game/exercise_5.py @@ -28,11 +28,11 @@ def decide(self, turn_score): print(f"{self.name} {'holds' if choice == 'h' else 'rolls again'}") return choice -class Game(): +class Game: - def __init__(self, players, targe=100): + def __init__(self, players, target=100): self.players = players - self.target = 100 + self.target = target self.current = 0 def play_turn(self, player): diff --git a/pig_game/exercise_6.py b/pig_game/exercise_6.py new file mode 100644 index 0000000..51c0e33 --- /dev/null +++ b/pig_game/exercise_6.py @@ -0,0 +1,83 @@ +import random +from abc import ABC, abstractmethod + + +class Player(ABC): + + def __init__(self, name): + self.name = name + self.total = 0 + + @abstractmethod + def decide(self, turn_score): + pass + +class HumanPlayer(Player): + + def decide(self,turn_score): + while True: + decision = input(f"Current score: {turn_score} hold(h) or roll again(r)").strip().lower() + if decision in ("h", "r"): + + return decision + print("Invalid input. Please enter 'h' or 'r'.") + +class BotPlayer(Player): + + def __init__(self,name): + super().__init__(name) + self.total=0 + + def decide(self, turn_score, hold_at=20): + decision = "h" if turn_score >=hold_at else "r" + print(f"{self.name} {decision} turn score: {turn_score}") + return decision + +class Game: + def __init__(self, players, target=100): + self.players=players + self.target=target + self.current=0 + + def play_turn(self, player): + turn_score = 0 + + while True: + die = random.randint(1,6) + + if die == 1: + print("Rolled - 1") + return 0 + turn_score+=die + print(f"Rolled {die}, turn score: {turn_score}") + + if player.decide(turn_score) == "h": + return turn_score + + def play(self): + while True: + player = self.players[self.current] + print(f"\n--- {player.name} turn ---") + player.total += self.play_turn(player) + print(f"{player.name} total score: {player.total}") + + if player.total >= 100: + print(f"{player.name} won!") + break + self.current = 1-self.current + +def main(): + while True: + mode = input("Play against player(p) or bot(b)").strip().lower() + if mode in ("p","b"): + if mode == "b": + game = Game([HumanPlayer("You"), BotPlayer("Bot")]) + else: + game = Game([HumanPlayer("Player 1"), HumanPlayer("Player 2")]) + break + print("Invalid input. Please enter 'p' or 'b'.") + + game.play() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/searching_sorting/binary_search/exercise_6.py b/searching_sorting/binary_search/exercise_6.py new file mode 100644 index 0000000..8437ef1 --- /dev/null +++ b/searching_sorting/binary_search/exercise_6.py @@ -0,0 +1,37 @@ +def main(): + nums = [82, 22, 56, 64, 73, 12] + ordered_nums = bubble_sort(nums) + target = 22 + print(ordered_nums) + print(binary_search(ordered_nums, target)) + +def bubble_sort(nums): + + for i in range(len(nums)): + swapped = False + for j in range(len(nums) - i - 1): + if nums[j] > nums[j+1]: + nums[j], nums[j+1] = nums[j+1], nums[j] + swapped = True + if swapped == False: + break + + return nums + +def binary_search(nums, target): + left = 0 + right = len(nums)-1 + + while left<=right: + mid = left+ (right - left) // 2 + if nums[mid] == target: + return mid + elif nums[mid] > target: + right = mid - 1 + else: + left = mid + 1 + + return -1 + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sliding_window/task1/exercise_6.py b/sliding_window/task1/exercise_6.py new file mode 100644 index 0000000..bdd518c --- /dev/null +++ b/sliding_window/task1/exercise_6.py @@ -0,0 +1,20 @@ +def main(): + nums = [2,1,5,2,3,2] + k = 3 + print(max_sum_subarray(nums, k)) + +def max_sum_subarray(nums, k): + max_sum = 0 + current_sum = 0 + for i in range(k): + current_sum+=nums[i] + + for j in range(k, len(nums)): + current_sum = current_sum + nums[j] - nums[j-k] + if current_sum > max_sum: + max_sum = current_sum + + return max_sum + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sliding_window/task3/exercise_7.py b/sliding_window/task3/exercise_7.py new file mode 100644 index 0000000..0f6757c --- /dev/null +++ b/sliding_window/task3/exercise_7.py @@ -0,0 +1,23 @@ +def main(): + s = "pwwkew" + print(longest_non_repeating_substring(s)) + +def longest_non_repeating_substring(s): + left=0 + seen = set() + max_length=0 + best_start=0 + for right in range(len(s)): + while s[right] in seen: + seen.remove(s[left]) + left+=1 + + seen.add(s[right]) + + if max_length" + if got == want: + print(f"PASS {name}") + else: + failed += 1 + print(f"FAIL {name}\n got : {got}\n want: {want}") + print(f"\n{len(checks) - failed}/{len(checks)} passed") + +if __name__ == "__main__": + main() \ No newline at end of file