Source code for pydistsim.utils.helpers

from collections.abc import Callable, Iterable
from itertools import product
from typing import TYPE_CHECKING, Any, TypeVar

T = TypeVar("T")
U = TypeVar("U")


[docs] def pydistsim_equal_objects(obj1, obj2): """ Compare two objects and their attributes, but allow for non immutable attributes to be equal up to their class. :param obj1: The first object to compare. :type obj1: object :param obj2: The second object to compare. :type obj2: object """ classes = obj1.__class__ == obj2.__class__ attr_names = attr_values = True if isinstance(obj1, object) and isinstance(obj2, object): attr_names = set(obj1.__dict__.keys()) == set(obj2.__dict__.keys()) types = (str, tuple, int, int, bool, float, frozenset, bytes, complex) for key, value in list(obj1.__dict__.items()): other_value = getattr(obj2, key, None) if (isinstance(value, types) and value != other_value) or value.__class__ != other_value.__class__: attr_values = False break return classes and attr_names and attr_values
[docs] def with_typehint(baseclass: type[T]) -> type[T]: """ Useful function to make mixins with baseclass typehint without actually inheriting from it. :param baseclass: The base class to use as a type hint. :type baseclass: type[T] :return: The base class if TYPE_CHECKING is True, otherwise object. :rtype: type[T] """ if TYPE_CHECKING: return baseclass return object
[docs] def first(iterable: Iterable[T], default: U | None = None) -> T | U | None: """ Return the first item in an iterable, or a default value if the iterable is empty. :param iterable: The iterable to get the first item from. :type iterable: Iterable[T] :param default: The default value to return if the iterable is empty. :type default: U | None :return: The first item in the iterable, or the default value if the iterable is empty. :rtype: T | U | None """ iterator = iter(iterable) return next(iterator, default)
[docs] def measure_sortedness(sequence: Iterable[T], key: Callable[[T], Any] = None, reverse: bool = False) -> float: """ Measure the sortedness of a sequence. A higher value means the sequence is more sorted. `sortedness = 1 - inversions / (n * (n - 1) / 2)`, so if `sortedness == 1` corresponds to a sorted sequence. :param sequence: The sequence to measure the sortedness of. :type sequence: Iterable[T] :param key: The key function to use to extract a comparison key from each element. :type key: Callable[[T], Any] :param reverse: Whether to sort the sequence in reverse order. :type reverse: bool :return: The sortedness of the sequence (between 0 and 1) and the inverted pairs found. :rtype: tuple[float, list[tuple[int, int]]] """ if len(sequence) <= 1: return 1.0, [] if not key: key = lambda x: x if reverse: key = lambda x: -key(x) sortedness = 0 inverted_pairs = [] for pair in product(range(len(sequence)), repeat=2): i, j = pair if i < j and key(sequence[i]) <= key(sequence[j]): sortedness += 1 elif i != j: inverted_pairs.append(pair) return (sortedness / (len(sequence) * (len(sequence) - 1) / 2), inverted_pairs)
[docs] def len_is_one(iterable): """ Returns true if the iterable has one element without consuming it entirely. """ it = iter(iterable) try: next(it) return not next(it, False) except StopIteration: return False
[docs] def len_is_not_zero(iterable): """ Returns true if the iterable has at least one element without consuming it entirely. """ try: next(iter(iterable)) return True except StopIteration: return False