Adds a __repr__ to graph.
[pyutils.git] / src / pyutils / dict_utils.py
index 1071bf5d63e959db43ad5fc1123e58012fbb235f..8e6d6cb9b3c357398104a6b48af61064d4f77217 100644 (file)
@@ -5,26 +5,33 @@
 """This module contains helper functions for dealing with Python dictionaries."""
 
 from itertools import islice
-from typing import Any, Callable, Dict, Iterator, List, Tuple
+from typing import Any, Callable, Dict, Hashable, Iterator, List, Tuple
 
 from pyutils import dataclass_utils
+from pyutils.typez.typing import Comparable
+
+AnyDict = Dict[Hashable, Any]
+DictWithComparableKeys = Dict[Comparable, Any]
 
 
 def init_or_inc(
-    d: Dict[Any, Any],
-    key: Any,
+    d: AnyDict,
+    key: Hashable,
     *,
     init_value: Any = 1,
     inc_function: Callable[..., Any] = lambda x: x + 1,
 ) -> bool:
-    """
-    Initialize a dict value (if it doesn't exist) or increments it (using the
+    """Initialize a dict value (if it doesn't exist) or increments it (using the
     inc_function, which is customizable) if it already does exist.
 
+    See also :py:class:`defaultdict`
+    (https://docs.python.org/3/library/collections.html#collections.defaultdict)
+    for a more pythonic alternative.
+
     Args:
         d: the dict to increment or initialize a value in
         key: the key to increment or initialize
-        init_value: default initial value
+        init_value: default initial value (see also :meth:`dict.setdefault`)
         inc_function: Callable use to increment a value
 
     Returns:
@@ -42,6 +49,7 @@ def init_or_inc(
     False
     >>> d
     {'test': 2, 'ing': 1}
+
     """
     if key in d.keys():
         d[key] = inc_function(d[key])
@@ -50,7 +58,7 @@ def init_or_inc(
     return False
 
 
-def shard(d: Dict[Any, Any], size: int) -> Iterator[Dict[Any, Any]]:
+def shard(d: AnyDict, size: int) -> Iterator[AnyDict]:
     """
     Shards (i.e. splits) a dict into N subdicts which, together,
     contain all keys/values from the original unsharded dict.
@@ -117,10 +125,10 @@ def raise_on_duplicated_keys(key, new_value, old_value):
 
 
 def coalesce(
-    inputs: Iterator[Dict[Any, Any]],
+    inputs: Iterator[AnyDict],
     *,
     aggregation_function: Callable[[Any, Any, Any], Any] = coalesce_by_creating_list,
-) -> Dict[Any, Any]:
+) -> AnyDict:
     """Coalesce (i.e. combine) N input dicts into one output dict
     ontaining the union of all keys / values in every input dict.
     When keys collide, apply the aggregation_function which, by
@@ -163,7 +171,7 @@ def coalesce(
     ...
     Exception: Key b is duplicated in more than one input dict.
     """
-    out: Dict[Any, Any] = {}
+    out: AnyDict = {}
     for d in inputs:
         for key in d:
             if key in out:
@@ -174,7 +182,7 @@ def coalesce(
     return out
 
 
-def item_with_max_value(d: Dict[Any, Any]) -> Tuple[Any, Any]:
+def item_with_max_value(d: AnyDict) -> Tuple[Hashable, Any]:
     """
     Args:
         d: a dict with comparable values
@@ -195,7 +203,7 @@ def item_with_max_value(d: Dict[Any, Any]) -> Tuple[Any, Any]:
     return max(d.items(), key=lambda _: _[1])
 
 
-def item_with_min_value(d: Dict[Any, Any]) -> Tuple[Any, Any]:
+def item_with_min_value(d: AnyDict) -> Tuple[Hashable, Any]:
     """
     Args:
         d: a dict with comparable values
@@ -212,7 +220,7 @@ def item_with_min_value(d: Dict[Any, Any]) -> Tuple[Any, Any]:
     return min(d.items(), key=lambda _: _[1])
 
 
-def key_with_max_value(d: Dict[Any, Any]) -> Any:
+def key_with_max_value(d: AnyDict) -> Hashable:
     """
     Args:
         d: a dict with comparable keys
@@ -232,7 +240,7 @@ def key_with_max_value(d: Dict[Any, Any]) -> Any:
     return item_with_max_value(d)[0]
 
 
-def key_with_min_value(d: Dict[Any, Any]) -> Any:
+def key_with_min_value(d: AnyDict) -> Hashable:
     """
     Args:
         d: a dict with comparable keys
@@ -252,7 +260,7 @@ def key_with_min_value(d: Dict[Any, Any]) -> Any:
     return item_with_min_value(d)[0]
 
 
-def max_value(d: Dict[Any, Any]) -> Any:
+def max_value(d: AnyDict) -> Any:
     """
     Args:
         d: a dict with compatable values
@@ -267,7 +275,7 @@ def max_value(d: Dict[Any, Any]) -> Any:
     return item_with_max_value(d)[1]
 
 
-def min_value(d: Dict[Any, Any]) -> Any:
+def min_value(d: AnyDict) -> Any:
     """
     Args:
         d: a dict with comparable values
@@ -282,7 +290,7 @@ def min_value(d: Dict[Any, Any]) -> Any:
     return item_with_min_value(d)[1]
 
 
-def max_key(d: Dict[Any, Any]) -> Any:
+def max_key(d: DictWithComparableKeys) -> Comparable:
     """
     Args:
         d: a dict with comparable keys
@@ -300,7 +308,7 @@ def max_key(d: Dict[Any, Any]) -> Any:
     return max(d.keys())
 
 
-def min_key(d: Dict[Any, Any]) -> Any:
+def min_key(d: DictWithComparableKeys) -> Comparable:
     """
     Args:
         d: a dict with comparable keys
@@ -318,7 +326,7 @@ def min_key(d: Dict[Any, Any]) -> Any:
     return min(d.keys())
 
 
-def parallel_lists_to_dict(keys: List[Any], values: List[Any]) -> Dict[Any, Any]:
+def parallel_lists_to_dict(keys: List[Hashable], values: List[Any]) -> AnyDict:
     """Given two parallel lists (keys and values), create and return
     a dict.
 
@@ -329,17 +337,20 @@ def parallel_lists_to_dict(keys: List[Any], values: List[Any]) -> Dict[Any, Any]
     Returns:
         A dict composed of zipping the keys list and values list together.
 
+    Raises:
+        ValueError: if keys and values lists not the same length.
+
     >>> k = ['name', 'phone', 'address', 'zip']
     >>> v = ['scott', '555-1212', '123 main st.', '12345']
     >>> parallel_lists_to_dict(k, v)
     {'name': 'scott', 'phone': '555-1212', 'address': '123 main st.', 'zip': '12345'}
     """
     if len(keys) != len(values):
-        raise Exception("Parallel keys and values lists must have the same length")
+        raise ValueError("Parallel keys and values lists must have the same length")
     return dict(zip(keys, values))
 
 
-def dict_to_key_value_lists(d: Dict[Any, Any]) -> Tuple[List[Any], List[Any]]:
+def dict_to_key_value_lists(d: AnyDict) -> Tuple[List[Hashable], List[Any]]:
     """Given a dict, decompose it into a list of keys and values.
 
     Args: