More python files

This commit is contained in:
Donald Calloway 2025-09-27 14:51:07 -07:00
parent 657e0ea6a8
commit 7c63daa757
6 changed files with 737 additions and 51 deletions

View File

@ -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

View File

@ -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

View File

@ -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']

173
LRUCache.py Normal file
View File

@ -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()

View File

@ -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})]

118
linked_list.py Normal file
View File

@ -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