Source code for dictutils.pivot

from __future__ import annotations

from collections.abc import Mapping

# Minimum array length for nesting (key + value)
MIN_ARRAY_LENGTH = 2


def flatten_dict(d: Mapping) -> list[list]:
    """
    Flatten a dictionary into an array of arrays.

    Example:
        {a: {x: 2, y: 3}, b: {x: 4, y: 5}}
        becomes
        [[a, x, 2], [a, y, 3], [b, x, 4], [b, y, 5]]

    Used as a component of the pivot function.
    """
    if not isinstance(d, Mapping):
        return [[d]]

    arr = []
    for k, v in d.items():
        for el in flatten_dict(v):
            arr.append([k] + el)

    return arr


def rearrange(in_arrs: list[list], order: list[int]) -> list[list]:
    """
    Rearrange elements in a given list of arrays by order indices.
    The last element (value) always remains in place.

    Args:
        in_arrs: List of arrays to rearrange
        order: List of indices specifying new order of keys

    Returns:
        Rearranged arrays

    Raises:
        IndexError: If any index in order is out of range for the row

    Example:
        rearrange([[a, x, 2], [a, y, 3]], [1, 0])
        becomes [[x, a, 2], [y, a, 3]]
    """
    out = []
    for arr in in_arrs:
        # Validate indices against row width (excluding the last element which is the value)
        max_key_index = len(arr) - 2  # -1 for last element, -1 for 0-based indexing
        for i in order:
            if i < 0 or i > max_key_index:
                raise IndexError(
                    f"order index {i} out of range for row with {max_key_index + 1} keys"
                )

        out.append([arr[i] for i in order] + [arr[-1]])

    return out


def nest(arrays: list, root: dict | None = None) -> dict:
    """
    Unflatten a dictionary from arrays. Similar to qsdict but simpler.

    Args:
        arrays: List of arrays where each array represents a path + value
        root: Optional existing dict to merge into

    Returns:
        Nested dictionary
    """
    if not arrays:
        return {}

    d = root or {}
    for arr in arrays:
        if len(arr) >= MIN_ARRAY_LENGTH:
            head, *tail = arr
            if len(tail) == 1:
                d[head] = tail[0]
            else:
                d[head] = nest([tail], d.get(head, {}))
    return d


[docs] def pivot(d: dict, order: list[int]) -> dict: """ Pivot a nested dictionary by rearranging the key levels according to order. Args: d: Input nested dictionary order: List of indices specifying the new order of key levels Returns: Pivoted dictionary with keys rearranged Example: d = {"A": {"X": 1, "Y": 2}, "B": {"X": 3, "Y": 4}} pivot(d, [1, 0]) -> {"X": {"A": 1, "B": 3}, "Y": {"A": 2, "B": 4}} """ flattened = flatten_dict(d) rearranged = rearrange(flattened, order) nested = nest(rearranged) return nested