Skip to content

foapy.intervals_chain

intervals_chain(X, binding, chain_mode)

Compute the raw intervals chain from a sequence.

The intervals chain is the n-tuple of distances between consecutive occurrences of the same element, with the first occurrence's distance measured from the sequence boundary (or cyclically). The chain values alone are sufficient to determine both binding direction and chain mode via structural mathematical properties.

Parameters:

Name Type Description Default

X

array_like

Input sequence (strings, integers, or any comparable elements). Must be 1-dimensional. No pre-ordering via order() is required.

required

binding

int

binding.start (1) — intervals extracted left-to-right. binding.end (2) — intervals extracted right-to-left.

required

chain_mode

int

chain_mode.boundary (1) — sequence treated as finite; boundary intervals are distances from sequence edges to first/last occurrence. chain_mode.cycle (2) — sequence treated as circular; leading and trailing boundary distances are combined into a single cyclic interval.

required

Returns:

Type Description
ndarray

Plain 1-D ndarray of dtype intp containing the raw interval chain in original sequence order. All values are positive integers ≥ 1 and ≤ len(X).

Raises:

Type Description
Not1DArrayException

When X is not a 1-dimensional array.

ValueError

When binding is not binding.start or binding.end. When chain_mode is not chain_mode.boundary or chain_mode.cycle.

Examples:

1
2
3
4
5
import foapy

X = ['b', 'a', 'b', 'c', 'b']
chain = foapy.intervals_chain(X, foapy.binding.start, foapy.chain_mode.boundary)
print(chain)  # [1 2 2 4 2]
1
2
3
4
5
import foapy

X = ['b', 'a', 'b', 'c', 'b']
chain = foapy.intervals_chain(X, foapy.binding.start, foapy.chain_mode.cycle)
print(chain)  # [1 5 2 5 2]
1
2
3
4
5
import foapy

source = []
chain = foapy.intervals_chain(source, foapy.binding.start, foapy.chain_mode.boundary)  # noqa: E501
print(chain)  # []
Source code in .tox/docs-deploy/lib/python3.11/site-packages/foapy/core/_intervals_chain.py
def intervals_chain(X, binding: int, chain_mode: int) -> ndarray:
    """
    Compute the raw intervals chain from a sequence.

    The intervals chain is the n-tuple of distances between consecutive
    occurrences of the same element, with the first occurrence's distance
    measured from the sequence boundary (or cyclically). The chain values
    alone are sufficient to determine both binding direction and chain mode
    via structural mathematical properties.

    Parameters
    ----------
    X : array_like
        Input sequence (strings, integers, or any comparable elements).
        Must be 1-dimensional. No pre-ordering via ``order()`` is required.
    binding : int
        ``binding.start`` (1) — intervals extracted left-to-right.
        ``binding.end`` (2) — intervals extracted right-to-left.
    chain_mode : int
        ``chain_mode.boundary`` (1) — sequence treated as finite; boundary
        intervals are distances from sequence edges to first/last occurrence.
        ``chain_mode.cycle`` (2) — sequence treated as circular; leading and
        trailing boundary distances are combined into a single cyclic interval.

    Returns
    -------
    ndarray
        Plain 1-D ndarray of dtype ``intp`` containing the raw interval chain
        in original sequence order. All values are positive integers ≥ 1 and
        ≤ ``len(X)``.

    Raises
    ------
    Not1DArrayException
        When ``X`` is not a 1-dimensional array.
    ValueError
        When ``binding`` is not ``binding.start`` or ``binding.end``.
        When ``chain_mode`` is not ``chain_mode.boundary`` or ``chain_mode.cycle``.

    Examples
    --------

    ``` py linenums="1"
    import foapy

    X = ['b', 'a', 'b', 'c', 'b']
    chain = foapy.intervals_chain(X, foapy.binding.start, foapy.chain_mode.boundary)
    print(chain)  # [1 2 2 4 2]
    ```

    ``` py linenums="1"
    import foapy

    X = ['b', 'a', 'b', 'c', 'b']
    chain = foapy.intervals_chain(X, foapy.binding.start, foapy.chain_mode.cycle)
    print(chain)  # [1 5 2 5 2]
    ```

    ``` py linenums="1"
    import foapy

    source = []
    chain = foapy.intervals_chain(source, foapy.binding.start, foapy.chain_mode.boundary)  # noqa: E501
    print(chain)  # []
    ```
    """
    if binding not in {binding_cls.start, binding_cls.end}:
        raise ValueError(
            {"message": "Invalid binding value. Use binding.start or binding.end."}
        )

    if chain_mode not in {chain_mode_cls.boundary, chain_mode_cls.cycle}:
        raise ValueError(
            {
                "message": (
                    "Invalid chain_mode value. "
                    "Use chain_mode.boundary or chain_mode.cycle."
                )
            }
        )

    ar = np.asanyarray(X)

    if ar.ndim != 1:
        raise Not1DArrayException(
            {"message": (f"Incorrect array form. Expected d1 array, exists {ar.ndim}")}
        )

    if ar.shape[0] == 0:
        return np.array([], dtype=np.intp)

    if binding == binding_cls.end:
        ar = ar[::-1]

    perm = ar.argsort(kind="mergesort")

    n = ar.shape[0]
    mask = np.empty(n + 1, dtype=bool)
    mask[:1] = True
    mask[1:-1] = ar[perm[1:]] != ar[perm[:-1]]
    mask[-1:] = True

    first_mask = mask[:-1]
    last_mask = mask[1:]

    chain = np.empty(ar.shape, dtype=np.intp)
    chain[1:] = perm[1:] - perm[:-1]

    if chain_mode == chain_mode_cls.cycle:
        delta = n - perm[last_mask]
    else:
        delta = 1

    chain[first_mask] = perm[first_mask] + delta

    inverse_perm = np.empty(ar.shape, dtype=np.intp)
    inverse_perm[perm] = np.arange(n)

    result = chain[inverse_perm]

    if binding == binding_cls.end:
        result = result[::-1]

    return result