Source code for hbutils.algorithm.linear

from bisect import bisect_right
from typing import Sequence, Union, Tuple, Callable

__all__ = [
    'linear_map',
]


[docs]def linear_map(points: Union[Sequence[float], Sequence[Tuple[float, float]]]) -> Callable[[float], float]: """ Overview: Multiple-staged linear gradient calculation based on float number. :param points: Points for this linear mapping. If the sequence consists of float numbers, it will be seen as the \ simple linear mapping. If the elements are binary tuples (contains 2 float numbers), it means the x-range is \ assigned. :return: A callable function for linear mapping. Examples:: - Simple Linear Mapping >>> from hbutils.algorithm import linear_map >>> >>> f = linear_map((0, 1, 0.5)) >>> f(0) 0.0 >>> f(0.25) 0.5 >>> f(1 / 3) 0.6666666666666666 >>> f(0.5) 1.0 >>> f(2 / 3) 0.8333333333333334 >>> f(0.75) 0.75 >>> f(1) 0.5 - Complex Linear Mapping (x values are customized) >>> f = linear_map(((-0.2, 0), (0.7, 1), (1.1, 0.5))) >>> f(-0.2) 0.0 >>> f(0) 0.22222222222222227 >>> f(0.25) 0.5000000000000001 >>> f(1 / 3) 0.5925925925925927 >>> f(0.5) 0.7777777777777778 >>> f(2 / 3) 0.9629629629629631 >>> f(0.7) 1.0 >>> f(0.75) 0.9375 >>> f(0.8) 0.875 >>> f(1) 0.625 >>> f(1.1) 0.5 """ assert points, f'Points should be non-empty, but {points!r} found.' try: xys = [(x, y) for x, y in points] except TypeError: pts = list(points) n = len(pts) xys = [(i / (n - 1), y) for i, y in enumerate(points)] for i, (x1_, x2_) in enumerate(zip(xys[:-1], xys[1:])): assert x1_ < x2_, f'The former x value should be no more than the latter one, ' \ f'but {x1_} (at {i}) >= {x2_} (at {i + 1}) found.' def _linear(x: float) -> float: if xys[0][0] <= x <= xys[-1][0]: _index = bisect_right([x_ for x_, _ in xys[:-1]], x) - 1 x1, y1 = xys[_index] x2, y2 = xys[_index + 1] r = (x - x1) / (x2 - x1) return (1 - r) * y1 + r * y2 else: raise ValueError(f'Invalid x value, [{xys[0][0]!r}, {xys[-1][0]!r}] expected but {x!r} found.') return _linear