Skip to content

Commit b2798cb

Browse files
MengAiDevMengAiDev
authored andcommitted
feat(collections): Add HeapDict class
HeapDict is a hybrid data structure combining dictionary and heap functionalities, providing efficient key-value access and priority-based operations. Key features include: - Basic dictionary operations: set/get/delete key-value pairs, key existence check, size retrieval, key iteration - Heap operations: remove-and-return minimum key-value pair, peek minimum pair without removal, update priority of existing keys
1 parent c5e77af commit b2798cb

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

Lib/collections/__init__.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add HeapDict in collection

0 commit comments

Comments
 (0)