diff --git a/ExpensiveQuerySimulation.py b/ExpensiveQuerySimulation.py new file mode 100644 index 0000000..dab6ee8 --- /dev/null +++ b/ExpensiveQuerySimulation.py @@ -0,0 +1,32 @@ +import LRUCache +import time + + +def expensive_query(query): + print(f"Running query: {query}") + time.sleep(2) # Simulate delay + return f"Result for '{query}'" + +class QueryEngine: + def __init__(self, cache_size=5): + self.cache = LRUCache(cache_size) + + def run_query(self, query): + cached = self.cache.get(query) + if cached != -1: + print(f"Cache hit for: {query}") + return cached + result = expensive_query(query) + self.cache.put(query, result) + return result + + +engine = QueryEngine() + +print(engine.run_query("SELECT * FROM users")) +print(engine.run_query("SELECT * FROM orders")) +print(engine.run_query("SELECT * FROM users")) # Cached! +print(engine.run_query("SELECT * FROM products")) +print(engine.run_query("SELECT * FROM inventory")) +print(engine.run_query("SELECT * FROM orders")) # Might be evicted + diff --git a/Gin_Rummy_Card_Game.py b/Gin_Rummy_Card_Game.py index 56355f7..457adec 100644 --- a/Gin_Rummy_Card_Game.py +++ b/Gin_Rummy_Card_Game.py @@ -22,16 +22,38 @@ class Card: raise ValueError(f"Invalid suit: {suit}") self.rank = rank self.suit = suit + + @staticmethod + def safe_parse_card(card_str): + if not isinstance(card_str, str) or len(card_str) < 2: + return None - @property - def value(self): - if isinstance(self.rank, int): - return self.rank - return 10 if self.rank in ['J', 'Q', 'K'] else 1 # Ace low + rank = card_str[:-1] + suit = card_str[-1] + + if rank not in VALID_RANKS or suit not in Card.suits: + return None + + return Card(rank, suit) + + def __eq__(self, other): + return isinstance(other, Card) and self.rank == other.rank and self.suit == other.suit + + def __hash__(self): + return hash((self.rank, self.suit)) def __repr__(self): return f"{self.rank}{self.suit}" + @property + def value(self): + if self.rank in ['J', 'Q', 'K']: + return 10 + elif self.rank == 'A': + return 1 + else: + return int(self.rank) # works for '2' to '10' + class Deck: def __init__(self): @@ -53,19 +75,30 @@ class DiscardPile: def discard(self, card): self.cards.append(card) - class Hand: def __init__(self): self.cards = [] def add(self, card): self.cards.append(card) - + def remove(self, card): - self.cards.remove(card) + for c in self.cards: + if c == card: + self.cards.remove(c) + return + raise ValueError(f"Card {card} not found in hand.") def get_all_melds(self): return self.find_sets() + self.find_runs() + + def find_card(self, card_str): + rank = card_str[:-1] + suit = card_str[-1] + for card in self.hand.cards: + if card.rank == rank and card.suit == suit: + return card + raise ValueError(f"Card {card_str} not found in hand.") def find_sets(self): groups = defaultdict(list) @@ -76,10 +109,15 @@ class Hand: def find_runs(self): runs = [] suits = defaultdict(list) + + # Group cards by suit for c in self.cards: + if not isinstance(c, Card): + raise TypeError(f"Expected Card object, got {type(c)}: {c}") suits[c.suit].append(c) - for suit, suited_cards in suits.items(): + # For each suit, find sequential runs + for suited_cards in suits.values(): sorted_cards = sorted(suited_cards, key=lambda c: RANK_ORDER[str(c.rank)]) temp = [sorted_cards[0]] for i in range(1, len(sorted_cards)): @@ -93,7 +131,12 @@ class Hand: temp = [sorted_cards[i]] if len(temp) >= 3: runs.append(temp) - return runs + return runs + + def validate_hand(self): + for c in self.cards: + if not isinstance(c, Card): + raise TypeError(f"Invalid card in hand: {c} (type {type(c)})") def non_overlapping_meld_combos(self, all_melds): valid_combos = [] @@ -114,6 +157,7 @@ class Hand: return valid_combos def best_meld_combo(self): + self.validate_hand() all_melds = self.get_all_melds() combos = self.non_overlapping_meld_combos(all_melds) best = max(combos, key=lambda combo: len(set(c for meld in combo for c in meld)), default=[]) @@ -146,11 +190,14 @@ class Player: return card def discard(self, discard_pile, card): + card = Card.safe_parse_card(card) + print("Hand contains:", self.cards) + print("Trying to remove:", card) self.hand.remove(card) discard_pile.discard(card) self.seen_discards.append(card) -def choose_discard(self, discard_pile): +def choose_discard(self): hand = self.hand.cards best_melds = self.hand.best_meld_combo() used = set(c for meld in best_melds for c in meld) @@ -173,72 +220,177 @@ def choose_discard(self, discard_pile): else: return max(hand, key=lambda c: c.value) +class HumanPlayer(Player): + def show_hand(self): + self.hand.validate_hand() + print(f"\nšŸ§ {self.name}'s Hand: {', '.join(str(c) for c in self.hand.cards)}") + print(f"Melds: {[', '.join(str(c) for c in meld) for meld in self.hand.best_meld_combo()]}") + print(f"Deadwood: {', '.join(str(c) for c in self.hand.deadwood())} ({self.hand.deadwood_points()} pts)") + +class AIPlayer(Player): + def show_melds_only(self): + print(f"\nšŸ¤– {self.name}'s Melds:") + for meld in self.hand.best_meld_combo(): + print(" ", ', '.join(str(c) for c in meld)) + print("Hand: [hidden]") + + def choose_discard(self, discard_pile): + hand = self.hand.cards + best_melds = self.hand.best_meld_combo() + used = set(c for meld in best_melds for c in meld) + candidates = [c for c in hand if c not in used] + + top = discard_pile.top() + if top: + # Avoid discarding same rank or suit as top card + candidates = [c for c in candidates if c.rank != top.rank and c.suit != top.suit] + + # Score each candidate based on risk and meld potential + def score(card, hand): + value_penalty = card.value + suit_cluster = sum(1 for c in hand if c.suit == card.suit) + rank_cluster = sum(1 for c in hand if abs(RANK_ORDER[str(c.rank)] - RANK_ORDER[str(card.rank)]) <= 2) + return value_penalty - (suit_cluster + rank_cluster) + + +class TextUI: + def __init__(self, game): + self.game = game + + def show_player_state(self, player): + if isinstance(player, HumanPlayer): + print(f"\nšŸ§ {player.name}'s Hand: {', '.join(str(c) for c in player.hand.cards)}") + print(f"Melds: {[', '.join(str(c) for c in meld) for meld in player.hand.best_meld_combo()]}") + print(f"Deadwood: {', '.join(str(c) for c in player.hand.deadwood())} ({player.hand.deadwood_points()} pts)") + elif isinstance(player, AIPlayer): + print(f"\nšŸ¤– {player.name}'s Melds:") + for meld in player.hand.best_meld_combo(): + print(" ", ', '.join(str(c) for c in meld)) + print("Hand: [hidden]") + + def show_turn_banner(self, player_name): + print("\n" + "-" * 30) + print(f"šŸŽÆ {player_name}'s Turn") + print("-" * 30) + + def show_game_over(self, winner, score): + print("\n" + "=" * 40) + print("šŸŽ‰ GAME OVER šŸŽ‰") + print(f"{winner} wins with {score} points!") + print("=" * 40) + + print("\nWould you like to play again? (Y/N)") + choice = input("> ").strip().upper() + if choice == "Y": + self.reset_game() + else: + print("Thanks for playing Gin Rummy!") + + def show_draw(self, player_name, card): + print(f"{player_name} draws: {card}") + + def show_discard(self, player_name, card): + print(f"{player_name} discards: {card}") class Game: - def __init__(self, players): + def __init__(self, human_player, ai_player): + self.human = human_player + self.ai = ai_player + self.players = [self.human, self.ai] self.deck = Deck() self.discard_pile = DiscardPile() - self.players = players - self.scores = {p.name: 0 for p in players} + self.scores = {p.name: 0 for p in self.players} + self.ui = TextUI(self) def deal(self): for _ in range(10): for player in self.players: + self.human.hand.validate_hand() + self.ai.hand.validate_hand() player.hand.add(self.deck.draw()) self.discard_pile.discard(self.deck.draw()) - def play_round(self): + def play_round(self, player): self.deal() turn = 0 while True: - player = self.players[turn % 2] - print(f"\n{player.name}'s turn") - self.take_turn(player) + current = self.players[turn % 2] + self.ui.show_player_state(player) + self.ui.show_turn_banner(current.name) - if player.hand.deadwood_points() == 0: - print(f"{player.name} goes GIN!") - self.score_round(player, gin=True) + if isinstance(current, HumanPlayer): + self.human_turn(current) + else: + self.ai_turn(current) + + if current.hand.deadwood_points() == 0: + print(f"{current.name} goes GIN!") + self.score_round(current, gin=True) break - elif player.hand.deadwood_points() <= 10: - print(f"{player.name} knocks!") - self.score_round(player, gin=False) + elif current.hand.deadwood_points() <= 10: + print(f"{current.name} knocks!") + self.score_round(current, gin=False) break turn += 1 + + if self.check_game_over(): + return - def take_turn(self, player): - # Simple AI: always draw from stock + def human_turn(self, player): + choice = self.ui.prompt_draw_choice() + from_discard = choice == "D" + drawn = player.draw(self.deck, self.discard_pile, from_discard) + self.ui.show_draw(player.name, drawn) + + discard_input = input("Choose a card to discard (e.g., '5ā™ '): ").strip() + try: + discard_card = player.hand.find_card(discard_input) + player.discard(self.discard_pile, discard_card) + self.ui.show_discard(player.name, discard_card) + except ValueError as e: + print(e) + self.human_turn(player) # retry + + def ai_turn(self, player): + # AI always draws from stock for now drawn = player.draw(self.deck, self.discard_pile, from_discard=False) - print(f"{player.name} draws {drawn}") + self.ui.show_draw(player.name, drawn) - # Discard highest deadwood card - deadwood = player.hand.deadwood() - if deadwood: - discard = max(deadwood, key=lambda c: c.value) - player.discard(self.discard_pile, discard) - print(f"{player.name} discards {discard}") + discard = player.choose_discard(self.discard_pile) + player.discard(self.discard_pile, discard) + self.ui.show_discard(player.name, discard) - def score_round(self, knocker, gin=False): - opponent = [p for p in self.players if p != knocker][0] + def score_round(knocker, opponent, gin=False): knocker_deadwood = knocker.hand.deadwood_points() opponent_deadwood = opponent.hand.deadwood_points() if gin: score = opponent_deadwood + 25 - elif opponent_deadwood <= knocker_deadwood: - score = (knocker_deadwood - opponent_deadwood) + 25 # undercut - self.scores[opponent.name] += score - print(f"{opponent.name} undercuts! Scores {score}") - return - else: - score = opponent_deadwood - knocker_deadwood + return {knocker.name: score, opponent.name: 0}, "gin" - self.scores[knocker.name] += score - print(f"{knocker.name} scores {score}") + if opponent_deadwood <= knocker_deadwood: + score = (knocker_deadwood - opponent_deadwood) + 25 + return {knocker.name: 0, opponent.name: score}, "undercut" - print("\nScores:") - for name, pts in self.scores.items(): - print(f"{name}: {pts}") + score = opponent_deadwood - knocker_deadwood + return {knocker.name: score, opponent.name: 0}, "knock" + + def check_game_over(self, winning_score=100): + for name, score in self.scores.items(): + if score >= winning_score: + self.ui.show_game_over(winner=name, score=score) + return True + return False + + def reset_game(self): + self.deck = Deck() + self.discard_pile = DiscardPile() + for p in self.players: + p.hand = Hand() + p.seen_discards = [] + self.scores = {p.name: 0 for p in self.players} + self.play_round() # Functions to detect melds outside classes # Function to detect sets @@ -256,7 +408,7 @@ def find_runs(cards): for c in cards: suits[c.suit].append(c) - for suit, suited_cards in suits.items(): + for suited_cards in suits.items(): # sort by rank order sorted_cards = sorted(suited_cards, key=lambda c: RANK_ORDER[str(c.rank)]) # scan for consecutive sequences @@ -298,9 +450,12 @@ def non_overlapping_meld_combos(all_melds): # start a game with two players if __name__ == "__main__": - players = [Player("Alice"), Player("Bob")] - game = Game(players) - game.play_round() + human = HumanPlayer("Python") + ai = AIPlayer("Bot") + game = Game(ai, human) + player = human # Human starts first + game.play_round(player) + # Further game logic would go here \ No newline at end of file diff --git a/Graph_Database_Simulation.py b/Graph_Database_Simulation.py new file mode 100644 index 0000000..b543999 --- /dev/null +++ b/Graph_Database_Simulation.py @@ -0,0 +1,50 @@ +class GraphDB: + def __init__(self): + self.nodes = {} # node_id -> properties + self.edges = {} # node_id -> set of connected node_ids + + def add_node(self, node_id, **properties): + self.nodes[node_id] = properties + self.edges.setdefault(node_id, set()) + + def add_edge(self, from_node, to_node): + if from_node in self.nodes and to_node in self.nodes: + self.edges[from_node].add(to_node) + self.edges[to_node].add(from_node) # undirected graph + + def get_node(self, node_id): + return self.nodes.get(node_id) + + def get_neighbors(self, node_id): + return self.edges.get(node_id, set()) + + def find_nodes_by_property(self, key, value): + return [nid for nid, props in self.nodes.items() if props.get(key) == value] + + def shortest_path(self, start, end): + from collections import deque + visited = set() + queue = deque([(start, [start])]) + + while queue: + current, path = queue.popleft() + if current == end: + return path + visited.add(current) + for neighbor in self.edges.get(current, []): + if neighbor not in visited: + queue.append((neighbor, path + [neighbor])) + return None + + +g = GraphDB() +g.add_node("A", type="Person", name="Alice") +g.add_node("B", type="Person", name="Bob") +g.add_node("C", type="City", name="Phoenix") +g.add_edge("A", "B") +g.add_edge("A", "C") +g.add_edge("B", "C") + +print(g.get_neighbors("A")) # {'B', 'C'} +print(g.find_nodes_by_property("type", "Person")) # ['A', 'B'] +print(g.shortest_path("B", "C")) # ['B', 'A', 'C'] \ No newline at end of file diff --git a/LRUCache.py b/LRUCache.py new file mode 100644 index 0000000..c3568ad --- /dev/null +++ b/LRUCache.py @@ -0,0 +1,173 @@ +import json +from datetime import datetime +import os + + +class Node: + def __init__(self, key, value): + self.key = key + self.value = value + self.prev = None + self.next = None + +class LRUCache: + def __init__(self, capacity): + self.capacity = capacity + self.cache = {} # key → Node + self.head = None + self.tail = None + + def _remove(self, node): + if node.prev: + node.prev.next = node.next + else: + self.head = node.next # node was head + if node.next: + node.next.prev = node.prev + else: + self.tail = node.prev # node was tail + + def _add_to_front(self, node): + node.next = self.head + node.prev = None + if self.head: + self.head.prev = node + self.head = node + if not self.tail: + self.tail = node + + def get(self, key): + if key not in self.cache: + return -1 + node = self.cache[key] + self._remove(node) + self._add_to_front(node) + return node.value + + def put(self, key, value): + if key in self.cache: + self._remove(self.cache[key]) + elif len(self.cache) >= self.capacity: + del self.cache[self.tail.key] + self._remove(self.tail) + new_node = Node(key, value) + self._add_to_front(new_node) + self.cache[key] = new_node + + def display(self): + current = self.head + print("Cache (MRU → LRU): ", end="") + while current: + print(f"[{current.key}: {current.value}]", end=" ⇄ ") + current = current.next + print("None") + + def display_reverse(self): + current = self.tail + print("Cache (LRU → MRU): ", end="") + while current: + print(f"[{current.key}: {current.value}]", end=" ⇄ ") + current = current.prev + print("None") + + def to_dict(self): + result = {} + current = self.head + while current: + result[current.key] = current.value + current = current.next + return result + + def from_dict(self, data_dict): + self.cache.clear() + self.head = None + self.tail = None + + for key, value in data_dict.items(): + if len(self.cache) >= self.capacity: + break # Respect capacity limit + new_node = Node(key, value) + self._add_to_front(new_node) + self.cache[key] = new_node + + + def save_to_file(self, filename=None, version="1.0"): + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + if not filename: + filename = f"cache_{timestamp}.json" + + data = { + "version": version, + "timestamp": datetime.now().isoformat(), + "cache": self.to_dict() + } + + with open(filename, 'w') as f: + json.dump(data, f, indent=2) + + print(f"Cache saved to {filename}") + + + def load_from_file(self, filename): + try: + with open(filename, 'r') as f: + data = json.load(f) + version = data.get("version", "unknown") + timestamp = data.get("timestamp", "unknown") + print(f"Loaded cache version {version} from {timestamp}") + self.from_dict(data.get("cache", {})) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Failed to load cache: {e}") + + def load_latest_cache(self, directory=".", prefix="cache_", extension=".json"): + files = [ + f for f in os.listdir(directory) + if f.startswith(prefix) and f.endswith(extension) + ] + if not files: + print("No saved cache files found.") + return + + latest_file = max(files) # Lexicographic sort works with timestamped names + print(f"Loading latest cache: {latest_file}") + self.load_from_file(os.path.join(directory, latest_file)) + +@staticmethod +def list_saved_caches(directory=".", prefix="cache_", extension=".json"): + files = [] + for filename in os.listdir(directory): + if filename.startswith(prefix) and filename.endswith(extension): + files.append(filename) + files.sort() # Sort by name (timestamped) + print("Saved cache files:") + for f in files: + print(" •", f) + return files + + + + +lru = LRUCache(3) +lru.put("A", 1) +lru.put("B", 2) +lru.put("C", 3) +lru.save_to_file("cache.json") + +# Later or in another session +new_lru = LRUCache(3) +new_lru.load_from_file("cache.json") +new_lru.display() + + +lru = LRUCache(4) +lru.put("A", 1) +lru.put("B", 2) +lru.put("C", 3) +lru.put("D", 4) +lru.save_to_file() # Automatically names file like cache_2025-09-27_09-09-00.json + +list_saved_caches() + +lru = LRUCache(3) +lru.load_latest_cache() +lru.display() \ No newline at end of file diff --git a/Neo4j_graph_database_simulation.py b/Neo4j_graph_database_simulation.py new file mode 100644 index 0000000..1f89243 --- /dev/null +++ b/Neo4j_graph_database_simulation.py @@ -0,0 +1,158 @@ +import re + +class Node: + def __init__(self, node_id, labels=None, properties=None): + self.id = node_id + self.labels = set(labels or []) + self.properties = properties or {} + +class Relationship: + def __init__(self, from_node, to_node, rel_type, properties=None): + self.from_node = from_node + self.to_node = to_node + self.type = rel_type + self.properties = properties or {} + +class GraphDB: + def __init__(self): + self.nodes = {} # node_id -> Node + self.relationships = [] # list of Relationship objects + + def add_node(self, node_id, labels=None, **properties): + self.nodes[node_id] = Node(node_id, labels, properties) + + def add_relationship(self, from_node, to_node, rel_type, **properties): + if from_node in self.nodes and to_node in self.nodes: + self.relationships.append(Relationship(from_node, to_node, rel_type, properties)) + + def match(self, label=None, property_key=None, property_value=None): + return [ + node for node in self.nodes.values() + if (not label or label in node.labels) and + (not property_key or node.properties.get(property_key) == property_value) + ] + + def match_relationship(self, rel_type=None, from_node=None, to_node=None): + return [ + rel for rel in self.relationships + if (not rel_type or rel.type == rel_type) and + (not from_node or rel.from_node == from_node) and + (not to_node or rel.to_node == to_node) + ] + + def parse_properties(prop_str): + props = {} + for pair in re.findall(r"(\w+):\s*('([^']*)'|\d+|true|false)", prop_str): + key, raw_val, str_val = pair + if raw_val.startswith("'"): + props[key] = str_val + elif raw_val in ['true', 'false']: + props[key] = raw_val == 'true' + else: + props[key] = int(raw_val) + return props + + def parse_where_clause(where_str): + comparisons = re.findall(r"(\w+)\.(\w+)\s*(=|>|<|>=|<=)\s*('([^']*)'|\d+|true|false)", where_str) + filters = [] + for var, key, op, raw_val, str_val in comparisons: + if raw_val.startswith("'"): + val = str_val + elif raw_val in ['true', 'false']: + val = raw_val == 'true' + else: + val = int(raw_val) + filters.append((key, op, val)) + return filters + + +class CypherEngine: + def __init__(self, graph): + self.graph = graph + + def run(self, query): + # Basic MATCH node pattern: MATCH (n:Label {key: value}) RETURN n + match_node = re.match(r"MATCH\s+\((\w+):(\w+)\s*\{(\w+):\s*'([^']+)'\}\)\s+RETURN\s+\1", query) + if match_node: + var, label, key, value = match_node.groups() + return self.graph.match(label=label, property_key=key, property_value=value) + + # (e.g., MATCH (n:Person {name: 'Alice', age: 30}) RETURN n) + match_node = re.match(r"MATCH\s+\((\w+):(\w+)\s*\{([^}]+)\}\)\s+RETURN\s+\1", query) + if match_node: + var, label, prop_str = match_node.groups() + props = GraphDB.parse_properties(prop_str) + return self.graph.match(label=label, **props) + + # (e.g., MATCH (n:Person) WHERE n.age > 25 RETURN n) + match_node = re.match(r"MATCH\s+\((\w+):(\w+)\)\s*(?:WHERE\s+(.+?))?\s+RETURN\s+\1", query) + if match_node: + var, label, where_str = match_node.groups() + nodes = self.graph.match(label=label) + if where_str: + filters = GraphDB.parse_where_clause(where_str) + def passes(node): + for key, op, val in filters: + node_val = node.properties.get(key) + if node_val is None: + return False + if op == '=' and node_val != val: + return False + if op == '>' and not node_val > val: + return False + if op == '<' and not node_val < val: + return False + if op == '>=' and not node_val >= val: + return False + if op == '<=' and not node_val <= val: + return False + return True + nodes = [n for n in nodes if passes(n)] + return nodes + + + # Basic MATCH relationship pattern: MATCH (a)-[:TYPE]->(b) RETURN a,b + match_rel = re.match(r"MATCH\s+\((\w+)\)-\[:(\w+)\]->\((\w+)\)\s+RETURN\s+\1,\s*\3", query) + # MATCH (a:Person)-[:FRIEND]->(b:Person) RETURN a,b + match_rel = re.match(r"MATCH\s+\((\w+):(\w+)?\)-\[:(\w+)\]->\((\w+):(\w+)?\)\s+RETURN\s+\1,\s*\4", query) + if match_rel: + from_var, from_label, rel_type, to_var, to_label = match_rel.groups() + rels = self.graph.match_relationship(rel_type=rel_type) + return [ + rel for rel in rels + if (not from_label or from_label in self.graph.nodes[rel.from_node].labels) and + (not to_label or to_label in self.graph.nodes[rel.to_node].labels) + ] + return "Unsupported query format" + +# Example usage and test cases +g = GraphDB() +g.add_node("A", labels=["Person"], name="Alice", age=30) +g.add_node("B", labels=["Person"], name="Bob", age=25) +g.add_node("C", labels=["City"], name="Phoenix") + +g.add_relationship("A", "B", "FRIEND", since=2020) +g.add_relationship("A", "C", "LIVES_IN") + +# Match nodes with label 'Person' +people = g.match(label="Person") +# Match relationships of type 'FRIEND' +friendships = g.match_relationship(rel_type="FRIEND") +engine = CypherEngine(g) +result1 = engine.run("MATCH (n:Person {name: 'Alice'}) RETURN n") +result2 = engine.run("MATCH (n:Person) WHERE n.age > 25 RETURN n") +result3 = engine.run("MATCH (a:Person)-[:FRIEND]->(b:Person) RETURN a,b") +print("People:", [(p.id, p.properties) for p in people]) +print("Friendships:", [ (rel.from_node, rel.to_node, rel.properties) for rel in friendships]) +print("Result 1:", [(n.id, n.properties) for n in result1]) +print("Result 2:", [(n.id, n.properties) for n in result2]) +print("Result 3:", [ (rel.from_node, rel.to_node, rel.properties) for rel in result3]) + +# Expected Output: +# People: [('A', {'name': 'Alice', 'age': 30}), ('B', {'name': 'Bob', 'age': 25})] +# Friendships: [('A', 'B', {'since': 2020})] +# Result 1: [('A', {'name': 'Alice', 'age': 30})] +# Result 2: [('A', {'name': 'Alice', 'age': 30))] +# Result 3: [('A', 'B', {'since': 2020})] + + diff --git a/linked_list.py b/linked_list.py new file mode 100644 index 0000000..6a35ece --- /dev/null +++ b/linked_list.py @@ -0,0 +1,118 @@ +class Node: + def __init__(self, data): + self.data = data + self.prev = None + self.next = None + +class DoublyLinkedList: + def __init__(self): + self.head = None + + def append(self, data): + new_node = Node(data) + if not self.head: + self.head = new_node + return + current = self.head + while current.next: + current = current.next + current.next = new_node + new_node.prev = current + + def insert(self, index, data): + new_node = Node(data) + if index == 0: + new_node.next = self.head + if self.head: + self.head.prev = new_node + self.head = new_node + return + current = self.head + for _ in range(index - 1): + if not current: + raise IndexError("Index out of bounds") + current = current.next + if not current: + raise IndexError("Index out of bounds") + new_node.next = current.next + new_node.prev = current + if current.next: + current.next.prev = new_node + current.next = new_node + + def delete(self, data): + current = self.head + while current: + if current.data == data: + if current.prev: + current.prev.next = current.next + else: + self.head = current.next + if current.next: + current.next.prev = current.prev + return + current = current.next + raise ValueError(f"{data} not found in list") + + def reverse(self): + current = self.head + prev_node = None + while current: + next_node = current.next + current.next = prev_node + current.prev = next_node + prev_node = current + current = next_node + self.head = prev_node + + def find(self, data): + current = self.head + index = 0 + while current: + if current.data == data: + return index + current = current.next + index += 1 + return -1 + + def to_list(self): + result = [] + current = self.head + while current: + result.append(current.data) + current = current.next + return result + + def display_forward(self): + current = self.head + while current: + print(current.data, end=" ⇄ ") + current = current.next + print("None") + + def display_backward(self): + current = self.head + if not current: + print("None") + return + while current.next: + current = current.next + while current: + print(current.data, end=" ⇄ ") + current = current.prev + print("None") + +dll = DoublyLinkedList() +dll.append(10) +dll.append(20) +dll.append(30) +dll.insert(1, 15) # Insert 15 at index 1 +dll.delete(20) # Remove node with value 20 +dll.reverse() # Reverse the list +print("Index of 15:", dll.find(15)) +dll.append(50) +print(dll.to_list()) # Output: [30, 15, 10, 50] + + +dll.display_forward() # Output: 30 ⇄ 15 ⇄ 10 ⇄ None +dll.display_backward() # Output: 10 ⇄ 15 ⇄ 30 ⇄ None \ No newline at end of file