import collections
import dataclasses
import typing
[docs]@dataclasses.dataclass(slots=True, unsafe_hash=True)
class CyclicTuple:
"""
Cyclic tuple.
.. container:: example
Initializes from string:
>>> tuple_ = abjad.CyclicTuple('abcd')
>>> tuple_
CyclicTuple(items=('a', 'b', 'c', 'd'))
>>> for x in range(8):
... print(x, tuple_[x])
...
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
Cyclic tuples overload the item-getting method of built-in tuples.
Cyclic tuples return a value for any integer index.
Cyclic tuples otherwise behave exactly like built-in tuples.
"""
items: typing.Sequence = ()
[docs] def __post_init__(self):
self.items = tuple(self.items)
[docs] def __contains__(self, item) -> bool:
"""
Is true when cyclic tuple contains ``item``.
.. container:: example
>>> tuple_ = abjad.CyclicTuple('abcd')
>>> 'a' in tuple_
True
"""
return self.items.__contains__(item)
[docs] def __eq__(self, argument) -> bool:
"""
Compares ``items``.
"""
if isinstance(argument, tuple):
return self.items == argument
elif isinstance(argument, type(self)):
return self.items == argument.items
return False
[docs] def __getitem__(self, argument) -> typing.Any:
"""
Gets item or slice identified by ``argument``.
Gets slice open at right:
.. container:: example
>>> items = [0, 1, 2, 3, 4, 5]
>>> tuple_ = abjad.CyclicTuple(items=items)
>>> tuple_[2:]
(2, 3, 4, 5)
Gets slice closed at right:
.. container:: example
>>> items = [0, 1, 2, 3, 4, 5]
>>> tuple_ = abjad.CyclicTuple(items=items)
>>> tuple_[:15]
(0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2)
Raises index error when ``argument`` can not be found in cyclic tuple.
"""
if isinstance(argument, slice):
if (argument.stop is not None and argument.stop < 0) or (
argument.start is not None and argument.start < 0
):
return self.items.__getitem__(argument)
else:
return self._get_slice(argument.start, argument.stop)
if not self:
raise IndexError(f"cyclic tuple is empty: {self!r}.")
argument = argument % len(self)
return self.items.__getitem__(argument)
[docs] def __iter__(self) -> typing.Iterator:
"""
Iterates cyclic tuple.
Iterates items only once.
Does not iterate infinitely.
"""
return self.items.__iter__()
[docs] def __len__(self) -> int:
"""
Gets length of cyclic tuple.
"""
assert isinstance(self.items, tuple | CyclicTuple), repr(self.items)
return self.items.__len__()
def _get_slice(self, start_index, stop_index):
if stop_index is not None and 1000000 < stop_index:
stop_index = len(self)
result = []
if start_index is None:
start_index = 0
if stop_index is None:
indices = range(start_index, len(self))
else:
indices = range(start_index, stop_index)
result = [self[n] for n in indices]
return tuple(result)
collections.abc.Sequence.register(CyclicTuple)