Source code for stupidb.functions.ranking

"""Navigation and simple window function interface and implementation."""

from __future__ import annotations

import abc
from typing import Any, Sequence, Union

from ...protocols import Comparable
from ...typehints import T
from .core import RankingAggregate


[docs]class RowNumber(RankingAggregate[int]): """Row number analytic function.""" __slots__ = ("row_number",) def __init__( self, order_by_values: Sequence[tuple[Comparable | None, ...]] ) -> None: super().__init__(order_by_values) self.row_number = 0
[docs] def execute(self, begin: int, end: int) -> int: """Compute an abstract row rank value for rows between `begin` and `end`.""" row_number = self.row_number self.row_number += 1 return row_number
[docs]class Sentinel: """A class that is not equal to anything except instances of itself. This class is used as the starting value for :class:`stupidb.ranking.Rank` and :class:`stupidb.ranking.DenseRank` because their algorithms compare the previous ``ORDER BY`` value in the sequence to determine whether to increase the rank. """ __slots__ = () def __repr__(self) -> str: return f"{self.__class__.__name__}()" def __eq__(self, other: Any) -> bool: return isinstance(other, type(self))
Either = Union[Sentinel, T]
[docs]class AbstractRank(RowNumber): """A class represnting a numerical ordering of rows.""" __slots__ = ("previous_value",) def __init__( self, order_by_values: Sequence[tuple[Comparable | None, ...]] ) -> None: super().__init__(order_by_values) self.previous_value: Either | None = Sentinel()
[docs] @abc.abstractmethod def rank(self, current_order_by_value: Comparable, current_row_number: int) -> int: """Compute the rank of the current row."""
[docs] def execute(self, begin: int, end: int) -> int: """Compute an abstract row rank value for rows between `begin` and `end`.""" current_row_number = super().execute(begin, end) current_order_by_value = self.order_by_values[current_row_number] rank = self.rank(current_order_by_value, current_row_number) assert ( rank >= 0 ), f"rank should be greater than or equal to 0, got rank == {rank:d}" self.previous_value = current_order_by_value assert not isinstance( self.previous_value, Sentinel ), "expected non-Sentinel order by value, got Sentinel" return rank
[docs]class Rank(AbstractRank): """Non-dense ranking computation.""" __slots__ = ("previous_rank",) def __init__( self, order_by_values: Sequence[tuple[Comparable | None, ...]] ) -> None: super().__init__(order_by_values) self.previous_rank = -1
[docs] def rank(self, current_order_by_value: Comparable, current_row_number: int) -> int: """Rank the current row according to `current_order_by_value`.""" if current_order_by_value != self.previous_value: rank = current_row_number else: rank = self.previous_rank self.previous_rank = rank return rank
[docs]class DenseRank(AbstractRank): """Dense ranking computation.""" __slots__ = ("current_rank",) def __init__( self, order_by_values: Sequence[tuple[Comparable | None, ...]] ) -> None: super().__init__(order_by_values) self.current_rank = -1
[docs] def rank(self, current_order_by_value: Comparable, current_row_number: int) -> int: """Compute the current rank, densely.""" self.current_rank += current_order_by_value != self.previous_value return self.current_rank