]> Untitled Git - axy/ft/a-maze-ing.git/commitdiff
Performance improvements for dirty tracker using shuffleable set
authorAxy <gilliardmarthey.axel@gmail.com>
Tue, 17 Mar 2026 01:48:54 +0000 (02:48 +0100)
committerAxy <gilliardmarthey.axel@gmail.com>
Tue, 17 Mar 2026 01:48:54 +0000 (02:48 +0100)
15 files changed:
.gitignore
Makefile
__main__.py
amazeing/__init__.py
amazeing/config/config_parser.py
amazeing/maze_class/maze.py
amazeing/maze_class/maze_dirty_tracker.py
amazeing/maze_class/maze_network_tracker.py
amazeing/maze_display/TTYdisplay.py
amazeing/maze_display/__init__.py
amazeing/maze_display/backend.py
amazeing/maze_display/layout.py
amazeing/maze_make_pacman.py
amazeing/utils/avl.py
amazeing/utils/randset.py [new file with mode: 0644]

index 64d02bcfe770bc813131e5889fed0ae143c82731..93e98418291a777927748a3e124dc63438900720 100644 (file)
@@ -1,2 +1,4 @@
 **/__pycache__
 **/.mypy_cache
+out.prof
+.venv
index dd76e6fef67870d6252d46900aaf732c505db23b..55d0384d0425dda34e12caa415b05fdbb7c204db 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,11 @@
 install:
+       pip install flake8 mypy flameprof
+
+.venv:
+       python -m venv .venv
+
+venv_bash: .venv
+       bash --init-file <(echo ". ~/.bashrc; source .venv/bin/activate")
 
 run:
 
@@ -9,5 +16,9 @@ lint:
        mypy . --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs
 
 lint-strict:
-       flake8 .
-       mypy . --strict
+       bash -c "flake8 . --extend-exclude .venv; mypy . --strict"
+
+profile:
+       python -m cProfile -o out.prof __main__.py
+
+.PHONY: install venv  run clean lint lint-strict profile
index 9b35b9c280c988b02f906ac05d92fc70d3d9bc70..5619b5e2c6e5359dfe46a3c673e331c6d8e79e62 100644 (file)
@@ -1,3 +1,4 @@
+from sys import stderr
 import time
 from amazeing import (
     Maze,
@@ -5,7 +6,6 @@ from amazeing import (
     Pattern,
     maze_make_pacman,
     maze_make_perfect,
-    maze_make_empty,
 )
 import random
 
@@ -27,6 +27,7 @@ dims = IVec2(config.width, config.height)
 maze = Maze(dims)
 
 dirty_tracker = MazeDirtyTracker(maze)
+pacman_tracker = MazeDirtyTracker(maze)
 
 maze.outline()
 
@@ -92,16 +93,20 @@ def display_maze(maze: Maze) -> None:
 
 def poll_events(timeout_ms: int = -1) -> None:
     start = time.monotonic()
-    elapsed_ms = lambda: int((time.monotonic() - start) * 1000.0)
-    timeout = lambda: (
-        max(timeout_ms - elapsed_ms(), 0) if timeout_ms != -1 else -1
-    )
+
+    def elapsed_ms() -> int:
+        return int((time.monotonic() - start) * 1000.0)
+
+    def timeout() -> int:
+        return max(timeout_ms - elapsed_ms(), 0) if timeout_ms != -1 else -1
+
+    backend.present()
     while True:
         event = backend.event(timeout())
-        if event is None:
-            if timeout_ms == -1:
-                continue
-            return
+        if isinstance(event, bool):
+            if timeout() == 0 and not event:
+                return
+            continue
         if isinstance(event, CloseRequested) or event.sym == "q":
             exit(0)
         if event.sym == "c":
@@ -111,7 +116,6 @@ def poll_events(timeout_ms: int = -1) -> None:
             empty.cycle()
         else:
             continue
-        backend.present()
 
 
 prev_solution: list[Cardinal] = []
@@ -164,7 +168,7 @@ def elipse_manhattan(a: IVec2, b: IVec2, a2: IVec2, b2: IVec2) -> int:
 #    solution = maze.pathfind(CellCoord(config.entry), CellCoord(config.exit))
 #    if solution is None or prev_solution == solution:
 #        return
-#    prev_tiles = Cardinal.path_to_tiles(prev_solution, CellCoord(config.entry))
+#   prev_tiles = Cardinal.path_to_tiles(prev_solution, CellCoord(config.entry))
 #    tiles = Cardinal.path_to_tiles(solution, CellCoord(config.entry))
 #    backend.set_style(empty.curr_style())
 #    for tile in prev_tiles:
@@ -179,7 +183,7 @@ def elipse_manhattan(a: IVec2, b: IVec2, a2: IVec2, b2: IVec2) -> int:
 
 network_tracker = MazeNetworkTracker(maze)
 maze_make_perfect(maze, network_tracker, callback=display_maze)
-# maze_make_pacman(maze, walls_const, callback=display_maze)
+maze_make_pacman(maze, walls_const, pacman_tracker, callback=display_maze)
 
 
 # pathfind()
@@ -191,4 +195,5 @@ while False:
     # maze_make_empty(maze, walls_const, callback=display_maze)
     # poll_events(200)
     # maze._rebuild()
-poll_events()
+while True:
+    poll_events(16)
index 2557f93375e87b023aa1d3afc78c2e5478c6fc07..a68e0eb92fe146c31737180ebd9a6c3804663767 100644 (file)
@@ -2,7 +2,7 @@ __version__ = "0.0.0"
 __author__ = "luflores & agilliar"
 
 from amazeing.maze_class import WallCoord, Maze, Pattern
-from amazeing.maze_display import Backend, IVec2, TTYBackend
+from amazeing.maze_display import IVec2, TTYBackend
 from .maze_make_pacman import maze_make_pacman
 from .maze_make_perfect import maze_make_perfect
 from .maze_make_empty import maze_make_empty
@@ -11,7 +11,6 @@ __all__ = [
     "WallCoord",
     "Maze",
     "Pattern",
-    "Backend",
     "IVec2",
     "TTYBackend",
     "maze_make_pacman",
index e9b70419663f8f944891b4e37f878726db004fca..a18e305bb65137086f1c06f183b4a032ae4a1251 100644 (file)
@@ -252,7 +252,7 @@ def DefaultedStrField[T, U](
                         "Failed to construct defaulted field " + self.name()
                     )
                 acc.append(res[0])
-            return self.merge(acc)
+            return self.merge(acc)  # type: ignore
 
     return Inner
 
@@ -260,7 +260,7 @@ def DefaultedStrField[T, U](
 def MappedField[T, U, V](
     cls: Type[ConfigField[T, U]], mapping: Callable[[U], V]
 ) -> Type[ConfigField[T, V]]:
-    class Inner(ConfigField[T, V]):  # type: ignore
+    class Inner(ConfigField[T, V]):
         def __init__(self, name: str) -> None:
             self.__inner = cls(name)
             super().__init__(name)
@@ -334,7 +334,7 @@ def line_parser[T](
 
 
 def fields_parser(
-    fields_raw: dict[str, type[ConfigField]],
+    fields_raw: dict[str, type[ConfigField[Any]]],
 ) -> Parser[dict[str, Any]]:
     fields = {key: cls(key) for key, cls in fields_raw.items()}
     parse_line = terminated(line_parser(fields), cut(tag("\n")))
index 71b7166ea6661d86162cd11b92c84e08e01fb386..4acb454a417ffa651f0aa68b011233ee076837de 100644 (file)
@@ -1,5 +1,4 @@
-from typing import Callable, Generator, Iterable, cast
-
+from typing import Callable, Generator, Iterable
 from amazeing.maze_display.backend import IVec2
 from .maze_coords import (
     CellCoord,
@@ -83,3 +82,28 @@ class Maze:
 
     def walls_empty(self) -> Iterable[WallCoord]:
         return filter(lambda w: not self.get_wall(w), self.all_walls())
+
+    def wall_cuts_cycle(self, wall: WallCoord) -> bool:
+        return any(
+            (
+                len(
+                    [
+                        ()
+                        for wall in self.get_walls_checked(list(cell.walls()))
+                        if wall
+                    ]
+                )
+                >= (3 if self.get_wall(wall) else 2)
+            )
+            for cell in wall.neighbour_cells()
+        )
+
+    def wall_leaf_neighbours(self, coord: WallCoord) -> list[WallCoord]:
+        leaf_f: Callable[
+            [Callable[[WallCoord], list[WallCoord]]], list[WallCoord]
+        ] = lambda f: (
+            list(filter(lambda c: self.check_coord(c), f(coord)))
+            if all(not wall for wall in self.get_walls_checked(f(coord)))
+            else []
+        )
+        return leaf_f(WallCoord.a_neighbours) + leaf_f(WallCoord.b_neighbours)
index 4e09f8c56914a133a317841d33a57cec7e236bb3..7300320aa17c4ee7f89522aa972499ff683348a8 100644 (file)
@@ -1,27 +1,31 @@
 from collections.abc import Iterable
 from amazeing.maze_class.maze import Maze
 from amazeing.maze_class.maze_coords import WallCoord
+from amazeing.utils.randset import Randset
 
 
 class MazeDirtyTracker:
     def __init__(self, maze: Maze) -> None:
         self.__maze: Maze = maze
-        self.__dirty: set[WallCoord] = set()
+        self.__dirty: Randset[WallCoord] = Randset()
         maze.observers.add(self.__observer)
 
-    def __del__(self):
+    def __repr__(self) -> str:
+        return f"MazeDirtyTracker({self.__dirty})"
+
+    def __del__(self) -> None:
         self.__maze.observers.discard(self.__observer)
 
     def __observer(self, wall: WallCoord) -> None:
         self.__dirty ^= {wall}
 
-    def end(self):
-        self.__maze.observers.discard(self.__observer)
-
-    def clear(self) -> set[WallCoord]:
+    def clear(self) -> Randset[WallCoord]:
         res = self.__dirty
-        self.__dirty = set()
+        self.__dirty = Randset()
         return res
 
     def curr_dirty(self) -> Iterable[WallCoord]:
-        return list(self.__dirty)
+        return self.__dirty
+
+    def end(self) -> None:
+        self.__maze.observers.discard(self.__observer)
index 14c1e0a398f55fc2f21284dbffa59e4897cd8367..8b0297b570249157727910d5aa4f26a4db19d3af 100644 (file)
@@ -176,5 +176,5 @@ class MazeNetworkTracker:
     def wall_bisects(self, wall: WallCoord) -> bool:
         return self.__forest.wall_bisects(wall)
 
-    def end(self):
+    def end(self) -> None:
         self.__maze.observers.discard(self.__observer)
index a08fad3d4925d000e52987a0ed06290f7a8003c6..5d7b4c9b3ee013b0162066b77246603dc68c02da 100644 (file)
@@ -13,7 +13,7 @@ from amazeing.maze_display.layout import (
     layout_sort_chunked,
     layout_split,
 )
-from .backend import Backend, IVec2, BackendEvent, KeyboardInput
+from .backend import IVec2, BackendEvent, KeyboardInput
 import curses
 
 
@@ -286,7 +286,9 @@ class TileMaps:
                 dim,
             )
 
-        def add_style(tilemap, size=mazetile_dims):
+        def add_style(
+            tilemap: list[ColoredLine], size: IVec2 = mazetile_dims
+        ) -> int:
             return backend.add_style(new_tilemap(tilemap, size))
 
         self.empty: list[int] = list(map(add_style, config.tilemap_empty))
@@ -301,7 +303,9 @@ class TileMaps:
 
 
 class TileCycle[T]:
-    def __init__(self, styles: list[T], cb: Callable[[T], None], i=0) -> None:
+    def __init__(
+        self, styles: list[T], cb: Callable[[T], None], i: int = 0
+    ) -> None:
         if len(styles) == 0:
             raise BackendException("No styles provided in tilecycle")
         self.__styles = styles
@@ -309,7 +313,7 @@ class TileCycle[T]:
         self.__i = i
         cb(styles[i])
 
-    def cycle(self, by: int = 1):
+    def cycle(self, by: int = 1) -> None:
         new = (self.__i + by) % len(self.__styles)
         if new != self.__i:
             self.__cb(self.__styles[new])
@@ -319,7 +323,7 @@ class TileCycle[T]:
         return self.__styles[self.__i]
 
 
-class TTYBackend(Backend[int]):
+class TTYBackend:
     def __init__(
         self, maze_dims: IVec2, wall_dim: IVec2, cell_dim: IVec2
     ) -> None:
@@ -389,7 +393,7 @@ class TTYBackend(Backend[int]):
 
         self.__style_bimap: BiMap[int, IVec2] = BiMap()
 
-    def __del__(self):
+    def __del__(self) -> None:
         curses.curs_set(1)
         curses.nocbreak()
         self.__screen.keypad(False)
@@ -411,7 +415,7 @@ class TTYBackend(Backend[int]):
 
         def inner(new: int) -> None:
             nonlocal curr
-            if curr == None:
+            if curr is None:
                 curr = new
             if curr == new:
                 return
@@ -448,12 +452,12 @@ class TTYBackend(Backend[int]):
         self.__layout.laid_out(IVec2(0, 0), IVec2(x, y))
         self.__scratch.overwrite(self.__screen)
 
-    def event(self, timeout_ms: int = -1) -> BackendEvent | None:
+    def event(self, timeout_ms: int = -1) -> BackendEvent | bool:
         self.__screen.timeout(timeout_ms)
         try:
             key = self.__screen.getkey()
         except curses.error:
-            return None
+            return False
         match key:
             case "KEY_RESIZE":
                 self.__resize = True
@@ -467,5 +471,4 @@ class TTYBackend(Backend[int]):
                 self.__pad.scroll(IVec2(-1, 0))
             case _:
                 return KeyboardInput(key)
-        self.present()
-        return None
+        return True
index 6ae3ed2e29e95e2514affb49cec6ad511054ce8e..0964b48dbdec282d7a02bde5cf0f036a4177cb07 100644 (file)
@@ -1,11 +1,10 @@
 __version__ = "0.0.0"
 __author__ = "luflores & agilliar"
 
-from .backend import Backend, IVec2
+from .backend import IVec2
 from .TTYdisplay import TTYBackend
 
 __all__ = [
-    "Backend",
     "IVec2",
     "TTYBackend",
 ]
index 6a8f9c0ba6dad4a5d47ea9e1b59275fe3e052923..77ecf167507099e0f11db8f703fa840b9f2ba307 100644 (file)
@@ -1,4 +1,3 @@
-from abc import ABC, abstractmethod
 from collections.abc import Callable
 from dataclasses import dataclass
 from typing import Type, cast
@@ -29,34 +28,40 @@ class IVec2[T = int]:
                 (
                     other
                     if isinstance(other, IVec2)
-                    else (other := type(self).splat(cast(T, other)))
+                    else (other := type(self).splat(other))
                 ).x,
             ),
             op(self.y, cast(IVec2[T], other).y),
         )
 
-    def innertype(self) -> Type:
+    def innertype(self) -> Type[T]:
         return type(self.x)
 
     def __mul__(self, other: "T | IVec2[T]") -> "IVec2[T]":
-        return self.with_op(self.innertype().__mul__)(self, other)
+        return self.with_op(getattr(self.innertype(), "__mul__"))(self, other)
 
     def __add__(self, other: "T | IVec2[T]") -> "IVec2[T]":
-        return self.with_op(self.innertype().__add__)(self, other)
+        return self.with_op(getattr(self.innertype(), "__add__"))(self, other)
 
     def __sub__(self, other: "T | IVec2[T]") -> "IVec2[T]":
-        return self.with_op(self.innertype().__sub__)(self, other)
+        return self.with_op(getattr(self.innertype(), "__sub__"))(self, other)
 
     def __floordiv__(self, other: "T| IVec2[T]") -> "IVec2[T]":
-        return self.with_op(self.innertype().__floordiv__)(self, other)
+        return self.with_op(getattr(self.innertype(), "__floordiv__"))(
+            self, other
+        )
 
     def __mod__(self, other: "T | IVec2[T]") -> "IVec2[T]":
-        return self.with_op(self.innertype().__mod__)(self, other)
+        return self.with_op(getattr(self.innertype(), "__mod__"))(self, other)
 
     def __eq__(self, value: object, /) -> bool:
         if not isinstance(value, IVec2):
             return False
-        return self.x == value.x and self.y == value.y
+        if self.x != value.x:
+            return False
+        if self.y != value.y:
+            return False
+        return True
 
     def __hash__(self) -> int:
         return hash((self.x, self.y))
@@ -78,30 +83,3 @@ class CloseRequested:
 
 
 type BackendEvent = KeyboardInput | CloseRequested
-
-
-class Backend[T](ABC):
-    """
-    ABC for the maze display.
-    defining how the maze should be drawn.
-    """
-
-    @abstractmethod
-    def dims(self) -> IVec2:
-        pass
-
-    @abstractmethod
-    def draw_tile(self, pos: IVec2) -> None:
-        pass
-
-    @abstractmethod
-    def set_style(self, style: T) -> None:
-        pass
-
-    @abstractmethod
-    def present(self) -> None:
-        pass
-
-    @abstractmethod
-    def event(self, timeout_ms: int = -1) -> BackendEvent | None:
-        pass
index 248280181f9fb39a10df2932e214cab41b83c351..44435a1dba6f5980c21c1e3ac447520f2af611f2 100644 (file)
@@ -165,13 +165,13 @@ class Box(ABC):
 
 class VBox[T](Box):
     def __init__(
-        self, layout: Layout, boxes: list[tuple[Box, T]] = []
+        self, layout: Layout[T], boxes: list[tuple[Box, T]] = []
     ) -> None:
         self.boxes: list[tuple[Box, T]] = boxes
-        self.layout: Layout = layout
+        self.layout: Layout[T] = layout
 
     @staticmethod
-    def noassoc(layout: Layout, boxes: list[Box]) -> "VBox[None]":
+    def noassoc(layout: Layout[None], boxes: list[Box]) -> "VBox[None]":
         return VBox(layout, [(box, None) for box in boxes])
 
     def dims(self) -> BVec2:
@@ -199,13 +199,13 @@ class VBox[T](Box):
 
 class HBox[T](Box):
     def __init__(
-        self, layout: Layout, boxes: list[tuple[Box, T]] = []
+        self, layout: Layout[T], boxes: list[tuple[Box, T]] = []
     ) -> None:
         self.boxes: list[tuple[Box, T]] = boxes
-        self.layout: Layout = layout
+        self.layout: Layout[T] = layout
 
     @staticmethod
-    def noassoc(layout: Layout, boxes: list[Box]) -> "HBox[None]":
+    def noassoc(layout: Layout[None], boxes: list[Box]) -> "HBox[None]":
         return HBox(layout, [(box, None) for box in boxes])
 
     def dims(self) -> BVec2:
@@ -265,11 +265,17 @@ class DBox(Box):
         self.__inner.laid_out(at, into)
 
 
-def vpad_box(min_pad: int = 0, cb=lambda _at, _into: None) -> FBox:
+def vpad_box(
+    min_pad: int = 0,
+    cb: Callable[[IVec2, IVec2], None] = lambda _at, _into: None,
+) -> FBox:
     return FBox(IVec2(BInt(0), BInt(min_pad, True)), cb)
 
 
-def hpad_box(min_pad: int = 0, cb=lambda _at, _into: None) -> FBox:
+def hpad_box(
+    min_pad: int = 0,
+    cb: Callable[[IVec2, IVec2], None] = lambda _at, _into: None,
+) -> FBox:
     return FBox(IVec2(BInt(min_pad, True), BInt(0)), cb)
 
 
index 038185198ce1641cfe2b69e933f681ebbefcf3d6..dc4de9dc8253136fa9291e959a313a37365023ab 100644 (file)
@@ -1,29 +1,33 @@
+from sys import stderr
 from typing import Callable
 from amazeing import Maze, WallCoord
 import random
 
+from amazeing.maze_class.maze_dirty_tracker import MazeDirtyTracker
+
 
 def maze_make_pacman(
     maze: Maze,
     walls_const: set[WallCoord],
+    dirty_tracker: MazeDirtyTracker,
     callback: Callable[[Maze], None] = lambda _: None,
     iterations: int = 10,
 ) -> None:
     for _ in range(0, iterations):
-        walls = [wall for wall in maze.walls_full() if wall not in walls_const]
-        random.shuffle(walls)
+        walls = dirty_tracker.clear()
         n = 0
         for wall in walls:
+            if not maze.get_wall(wall) or wall in walls_const:
+                continue
             leaf_neighbours = maze.wall_leaf_neighbours(wall)
             if not maze.wall_cuts_cycle(wall):
                 continue
             if len(leaf_neighbours) == 0:
-                maze.set_wall(wall)
+                maze.set_wall(wall, False)
             else:
-                maze.set_wall(wall)
-                maze.fill_wall(random.choice(leaf_neighbours))
+                maze.set_wall(wall, False)
+                maze.set_wall(random.choice(leaf_neighbours), True)
             n += 1
             callback(maze)
         if n == 0:
             break
-    maze._rebuild()
index 4f061e599c623f27bd7228d627e5ef3d55b2bf03..5ce17ffe967cd14c358ae2cc48e5c0e9564f8392 100644 (file)
@@ -26,7 +26,7 @@ class Tree[T]:
             self.root.with_parent,
             lambda parent: Leaf(parent, value),
         )
-        return cast(Leaf, self.root.rhs)
+        return cast(Leaf[T], self.root.rhs)
 
     def prepend(self, value: T) -> "Leaf[T]":
         if self.root is None:
@@ -40,7 +40,7 @@ class Tree[T]:
             lambda parent: Leaf(parent, value),
             self.root.with_parent,
         )
-        return cast(Leaf, self.root.lhs)
+        return cast(Leaf[T], self.root.lhs)
 
     def height(self) -> int:
         return 0 if self.root is None else self.root.height
@@ -358,7 +358,7 @@ class Branch[T](Node[T]):
         self.balance_one_propagate()
         return new_leaf
 
-    def balance_one(self):
+    def balance_one(self) -> None:
         if abs(self.get_balance()) <= 1:
             return
 
diff --git a/amazeing/utils/randset.py b/amazeing/utils/randset.py
new file mode 100644 (file)
index 0000000..2961af2
--- /dev/null
@@ -0,0 +1,73 @@
+from collections.abc import Iterable, MutableSequence, MutableSet
+from typing import cast, overload
+
+
+class Randset[T](MutableSequence[T], MutableSet[T]):
+    # __getitem__, __setitem__, __delitem__, __len__, and insert().
+    # __contains__, __iter__, __len__,
+    # add(), and discard().
+    def __init__(self) -> None:
+        self.__elems: list[T] = []
+        self.__idx_map: dict[T, int] = {}
+
+    def __repr__(self) -> str:
+        return str(self.__idx_map)
+
+    @overload
+    def __getitem__(self, pos: int) -> T: ...
+    @overload
+    def __getitem__(self, pos: slice) -> "Randset[T]": ...
+
+    def __getitem__(self, pos: int | slice) -> T | "Randset[T]":
+        if isinstance(pos, int):
+            return self.__elems[pos]
+        else:
+            res = Randset[T]()
+            res.__elems = self.__elems[pos]
+            res.__idx_map = {e: i for i, e in enumerate(res.__elems)}
+            return res
+
+    @overload
+    def __setitem__(self, pos: int, value: T) -> None: ...
+    @overload
+    def __setitem__(self, pos: slice, value: Iterable[T]) -> None: ...
+
+    def __setitem__(self, pos: int | slice, value: T | Iterable[T]) -> None:
+        if isinstance(pos, int):
+            del self.__idx_map[self.__elems[pos]]
+            self.__elems[pos] = cast(T, value)
+            self.__idx_map[cast(T, value)] = pos
+        else:
+            raise NotImplementedError("slice setitem in randset")
+
+    def __len__(self) -> int:
+        return len(self.__elems)
+
+    @overload
+    def __delitem__(self, pos: int) -> None: ...
+    @overload
+    def __delitem__(self, pos: slice) -> None: ...
+    def __delitem__(self, pos: int | slice) -> None:
+        if isinstance(pos, int):
+            self.discard(self.__elems[pos])
+        else:
+            elems = self.__elems[pos]
+            for e in elems:
+                self.discard(e)
+
+    def add(self, value: T) -> None:
+        if value in self.__idx_map:
+            return
+        self.__idx_map[value] = len(self.__elems)
+        self.__elems.append(value)
+
+    def discard(self, value: T) -> None:
+        if value not in self.__idx_map:
+            return
+        self.__idx_map[self.__elems[-1]] = self.__idx_map[value]
+        self.__elems[self.__idx_map[value]] = self.__elems[-1]
+        self.__elems.pop()
+        del self.__idx_map[value]
+
+    def insert(self, index: int, value: T) -> None:
+        raise NotImplementedError("slice setitem in randset")