orderedset.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # From: https://github.com/ActiveState/code/blob/master/recipes/Python/576696_OrderedSet_with_Weakrefs/ # noqa
  2. import typing as t
  3. import weakref
  4. class Link:
  5. """Representation of one item in a doubly-linked list."""
  6. __slots__ = ("prev", "next", "key", "__weakref__")
  7. prev: "Link"
  8. next: "Link"
  9. key: str
  10. class OrderedSet(t.MutableSet[str]):
  11. """A set that remembers the order in which items were added."""
  12. # Big-O running times for all methods are the same as for regular sets.
  13. # The internal self.__map dictionary maps keys to links in a doubly linked
  14. # list. The circular doubly linked list starts and ends with a sentinel
  15. # element. The sentinel element never gets deleted (this simplifies the
  16. # algorithm). The prev/next links are weakref proxies (to prevent circular
  17. # references). Individual links are kept alive by the hard reference in
  18. # self.__map. Those hard references disappear when a key is deleted from
  19. # an OrderedSet.
  20. def __init__(self, iterable: t.Optional[t.Iterable[str]] = None):
  21. self.__root = root = Link() # sentinel node for doubly linked list
  22. root.prev = root.next = root
  23. self.__map: t.MutableMapping[str, Link] = {} # key --> link
  24. if iterable is not None:
  25. self |= iterable # type: ignore
  26. def __len__(self) -> int:
  27. return len(self.__map)
  28. def __contains__(self, key: object) -> bool:
  29. return key in self.__map
  30. def add(self, key: str) -> None:
  31. # Store new key in a new link at the end of the linked list
  32. if key not in self.__map:
  33. self.__map[key] = link = Link()
  34. root = self.__root
  35. last = root.prev
  36. link.prev, link.next, link.key = last, root, key
  37. last.next = root.prev = weakref.proxy(link)
  38. def discard(self, key: str) -> None:
  39. # Remove an existing item using self.__map to find the link which is
  40. # then removed by updating the links in the predecessor and successors.
  41. if key in self.__map:
  42. link = self.__map.pop(key)
  43. link.prev.next = link.next
  44. link.next.prev = link.prev
  45. def __iter__(self) -> t.Generator[str, None, None]:
  46. # Traverse the linked list in order.
  47. root = self.__root
  48. curr = root.next
  49. while curr is not root:
  50. yield curr.key
  51. curr = curr.next
  52. def __reversed__(self) -> t.Generator[str, None, None]:
  53. # Traverse the linked list in reverse order.
  54. root = self.__root
  55. curr = root.prev
  56. while curr is not root:
  57. yield curr.key
  58. curr = curr.prev
  59. def pop(self, last: bool = True) -> str:
  60. if not self:
  61. raise KeyError("set is empty")
  62. key = next(reversed(self)) if last else next(iter(self))
  63. self.discard(key)
  64. return key
  65. def __repr__(self) -> str:
  66. if not self:
  67. return f"{self.__class__.__name__}()"
  68. return f"{self.__class__.__name__}({list(self)!r})"
  69. def __str__(self) -> str:
  70. return self.__repr__()
  71. def __eq__(self, other: object) -> bool:
  72. if isinstance(other, OrderedSet):
  73. return len(self) == len(other) and list(self) == list(other)
  74. other = t.cast(t.Iterable[str], other)
  75. return not self.isdisjoint(other)