22"""
33Coalesced hashing (hybrid of open addressing + chaining inside the table).
44
5- Reference: https://en.wikipedia.org/wiki/Hash_table#Coalesced_hashing
5+ Reference: [ https://en.wikipedia.org/wiki/Hash_table#Coalesced_hashing](https://en.wikipedia.org/wiki/Hash_table#Coalesced_hashing)
66"""
77
88from __future__ import annotations
99
1010from collections .abc import Iterator , MutableMapping
1111from dataclasses import dataclass
12- from typing import TypeVar
12+ from typing import Generic , TypeVar
1313
1414KEY = TypeVar ("KEY" )
1515VAL = TypeVar ("VAL" )
1616
1717
1818@dataclass (slots = True )
19- class _Node [KEY , VAL ]:
19+ class _Node ( Generic [KEY , VAL ]): # noqa: UP046
2020 key : KEY
2121 val : VAL
2222 next : int # -1 means end of chain
@@ -83,18 +83,18 @@ def __setitem__(self, key: KEY, val: VAL) -> None:
8383 # Search chain for update.
8484 cur = home
8585 while True :
86- n = self ._table [cur ]
87- if n is None :
86+ # Explicitly type the current node to satisfy mypy
87+ current_node = self ._table [cur ]
88+ if current_node is None :
89+ # Should not happen if logic is correct, but handles None safety
8890 break
89- # FIX: Ensure n is not None before access
90- assert n is not None
9191
92- if n .key == key :
93- n .val = val
92+ if current_node .key == key :
93+ current_node .val = val
9494 return
95- if n .next == - 1 :
95+ if current_node .next == - 1 :
9696 break
97- cur = n .next
97+ cur = current_node .next
9898
9999 # Insert new node at a free slot and link it.
100100 free = self ._find_free_from_end ()
@@ -105,10 +105,10 @@ def __setitem__(self, key: KEY, val: VAL) -> None:
105105
106106 self ._table [free ] = _Node (key , val , - 1 )
107107
108- # FIX: Ensure we are linking from a valid node
108+ # Link the previous end of chain to the new free slot
109+ # We re-fetch the node at 'cur' to be safe
109110 if (tail_node := self ._table [cur ]) is not None :
110111 tail_node .next = free
111-
112112 self ._len += 1
113113
114114 def __getitem__ (self , key : KEY ) -> VAL :
@@ -118,7 +118,6 @@ def __getitem__(self, key: KEY) -> VAL:
118118 node = self ._table [cur ]
119119 if node is None :
120120 break
121- assert node is not None
122121 if node .key == key :
123122 return node .val
124123 cur = node .next
@@ -133,7 +132,6 @@ def __delitem__(self, key: KEY) -> None:
133132 node = self ._table [cur ]
134133 if node is None :
135134 break
136- assert node is not None
137135 if node .key == key :
138136 # If deleting head: copy next node into home if exists
139137 # (keeps chains valid).
@@ -142,17 +140,17 @@ def __delitem__(self, key: KEY) -> None:
142140 self ._table [cur ] = None
143141 else :
144142 nxt = node .next
145- # Safely copy next node data
146- nxt_node = self ._table [nxt ]
147- # Mypy needs to know nxt_node exists if we are copying from it
148- if nxt_node is not None :
143+ next_node = self ._table [nxt ]
144+ # Must assert next_node is not None for mypy
145+ if next_node is not None :
149146 self ._table [cur ] = _Node (
150- nxt_node .key ,
151- nxt_node .val ,
152- nxt_node .next ,
147+ next_node .key ,
148+ next_node .val ,
149+ next_node .next ,
153150 )
154151 self ._table [nxt ] = None
155152 else :
153+ # Update previous node's next pointer
156154 prev_node = self ._table [prev ]
157155 if prev_node is not None :
158156 prev_node .next = node .next
0 commit comments