r"""A set of generic classes and corresponding functions to iterate and index them.
The main goal of having this module is to allow for flexible modeling of collections with *most* python builtin
collections (e.g. `list`, `tuple`, `generator`) or even `Callable`\ s without awkward `isinstance` calls in
other modules of the library.
"""
from __future__ import annotations
from typing import Any, Callable, Iterable, Mapping, Sequence, Union
__all__ = [
"IterableMapping", # A mapping combining dicts, lists, tuples, and generators
"GenericMapping",
# An iterable mapping or a callable (e.g. function/functor)
# GenericMapping functions (apply to GMapping and IterableMapping)
"gmap", # An index on an IterableMapping or a call on a Callable
"gget",
# Like `gmap` but can return a default value
# IterableMapping functions (apply only to IterableMappings)
"ienumerate", # enumerates a mapping as key-value pairs
"iitems", # enumerates a mapping as key-value pairs
"ivalues", # enumerates all values in a mapping (no keys)
"ikeys", # enumerates all keys in a mapping (no values)
"iget", # Similar to `gget` but applies only to IterableMappings
]
# Generic classes that work with a variety of python mappings like collections or callables
IterableMapping = Union[Mapping, Sequence]
"""Something that can be iterated upon with Key-value pairs (e.g. list, dict, tuple)."""
GenericMapping = Union[Callable[[Any], Any], IterableMapping]
"""Something that can be indexed using [] or called using ()"""
[docs]
def gmap(group: GenericMapping, param: Any) -> Any:
"""Calls or indexes the group by the param
Args:
group: Either a Callable or a Mapping
param: The parameters to use for mapping
Examples:
>>> gmap([1, 23, 44], 1)
23
>>> gmap({"a": 3, "b": 5, "c": 4}, "c")
4
>>> gmap(lambda x: 3 * x, 20)
60
Returns:
"""
if hasattr(group, "__call__"):
return group(param) # type: ignore
return group[param] # type: ignore
[docs]
def iget(x: IterableMapping, _key: Any, default=None) -> Any:
"""Get an item from an IterableMapping
Args:
x: the generic mapping
_key: key (must be immutable)
default: default value if no value attached with the key is found
Examples:
Example with a list
>>> [iget([10, 20, 30], _) for _ in (0, 2, 1, -1, 4)]
[10, 30, 20, 30, None]
Example with a dictionary
>>> [iget({"a": 10, "b": 20, "c": 30}, _) for _ in ("a", "c", "b", -1, "d")]
[10, 30, 20, None, None]
Example with a tuple
>>> [iget((10, 20, 30), _) for _ in (0, 2, 1, -1, 4)]
[10, 30, 20, 30, None]
Example with a generator
>>> [iget(range(10, 40, 10), _) for _ in (0, 2, 1, -1, 4)]
[10, 30, 20, 30, None]
Returns:
"""
if isinstance(x, dict):
return x.get(_key, default)
try:
return x[_key] # type: ignore
except IndexError:
return default
[docs]
def gget(x: GenericMapping, _key: Any, default=None) -> Any:
"""Get an item from an IterableMapping
Args:
x: the generic mapping
_key: key (must be immutable)
default: default value if no value attached with the key is found
Examples:
Example with a list
>>> [gget([10, 20, 30], _) for _ in (0, 2, 1, -1, 4)]
[10, 30, 20, 30, None]
Example with a dictionary
>>> [gget({"a": 10, "b": 20, "c": 30}, _) for _ in ("a", "c", "b", -1, "d")]
[10, 30, 20, None, None]
Example with a tuple
>>> [gget((10, 20, 30), _) for _ in (0, 2, 1, -1, 4)]
[10, 30, 20, 30, None]
Example with a generator
>>> [gget(range(10, 40, 10), _) for _ in (0, 2, 1, -1, 4)]
[10, 30, 20, 30, None]
Returns:
"""
if hasattr(x, "apply"):
# noinspection PyBroadException
try:
return x(_key) # type: ignore
except Exception:
return default
else:
return iget(x, _key, default) # type: ignore
[docs]
def ienumerate(x: IterableMapping) -> Iterable[tuple[Any, Any]]:
"""Enumerates a GenericMapping.
Args:
x (IterableMapping): A generic mapping (see `GenericMapping`)
Examples:
Example with a list
>>> for k, cutoff_utility in ienumerate([10, 20, 30]):
... print(k, cutoff_utility, end="-")
0 10-1 20-2 30-
Example with a dictionary
>>> for k, cutoff_utility in ienumerate({"a": 10, "b": 20, "c": 30}):
... print(k, cutoff_utility, end="-")
a 10-b 20-c 30-
Example with a tuple
>>> for k, cutoff_utility in ienumerate((10, 20, 30)):
... print(k, cutoff_utility, end="-")
0 10-1 20-2 30-
Example with a generator
>>> for k, cutoff_utility in ienumerate(range(10, 40, 10)):
... print(k, cutoff_utility, end="-")
0 10-1 20-2 30-
Returns:
a generator/iterator with tuples of key-value pairs.
"""
if isinstance(x, dict):
return x.items()
else:
return enumerate(x)
iitems = ienumerate
[docs]
def ivalues(x: IterableMapping) -> Iterable[Any]:
"""Returns all keys of the iterable.
Args:
x (IterableMapping): A generic mapping (see `GenericMapping`)
Examples:
Example with a list
>>> for k in ivalues([10, 20, 30]):
... print(k, end="-")
10-20-30-
Example with a dictionary
>>> for k in ivalues({"a": 10, "b": 20, "c": 30}):
... print(k, end="-")
10-20-30-
Example with a tuple
>>> for k in ivalues((10, 20, 30)):
... print(k, end="-")
10-20-30-
Example with a generator
>>> for k in ivalues(range(10, 40, 10)):
... print(k, end="-")
10-20-30-
Returns:
a generator/iterator with tuples of key-value pairs.
"""
if isinstance(x, dict):
return list(x.values())
else:
return x
[docs]
def ikeys(x: IterableMapping) -> Iterable[Any]:
"""Returns all keys of the iterable.
Args:
x (IterableMapping): A generic mapping (see `GenericMapping`)
Examples:
Example with a list
>>> for k in ikeys([10, 20, 30]):
... print(k, end="-")
0-1-2-
Example with a dictionary
>>> for k in ikeys({"a": 10, "b": 20, "c": 30}):
... print(k, end="-")
a-b-c-
Example with a tuple
>>> for k in ikeys((10, 20, 30)):
... print(k, end="-")
0-1-2-
Example with a generator
>>> for k in ikeys(range(10, 40, 10)):
... print(k, end="-")
0-1-2-
Returns:
a generator/iterator with tuples of key-value pairs.
"""
if isinstance(x, dict):
return list(x.keys())
else:
return range(len(x)) # type: ignore