Skip to content

Commit

Permalink
Add mapping manipulation functions (#548)
Browse files Browse the repository at this point in the history
  • Loading branch information
durandtibo authored Apr 2, 2024
1 parent 293417b commit 6489162
Show file tree
Hide file tree
Showing 4 changed files with 397 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/coola/nested/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
__all__ = [
"convert_to_dict_of_lists",
"convert_to_list_of_dicts",
"get_first_value",
"to_flat_dict",
]

from coola.nested.conversion import convert_to_dict_of_lists, convert_to_list_of_dicts
from coola.nested.mapping import get_first_value, to_flat_dict
5 changes: 1 addition & 4 deletions src/coola/nested/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

from __future__ import annotations

__all__ = [
"convert_to_dict_of_lists",
"convert_to_list_of_dicts",
]
__all__ = ["convert_to_dict_of_lists", "convert_to_list_of_dicts"]

from typing import TYPE_CHECKING

Expand Down
121 changes: 121 additions & 0 deletions src/coola/nested/mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
r"""Contain some utility functions to manipulate mappings."""

from __future__ import annotations

__all__ = ["get_first_value", "to_flat_dict"]

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from collections.abc import Mapping


def get_first_value(data: Mapping) -> Any:
r"""Get the first value of a mapping.
Args:
data: The input mapping.
Returns:
The first value in the mapping.
Raises:
ValueError: if the mapping is empty.
Example usage:
```pycon
>>> from coola.nested import get_first_value
>>> get_first_value({"key1": 1, "key2": 2})
1
```
"""
if not data:
msg = "First value cannot be returned because the mapping is empty"
raise ValueError(msg)
return data[next(iter(data))]


def to_flat_dict(
data: Any,
prefix: str | None = None,
separator: str = ".",
to_str: type[object] | tuple[type[object], ...] | None = None,
) -> dict[str, Any]:
r"""Return a flat representation of a nested dict with the dot
format.
Args:
data: The nested dict to flat.
prefix: The prefix to use to generate the name of the key.
``None`` means no prefix.
separator: The separator to concatenate keys of nested
collections.
to_str: The data types which will not be flattened out,
instead converted to string.
Returns:
The flatted dictionary.
Example usage:
```pycon
>>> from coola.nested import to_flat_dict
>>> data = {
... "str": "def",
... "module": {
... "component": {
... "float": 3.5,
... "int": 2,
... },
... },
... }
>>> to_flat_dict(data)
{'str': 'def', 'module.component.float': 3.5, 'module.component.int': 2}
>>> # Example with lists (also works with tuple)
>>> data = {
... "module": [[1, 2, 3], {"bool": True}],
... "str": "abc",
... }
>>> to_flat_dict(data)
{'module.0.0': 1, 'module.0.1': 2, 'module.0.2': 3, 'module.1.bool': True, 'str': 'abc'}
>>> # Example with lists with to_str=(list) (also works with tuple)
>>> data = {
... "module": [[1, 2, 3], {"bool": True}],
... "str": "abc",
... }
>>> to_flat_dict(data)
{'module.0.0': 1, 'module.0.1': 2, 'module.0.2': 3, 'module.1.bool': True, 'str': 'abc'}
```
"""
flat_dict = {}
to_str = to_str or ()
if isinstance(data, to_str):
flat_dict[prefix] = str(data)
elif isinstance(data, dict):
for key, value in data.items():
flat_dict.update(
to_flat_dict(
value,
prefix=f"{prefix}{separator}{key}" if prefix else key,
separator=separator,
to_str=to_str,
)
)
elif isinstance(data, (list, tuple)):
for i, value in enumerate(data):
flat_dict.update(
to_flat_dict(
value,
prefix=f"{prefix}{separator}{i}" if prefix else str(i),
separator=separator,
to_str=to_str,
)
)
else:
flat_dict[prefix] = data
return flat_dict
Loading

0 comments on commit 6489162

Please sign in to comment.