| 60 | |
| 61 | @staticmethod |
| 62 | def recursive_slice(value: Any, slices: tuple[SliceLike, ...]) -> Any: |
| 63 | if not isinstance(value, Sliceable): |
| 64 | return value |
| 65 | |
| 66 | # Torch tensors and NumPy arrays don't need to be recursively sliced, since they |
| 67 | # already support tuples of slices. |
| 68 | if isinstance(value, Tensor) or isinstance(value, np.ndarray): |
| 69 | # If the number of slices exceeds the number of dimensions, drop the |
| 70 | # trailing slices. |
| 71 | slices = slices[: value.ndim] |
| 72 | |
| 73 | # Apply the remaining slices. |
| 74 | return value[slices] |
| 75 | |
| 76 | # Lists and tuples need to be recursively sliced. To check if we should recurse |
| 77 | # further, we peek at the value's first element. |
| 78 | can_recurse = isinstance(value[0], Sliceable) |
| 79 | |
| 80 | # Slice the value. |
| 81 | leading_slice, *remaining_slices = slices |
| 82 | value = value[leading_slice] |
| 83 | |
| 84 | # If the first element was sliceable, recursively apply slicing, making sure |
| 85 | # to keep the value's type the same (e.g., tuples remain tuples, and lists |
| 86 | # remain lists). |
| 87 | if len(remaining_slices) > 0 and can_recurse: |
| 88 | value = type(value)( |
| 89 | [Manipulable.recursive_slice(x, remaining_slices) for x in value] |
| 90 | ) |
| 91 | |
| 92 | return value |
| 93 | |
| 94 | @staticmethod |
| 95 | def cat(manipulables: list[T], dim: int) -> T: |