Source code for energia.utils.dictionary
"""dictionary utils"""
from collections import defaultdict
from operator import is_
[docs]
def get_depth(d: dict) -> int:
"""
Finds the depth of a dictionary.
:param d: The dictionary to measure.
:type d: dict
:return: Depth of the dictionary.
:rtype: int
"""
if not isinstance(d, dict) or not d:
# If the input is not a d or is an empty d, return 0
return 0
# Recursively find the maximum depth of nested dictionaries
max_depth = max(get_depth(value) for value in d.values())
# Return one more than the maximum depth found
return 1 + max_depth
[docs]
def flatten(d: dict, key: tuple = ()) -> dict:
"""
Makes a flat dictionary from a nested dictionary.
:param d: The dictionary to flatten.
:type d: dict
:param key: Current key path. Defaults to ``()``.
:type key: tuple, optional
:return: Flattened dictionary with tuple keys.
:rtype: dict
"""
items = []
for key, val in d.items():
keyupd = key + (key,)
if isinstance(val, dict):
items.extend(flatten(val, keyupd).items())
else:
items.append((keyupd, val))
return dict(items)
[docs]
def tupler(d: dict, path: tuple = ()) -> list[tuple[str]]:
"""
Makes a list of tuples of keys in a nested dictionary.
:param d: The dictionary to traverse.
:type d: dict
:param path: Current path. Defaults to ``()``.
:type path: tuple, optional
:return: List of tuples of keys representing the paths to each value.
:rtype: list[tuple[str]]
"""
result = []
for k, v in d.items():
path_ = path + (k,)
result.append(path_)
if isinstance(v, dict):
result.extend(tupler(v, path_))
if isinstance(v, set):
for v_ in v:
result.append(path_ + (v_,))
return result
[docs]
def merge_trees(d1: dict, d2: dict) -> dict:
"""
Recursively merge two tree-like dicts (values always dicts).
:param d1: First dictionary.
:type d1: dict
:param d2: Second dictionary.
:type d2: dict
:return: Merged dictionary.
:rtype: dict
"""
result = dict(d1) # shallow copy of d1
for k, v in d2.items():
if k in result:
result[k] = merge_trees(
result[k],
v,
) # recurse since v must also be a dict
else:
result[k] = v
return result
[docs]
def dictify(d: defaultdict | dict) -> dict:
"""
Recursively convert defaultdict to dict.
:param d: The dictionary to convert.
:type d: defaultdict | dict
"""
if isinstance(d, defaultdict):
return {k: dictify(v) for k, v in d.items()}
return d
[docs]
class NotFoundError(Exception):
"""Raised when two periods are not linked in the period tree."""
[docs]
def compare(tree: dict, of):
"""How many periods make this period"""
n = 1
for i, j in tree.items():
if is_(i, of):
return n
n *= i.size
if not j:
raise NotFoundError
n *= compare(j, of)
return n
[docs]
def dict_signature(d):
"""Hashable signature of a dict for grouping."""
if not isinstance(d, dict):
return None
return tuple(
(k, dict_signature(v)) for k, v in sorted(d.items(), key=lambda x: str(x[0]))
)
[docs]
def merge_tree_levels(d):
"""
Convert nested dict to level-wise compact structure,
merging repeated keys at all levels.
"""
if not isinstance(d, dict) or not d:
return []
# Group children by their structure signature
sig_to_keys = defaultdict(list)
for k, v in d.items():
sig_to_keys[dict_signature(v)].append((k, v))
current_level = []
next_levels = []
for _, kv_list in sig_to_keys.items():
keys = [k for k, _ in kv_list]
children = [v for _, v in kv_list]
# flatten singletons
current_level.append(keys if len(keys) > 1 else keys[0])
# recurse only once per identical children
child_levels = merge_tree_levels(children[0])
if child_levels:
next_levels.append(child_levels)
# merge next_levels by depth
merged_next_levels = []
for group in next_levels:
for i, lvl in enumerate(group):
if len(merged_next_levels) <= i:
merged_next_levels.append([])
# flatten duplicates at same level
for item in lvl:
if item not in merged_next_levels[i]:
merged_next_levels[i].append(item)
return [current_level] + merged_next_levels