Source code for hbutils.collection.dimension

"""
Dimension operations for multi-dimensional array structures.

This module provides utilities for working with nested list/tuple structures
that represent multi-dimensional arrays (cubes). It includes functions for:

* :func:`cube_shape` - Determine the shape of a cube array
* :func:`dimension_switch` - Switch/transpose dimensions in a cube array
* :func:`swap_2d` - Convenience helper for transposing 2D arrays

These operations are useful for manipulating nested data structures without
requiring external dependencies like NumPy.

Example::

    >>> from hbutils.collection.dimension import cube_shape, dimension_switch, swap_2d
    >>> cube_shape([[1, 2, 3], [4, 5, 6]])
    (2, 3)
    >>> dimension_switch([[1, 2, 3], [4, 5, 6]], (1, 0))
    [[1, 4], [2, 5], [3, 6]]
    >>> swap_2d([[1, 2, 3], [4, 5, 6]])
    [[1, 4], [2, 5], [3, 6]]

.. note::
   These utilities operate on standard Python list/tuple structures and will
   return lists when building new arrays.

"""
from typing import Any, List, Sequence, Tuple

__all__ = [
    'cube_shape', 'dimension_switch', 'swap_2d',
]


def _s_cube_shape(c: Any, path: Tuple[int, ...]) -> Tuple[int, ...]:
    """
    Internal recursive function to calculate the shape of a cube array.

    :param c: The current level of the nested structure to analyze.
    :type c: list or tuple or any
    :param path: The current path in the nested structure for error reporting.
    :type path: tuple

    :return: The shape tuple of the current level and all nested levels.
    :rtype: tuple
    :raises ValueError: If the structure is not a valid cube (inconsistent shapes at the same level).
    """
    if isinstance(c, (list, tuple)):
        n = len(c)
        if n == 0:
            return (0,)
        else:
            first = c[0]
            first_shape = _s_cube_shape(first, (*path, 0))
            for i, item in enumerate(c[1:], start=1):
                item_shape = _s_cube_shape(item, (*path, i))
                if first_shape != item_shape:
                    raise ValueError(f'Mismatching between {repr((*path, 0))} and {repr((*path, 1))}, '
                                     f'this is not a cube!', ((*path, 0), first_shape), ((*path, 1), item_shape))
            return (n, *first_shape)
    else:
        return ()


[docs] def cube_shape(c: Any) -> Tuple[int, ...]: """ Get the shape of a cube array (nested list/tuple structure). This function analyzes a nested list or tuple structure and returns its shape as a tuple of dimensions. The structure must be a valid "cube" where all elements at the same nesting level have consistent shapes. If the input is not a list or tuple, an empty shape tuple is returned. :param c: The cube array to analyze (nested list/tuple structure). :type c: list or tuple or any :return: Shape of the cube as a tuple of dimension sizes. :rtype: tuple :raises ValueError: If the structure is not a valid cube (inconsistent shapes). Examples:: >>> import numpy >>> from hbutils.collection import cube_shape >>> a = numpy.random.randint(-5, 15, (3, 5, 7, 9)).tolist() >>> cube_shape(a) (3, 5, 7, 9) >>> # 2D example >>> cube_shape([[1, 2, 3], [4, 5, 6]]) (2, 3) >>> # Invalid cube (mismatched dimensions) >>> cube_shape([[1, 2], [3, 4, 5]]) Traceback (most recent call last): ... ValueError: ('Mismatching between (0,) and (1,), this is not a cube!', ((0,), (2,)), ((1,), (3,))) """ return _s_cube_shape(c, ())
[docs] def dimension_switch(c: Sequence[Any], dimensions: Sequence[int]) -> List[Any]: """ Switch (transpose) the dimensions of a cube array according to a specified order. This function rearranges the dimensions of a multi-dimensional nested structure according to the provided dimension order. It is similar to NumPy's transpose operation but works with nested Python lists/tuples. :param c: Multi-dimensional array (nested list/tuple structure). :type c: list or tuple :param dimensions: New order of dimensions as a tuple/list of indices (0 to N-1). Each index should appear exactly once. :type dimensions: tuple or list :return: Array with switched dimensions. :rtype: list :raises ValueError: If dimensions parameter is invalid (missing indices, duplicates, or out of range). Examples:: >>> import numpy >>> from hbutils.collection import cube_shape, dimension_switch >>> a = numpy.random.randint(-5, 15, (3, 5, 7, 9)).tolist() >>> cube_shape(a) (3, 5, 7, 9) >>> b = dimension_switch(a, (3, 0, 2, 1)) >>> cube_shape(b) (9, 3, 7, 5) >>> # 2D transpose example >>> arr = [[1, 2, 3], [4, 5, 6]] >>> dimension_switch(arr, (1, 0)) [[1, 4], [2, 5], [3, 6]] """ shape = cube_shape(c) n = len(shape) if sorted(set(dimensions)) != list(range(len(shape))): raise ValueError(f'Invalid dimensions - {repr(dimensions)}.', dimensions) def _recursive(p: int, path: Tuple[int, ...]) -> Any: """ Recursive helper function to build the switched array. :param p: Current depth level in the recursion. :type p: int :param path: Current path of indices being built. :type path: tuple :return: Element or nested list at the current recursion level. :rtype: any or list """ if p >= n: actual_path = [None] * n for i, di in enumerate(dimensions): actual_path[di] = path[i] oc = c for ip in actual_path: oc = oc[ip] return oc else: return [_recursive(p + 1, (*path, i)) for i in range(shape[dimensions[p]])] return _recursive(0, ())
[docs] def swap_2d(c: Sequence[Sequence[Any]]) -> List[Any]: """ Swap the dimensions of a 2D array (transpose rows and columns). This is a convenience function that transposes a 2D nested list/tuple structure by swapping its two dimensions. It is equivalent to calling :func:`dimension_switch` with ``(1, 0)``. :param c: 2D array (nested list/tuple structure). :type c: list or tuple :return: Transposed 2D array. :rtype: list Examples:: >>> from hbutils.collection import swap_2d >>> swap_2d([ ... [9, 6, 4, 11, 5, -2, 1], ... [0, 0, 11, 5, 8, -4, 9], ... [0, 2, 13, 7, 0, 13, 0] ... ]) [[9, 0, 0], [6, 0, 2], [4, 11, 13], [11, 5, 7], [5, 8, 0], [-2, -4, 13], [1, 9, 0]] >>> # Simple example >>> swap_2d([[1, 2, 3], [4, 5, 6]]) [[1, 4], [2, 5], [3, 6]] """ return dimension_switch(c, (1, 0))