You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
3.3 KiB
92 lines
3.3 KiB
# From: https://github.com/ActiveState/code/blob/master/recipes/Python/576696_OrderedSet_with_Weakrefs/ # noqa
|
|
import typing as t
|
|
import weakref
|
|
|
|
|
|
class Link:
|
|
"""Representation of one item in a doubly-linked list."""
|
|
|
|
__slots__ = ("prev", "next", "key", "__weakref__")
|
|
prev: "Link"
|
|
next: "Link"
|
|
key: str
|
|
|
|
|
|
class OrderedSet(t.MutableSet[str]):
|
|
"""A set that remembers the order in which items were added."""
|
|
|
|
# Big-O running times for all methods are the same as for regular sets.
|
|
# The internal self.__map dictionary maps keys to links in a doubly linked
|
|
# list. The circular doubly linked list starts and ends with a sentinel
|
|
# element. The sentinel element never gets deleted (this simplifies the
|
|
# algorithm). The prev/next links are weakref proxies (to prevent circular
|
|
# references). Individual links are kept alive by the hard reference in
|
|
# self.__map. Those hard references disappear when a key is deleted from
|
|
# an OrderedSet.
|
|
|
|
def __init__(self, iterable: t.Optional[t.Iterable[str]] = None):
|
|
self.__root = root = Link() # sentinel node for doubly linked list
|
|
root.prev = root.next = root
|
|
self.__map: t.MutableMapping[str, Link] = {} # key --> link
|
|
if iterable is not None:
|
|
self |= iterable # type: ignore
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.__map)
|
|
|
|
def __contains__(self, key: object) -> bool:
|
|
return key in self.__map
|
|
|
|
def add(self, key: str) -> None:
|
|
# Store new key in a new link at the end of the linked list
|
|
if key not in self.__map:
|
|
self.__map[key] = link = Link()
|
|
root = self.__root
|
|
last = root.prev
|
|
link.prev, link.next, link.key = last, root, key
|
|
last.next = root.prev = weakref.proxy(link)
|
|
|
|
def discard(self, key: str) -> None:
|
|
# Remove an existing item using self.__map to find the link which is
|
|
# then removed by updating the links in the predecessor and successors.
|
|
if key in self.__map:
|
|
link = self.__map.pop(key)
|
|
link.prev.next = link.next
|
|
link.next.prev = link.prev
|
|
|
|
def __iter__(self) -> t.Generator[str, None, None]:
|
|
# Traverse the linked list in order.
|
|
root = self.__root
|
|
curr = root.next
|
|
while curr is not root:
|
|
yield curr.key
|
|
curr = curr.next
|
|
|
|
def __reversed__(self) -> t.Generator[str, None, None]:
|
|
# Traverse the linked list in reverse order.
|
|
root = self.__root
|
|
curr = root.prev
|
|
while curr is not root:
|
|
yield curr.key
|
|
curr = curr.prev
|
|
|
|
def pop(self, last: bool = True) -> str:
|
|
if not self:
|
|
raise KeyError("set is empty")
|
|
key = next(reversed(self)) if last else next(iter(self))
|
|
self.discard(key)
|
|
return key
|
|
|
|
def __repr__(self) -> str:
|
|
if not self:
|
|
return f"{self.__class__.__name__}()"
|
|
return f"{self.__class__.__name__}({list(self)!r})"
|
|
|
|
def __str__(self) -> str:
|
|
return self.__repr__()
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if isinstance(other, OrderedSet):
|
|
return len(self) == len(other) and list(self) == list(other)
|
|
other = t.cast(t.Iterable[str], other)
|
|
return not self.isdisjoint(other)
|
|
|