Source code for ulkb.util

# Copyright (C) 2023 IBM Corp.
# SPDX-License-Identifier: Apache-2.0

"""
Utility functions.

(Not intended for external use.)
"""
import logging
from collections import deque
from copy import copy, deepcopy
from functools import cmp_to_key, lru_cache, reduce, total_ordering, wraps
from hashlib import sha256
from itertools import chain, combinations, count, dropwhile, repeat, starmap
from pathlib import Path
from re import compile

from more_itertools import (always_reversible, first, flatten, islice_extended,
                            last, peekable, sliding_window, unique_everseen)

islice = islice_extended


# -- Combinators -----------------------------------------------------------

[docs] def identity(x): """The identity function, i.e., (𝜆 x ⇒ x).""" return x
[docs] def all_map(f, xs): """Tests whether `f` holds for all `x` in `xs`.""" return all(map(f, xs))
[docs] def any_map(f, xs): """Tests whether `f` holds for some `x` in `xs`.""" return any(map(f, xs))
[docs] def flip(f): """Returns the function (𝜆 x, y ⇒ f(y, x)).""" @wraps(f) def g(x, y): return f(y, x) return g
[docs] def foldl(f, x, xs): """Left-folds `xs` with using `f` and starting value `x`.""" return reduce(f, xs, x)
[docs] def foldl_args(f, x, *args): """Same as :func:`foldl` but applies to varargs.""" return foldl(f, x, args)
[docs] def foldl1(f, xs): """Left-folds `xs` using `f` and no starting value.""" return reduce(f, xs)
[docs] def foldl1_args(f, *args): """Same as :func:`foldl1` but applies to varargs.""" return foldl1(f, args)
[docs] def foldl_infix(f, g, arg1, arg2, *args, **kwargs): """Left-fold variant used to apply infix operators. For example: .. code-block:: python foldl_infix(f, g, [1,2,3,4,5], **kwargs) is equivalent to: .. code-block:: python f(g(g(g(1, 2), 3), 4), 5, **kwargs) """ if not args: return f(arg1, arg2, **kwargs) else: it = chain([arg1, arg2], islice(args, len(args) - 1)) return f(foldl1(g, it), args[-1], **kwargs)
[docs] def unfoldl(f, x): """Left-unfolds `x` using unpacking function `f`. Equivalent to: .. code-block:: python def g(x): if f(x) is None: # no more unpacking return [x] else: return g(x[0]) + [x[1]] Used to recover values which were left-folded. For example: .. code-block:: python >>> f = (lambda t: t if isinstance(t, tuple) else None) >>> it = unfold(f, (((1, 2), 3), 4)) >>> list(it) [1, 2, 3, 4] """ return always_reversible(_unfoldl(f, x))
def _unfoldl(f, x): while True: t = f(x) if t is None: yield x break x, r = t yield r
[docs] def foldr(f, x, xs): """Right-folds `xs` using `f` and starting value `x`.""" # # This seems to be the fastest version. # See <tests/profile_util.py>. # for y in reversed(xs): x = f(y, x) return x
[docs] def foldr_args(f, x, *args): """Same as :func:`foldr` but applies to varargs.""" return foldr(f, x, args)
[docs] def foldr1(f, xs): """Right-folds `xs` using `f` and no starting value.""" # # The efficiency remarks regarding foldr() also apply here. # See <tests/profile_util.py>. # it = reversed(xs) x = next(it) for y in it: x = f(y, x) return x
[docs] def foldr1_args(f, *args): """Same as :func:`foldr1` but applies to varargs.""" return foldr1(f, args)
[docs] def foldr_infix(f, g, arg1, arg2, *args, **kwargs): """Right-fold variant used to apply infix operators. For example: .. code-block:: python foldr_infix(f, g, [1,2,3,4,5], **kwargs) is equivalent to: .. code-block:: python f(1, g(2, g(3, g(4, 5))), **kwargs) """ if not args: return f(arg1, arg2, **kwargs) else: return f(arg1, foldr1_args(g, arg2, *args), **kwargs)
[docs] def unfoldr(f, x): """Right-unfolds `x` using unpacking function `f`. Equivalent to: .. code-block:: python def h(t): if f(t) is None: # no more unpacking return [t] else: return [t[0]] + h(t[1]) Used to recover values which were right-folded. For example: .. code-block:: python >>> f = (lambda t: t if isinstance(t, tuple) else None) >>> it = unfold(f, (1, (2, (3, 4)))) >>> list(it) [1, 2, 3, 4] """ while True: t = f(x) if t is None: yield x break l, x = t yield l
[docs] def map_args(f, *args): """Same as :func:`map` but applies to varargs.""" return map(f, args)
[docs] def match_first(f, xs, x=None): """Returns the first element of `xs` satisfying `f`; or `x`""" return foldr(lambda x, y: x if f(x) else y, x, xs)
[docs] def match_last(f, xs, x=None): # last element satisfying f; or x """Returns the last element of `xs` satisfying `f`; or `x`.""" return foldl(lambda x, y: y if f(y) else x, x, xs)
[docs] def sliding_pairs(it): """Returns a sliding window of width 2 over `it`.""" return sliding_window(it, 2)
[docs] def sliding_pairs_args(x1, x2, *xs): """Same as :func:`sliding_pairs` but applies to varargs.""" return sliding_pairs(chain([x1, x2], xs))
# -- Proxy -----------------------------------------------------------------
[docs] class Proxy: """Proxy object. Forwards any access to the object obtained using `get_proxy`. """ def __init__(self, get_proxy): get_proxy = get_proxy if callable(get_proxy) else lambda: get_proxy super().__setattr__('_get_proxy', get_proxy) def __call__(self, *args, **kwargs): return self._get_proxy()(*args, **kwargs) def __getattr__(self, k): return getattr(self._get_proxy(), k) def __setattr__(self, k, v): setattr(self._get_proxy(), k, v) def __delattr__(self, k): delattr(self._get_proxy(), k) def __str__(self): return str(self._get_proxy())
# -- Imports ---------------------------------------------------------------
[docs] def get_package_data_dir(modname): """Returns package-data directory of `modname`.""" import importlib return Path(importlib.util.find_spec(modname).origin).parent
# -- Misc ------------------------------------------------------------------
[docs] class Nil: """Class representing the absence of value.""" pass
[docs] def camel2snake( name, re1=compile(r'(.)([A-Z][a-z]+)'), re2=compile(r'([a-z0-9])([A-Z])')): """Converts `name` from camel-case to snake-case.""" return re2.sub(r'\1_\2', re1.sub(r'\1_\2', name)).lower()
[docs] def get_variant(name, re=compile(r'(.*?)(\d*)$')): """Returns the next numerical-suffixed variant of `name`.""" pre, suf = re.match(name).groups() if suf: return pre + str(int(suf) + 1) else: return pre + '0'
[docs] def get_variant_not_in(name, avoid): """Same as :func:`get_variant` but skip variants in `avoid`.""" var = name while var in avoid: var = get_variant(name) return var