@@ -1606,3 +1606,148 @@ def upper(self):
16061606
16071607 def zfill (self , width ):
16081608 return self .__class__ (self .data .zfill (width ))
1609+
1610+
1611+ ################################################################################
1612+ ### HeapDict
1613+ ################################################################################
1614+
1615+ import collections .abc
1616+ import heapq
1617+ from typing import Any , Dict , Iterator , List , Optional , Tuple , TypeVar , Union
1618+
1619+ K = TypeVar ('K' )
1620+ V = TypeVar ('V' )
1621+
1622+ class HeapDict (collections .abc .MutableMapping ):
1623+ """Dictionary that maintains heap property based on values.
1624+
1625+ HeapDict combines the functionality of a dictionary with a heap,
1626+ providing efficient access to key-value pairs while maintaining
1627+ a heap property for priority-based operations.
1628+
1629+ Basic operations:
1630+ - d[key] = value: Set a key-value pair
1631+ - value = d[key]: Get value by key
1632+ - del d[key]: Remove a key-value pair
1633+ - key in d: Test if key exists
1634+ - len(d): Get number of items
1635+ - iter(d): Iterate through keys
1636+
1637+ Heap operations:
1638+ - d.popmin(): Remove and return the (key, value) pair with minimum value
1639+ - d.peekmin(): Return the (key, value) pair with minimum value without removing
1640+ - d.update_priority(key, new_value): Update the value/priority of an existing key
1641+ """
1642+
1643+ def __init__ (self , * args , ** kwargs ):
1644+ """Initialize a new HeapDict with optional initial values."""
1645+ self ._dict : Dict [K , V ] = {} # Maps keys to values
1646+ self ._heap : List [Tuple [V , K , int ]] = [] # List of (value, key, counter)
1647+ self ._counter = 0 # Used to break ties for values that compare equal
1648+ self ._removed_keys = set () # Track removed keys for lazy deletion
1649+
1650+ # Add initial items
1651+ if args or kwargs :
1652+ self .update (* args , ** kwargs )
1653+
1654+ def __setitem__ (self , key : K , value : V ) -> None :
1655+ """Set a key-value pair, maintaining heap property."""
1656+ if key in self ._dict :
1657+ self .update_priority (key , value )
1658+ else :
1659+ self ._dict [key ] = value
1660+ count = self ._counter
1661+ self ._counter += 1
1662+ heapq .heappush (self ._heap , (value , key , count ))
1663+
1664+ def __getitem__ (self , key : K ) -> V :
1665+ """Get value by key."""
1666+ return self ._dict [key ]
1667+
1668+ def __delitem__ (self , key : K ) -> None :
1669+ """Remove a key-value pair."""
1670+ if key not in self ._dict :
1671+ raise KeyError (key )
1672+
1673+ # Mark the key as removed
1674+ self ._removed_keys .add (key )
1675+
1676+ # Remove from dictionary
1677+ del self ._dict [key ]
1678+
1679+ # Note: We don't remove from the heap here for efficiency.
1680+ # Instead, we do lazy deletion during heap operations.
1681+
1682+ def __iter__ (self ) -> Iterator [K ]:
1683+ """Iterate through keys."""
1684+ return iter (self ._dict )
1685+
1686+ def __len__ (self ) -> int :
1687+ """Return the number of items."""
1688+ return len (self ._dict )
1689+
1690+ def __repr__ (self ) -> str :
1691+ """Return string representation."""
1692+ return f"{ self .__class__ .__name__ } ({ dict (self .items ())} )"
1693+
1694+ def _clean_heap (self ) -> None :
1695+ """Clean the heap by removing marked items."""
1696+ if len (self ._removed_keys ) > len (self ._heap ) // 2 :
1697+ # If too many removed items, rebuild the heap
1698+ new_heap = [(v , k , c ) for v , k , c in self ._heap if k in self ._dict ]
1699+ heapq .heapify (new_heap )
1700+ self ._heap = new_heap
1701+ self ._removed_keys .clear ()
1702+
1703+ def popmin (self ) -> Tuple [K , V ]:
1704+ """Remove and return the (key, value) pair with minimum value."""
1705+ if not self ._dict :
1706+ raise KeyError ("popmin from an empty HeapDict" )
1707+
1708+ # Skip items that were already removed
1709+ while self ._heap :
1710+ value , key , _ = heapq .heappop (self ._heap )
1711+ if key not in self ._removed_keys and key in self ._dict :
1712+ del self ._dict [key ]
1713+ return key , value
1714+
1715+ # This should never happen if the data structure is consistent
1716+ raise RuntimeError ("Heap is inconsistent with dictionary" )
1717+
1718+ def peekmin (self ) -> Tuple [K , V ]:
1719+ """Return the (key, value) pair with minimum value without removing it."""
1720+ if not self ._dict :
1721+ raise KeyError ("peekmin from an empty HeapDict" )
1722+
1723+ # Skip items that were already removed
1724+ while self ._heap :
1725+ value , key , _ = self ._heap [0 ]
1726+ if key not in self ._removed_keys and key in self ._dict :
1727+ return key , value
1728+
1729+ # If the top item is removed, pop it and continue
1730+ heapq .heappop (self ._heap )
1731+
1732+ # This should never happen if the data structure is consistent
1733+ raise RuntimeError ("Heap is inconsistent with dictionary" )
1734+
1735+ def update_priority (self , key : K , new_value : V ) -> None :
1736+ """Update the value/priority of an existing key."""
1737+ if key not in self ._dict :
1738+ raise KeyError (key )
1739+
1740+ # Update the dictionary
1741+ self ._dict [key ] = new_value
1742+
1743+ # Mark the old entry as removed
1744+ self ._removed_keys .add (key )
1745+
1746+ # Add a new entry to the heap
1747+ count = self ._counter
1748+ self ._counter += 1
1749+ heapq .heappush (self ._heap , (new_value , key , count ))
1750+
1751+ # Clean the heap if there are too many removed items
1752+ if len (self ._removed_keys ) > len (self ._heap ) // 2 :
1753+ self ._clean_heap ()
0 commit comments