Source code for knit_graphs.loop_sequence

"""Module containing the Loop Sequence class."""

from __future__ import annotations

from collections.abc import Iterable, Iterator, Sequence
from typing import Generic, TypeVar, overload

from knit_graphs.directed_loop_graph import Float_Edge
from knit_graphs.Loop import Loop

LoopT = TypeVar("LoopT", bound=Loop)


[docs] class Loop_Sequence(Sequence[LoopT], Generic[LoopT]): """Generic class for a series of loops as in a course or a course-face"""
[docs] def __init__(self) -> None: self._loops_in_order: list[LoopT] = [] self._loop_set: set[LoopT] = set()
@property def loops_in_order(self) -> list[LoopT]: """ Returns: list[Loop]: The list of loops in this course. """ return self._loops_in_order @property def loop_ids(self) -> list[int]: """ Returns: list[int]: The loop ids in the course in the order they occur. """ return [l.loop_id for l in self.loops_in_order] @property def has_increase(self) -> bool: """Check if this course contains any yarn overs that start new wales. Returns: bool: True if the course has at least one yarn over (loop with no parent loops) to start new wales. """ return any(not loop.has_parent_loops for loop in self) @property def has_decrease(self) -> bool: """Check if this course contains any decrease stitches that merge wales. Returns: bool: True if the course has at least one decrease stitch (loop with multiple parent loops) merging two or more wales. """ return any(len(loop.parent_loops) > 1 for loop in self) @property def floats(self) -> Iterator[Float_Edge[LoopT]]: """ Returns: set[Float_Edge[LoopT]]: The set of yarn-floats between loops in this sequence. """ for loop in self: f = self.following_float_in_sequence(loop) if f is not None: yield f @property def loops_in_front_of_floats(self) -> set[LoopT]: """ Returns: set[LoopT]: The set of loops in front of at least one float in this sequence. """ loops = set() for f in self.floats: loops.update(f.front_loops) return loops @property def loops_behind_floats(self) -> set[LoopT]: """ Returns: set[LoopT]: The set of loops behind of at least one float in this sequence. """ loops = set() for f in self.floats: loops.update(f.back_loops) return loops
[docs] def loop_is_in_front(self, loop: LoopT) -> bool: """ Args: loop (LoopT): The loop to check for. Returns: bool: True if the loop is in front of any float in this sequence. """ return any(loop in f.front_loops for f in self.floats)
[docs] def in_front_of_loops(self, other: Sequence[LoopT]) -> bool: """ Args: other (Sequence[LoopT]): The series of loop to compare to. Returns: bool: True if all loops in the other series cross in front of a float in this series. """ return all(l in self.loops_in_front_of_floats for l in other)
[docs] def loop_is_behind(self, loop: LoopT) -> bool: """ Args: loop (LoopT): The loop to check for. Returns: bool: True if the loop is behind any float in this sequence. """ return any(loop in f.back_loops for f in self.floats)
[docs] def behind_loops(self, other: Sequence[LoopT]) -> bool: """ Args: other (Sequence[LoopT]): The series of loop to compare to. Returns: bool: True if all loops in the other series cross behind a float in this series. """ return all(l in self.loops_behind_floats for l in other)
[docs] def loop_crosses_floats(self, loop: LoopT) -> bool: """ Args: loop [LoopT]: The loop to check for crossing floats. Returns: bool: True if the given loop crosses at least one float in this sequence. """ return not loop.cross_floats.isdisjoint(self._loop_set)
[docs] def tangled_with_loops(self, other: Sequence[LoopT]) -> bool: """ Args: other (Sequence[LoopT]): The series of loop to compare to. Returns: bool: True if some loops in the given sequence fall in-front and others behind this sequence. """ return any(self.loop_is_in_front(l) for l in other) and any(self.loop_is_behind(l) for l in other)
[docs] def next_loop_in_sequence(self, loop: int | LoopT) -> LoopT | None: """ Args: loop (int | LoopT): The index of the loop to find the following loop in the series or the loop to find the next loop in the series. Returns: LoopT | None: The loop that follows the given loop in this series or None if the given loop is the last loop in the series. Raises: ValueError: If the given loop is not a loop in the series. """ if isinstance(loop, int): index = loop elif loop not in self: raise ValueError(f"Loop {loop} does not exist in {self}") else: index = self._loops_in_order.index(loop) if index + 1 < len(self): return self.loops_in_order[index + 1] else: return None
[docs] def following_float_in_sequence(self, loop: int | LoopT) -> Float_Edge[LoopT] | None: """ Args: loop (int | LoopT): The index of the loop to find the following float of or the loop to find the next loop in the series. Returns: Float_Edge[LoopT] | None: The float-edge initiated at the given loop and going to a loop in the sequence or None if there is no following loop along the yarn in this sequence. """ if isinstance(loop, int): loop = self[loop] if loop.next_loop_on_yarn is None or loop.next_loop_on_yarn not in self: return None return loop.started_float
def _add_loop_to_sequence(self, loop: LoopT, index: int | None = None) -> None: self._loop_set.add(loop) if index is None: self.loops_in_order.append(loop) else: self.loops_in_order.insert(index, loop)
[docs] def __contains__(self, loop: object) -> bool: """O(1) membership test backed by the internal loop set. Args: loop (object): The object to test for membership. Returns: bool: True if loop is in this sequence, False otherwise. """ return loop in self._loop_set
@overload def __getitem__(self, index: int) -> LoopT: ... @overload def __getitem__(self, index: slice) -> list[LoopT]: ...
[docs] def __getitem__(self, index: int | slice) -> LoopT | list[LoopT]: """ Args: index (int | slice): The index or slice to retrieve. Returns: Loop | list[Loop]: The loop at the specified index, or list of loops for a slice. """ return self.loops_in_order[index]
[docs] def __iter__(self) -> Iterator[LoopT]: """ Returns: Iterator[Loop]: An iterator over the loops in the series in their natural order. """ return iter(self.loops_in_order)
[docs] def __len__(self) -> int: """O(1) length backed by the internal loop set. Returns: int: The total number of loops in the series. """ return len(self._loop_set)
[docs] def __reversed__(self) -> Iterator[LoopT]: """Iterate over loops in reverse sequence order. Enables ``reversed(seq)`` and communicates that the sequence has a meaningful direction — relevant for knitting since course direction (e.g. left-to-right vs. right-to-left passes) matters structurally. Returns: Iterator[LoopT]: An iterator over the loops in reverse order. """ return reversed(self._loops_in_order)
[docs] def count(self, loop: object) -> int: """ Args: loop (object): The object whose occurrences to count. Returns: int: The number of times loop appears in this sequence. 1 if loop is in this sequence, 0 otherwise. """ return int(loop in self._loop_set)
[docs] def isdisjoint(self, other: Iterable[LoopT]) -> bool: """ Args: other (Iterable[LoopT]): The sequence to test against. Returns: bool: True if the two sequences have no loops in common. """ return self._loop_set.isdisjoint(other)