here.platform.utils.collection

Source code for here.platform.utils.collection

Copyright (C) 2020-2022 HERE Global B.V. and its affiliate(s).

All rights reserved.

This software and other materials contain proprietary information

controlled by HERE and are protected by applicable copyright legislation.

Any use and utilization of this software and other materials and

disclosure to any third parties is conditional upon having a separate

agreement with HERE for the access, use, utilization or disclosure of this

software. In the absence of such agreement, the use of the software is not

allowed.

"""
This module contains generic utility functions for python collections and iterables.
"""

import collections.abc
from itertools import zip_longest
from typing import Any, Collection, Iterable, Iterator, Tuple, TypeVar

T = TypeVar("T")

[docs]
def grouper(size: int, iterable: Iterable[T], fill_value=None) -> Iterator[T]:
"""
Create groups of size each from given iterable.

:param size: An int representing size of each group.
:param iterable: An iterable.
:param fill_value: Value to put for the last group.
:return: A Generator.
"""
args = [iter(iterable)] * size
return zip_longest(fillvalue=fill_value, *args)

[docs]
def iter_tuples(iterable):
"""
Return an iterator of key-value tuples from a variety of collections types.

This is used to convert mapping to their items, but also supporting collections
or iterators that are already in the form of key-value tuples.

Supported types:

  • mappings (dict), yielding key-value tuples
  • every collection that, when iterated, yields tuples
    in this case the tuples are not modified and may contain
    more than two elements

:param iterable: a collection containing keys and values
:return: an iterator over the keys and values of the collection, as tuples

Usage::

dict_data = {1: 10, 2: 20, 3: 30, 4: 40}
list(iter_tuples(dict_data))
[(1, 10), (2, 20), (3, 30), (4, 40)]

list_data = [(1, 10), (2, 20), (3, 30), (4, 40)]
list(iter_tuples(list_data))
[(1, 10), (2, 20), (3, 30), (4, 40)]

map_data = map(lambda x: (x, x * 10), range(1, 5))
list(iter_tuples(map_data))
[(1, 10), (2, 20), (3, 30), (4, 40)]
"""
return (
iter(iterable.items()) if isinstance(iterable, collections.abc.Mapping) else iter(iterable)
)

[docs]
def flatten_iterator(
iterator: Iterator[Tuple[Any, Any]],
max_level: int = -1,
prefix: str = "",
sep: str = ".",
exclude_keys: Collection = [],
) -> Iterator[Tuple[str, Any]]:
"""
Scan a tuple iterator and flatten keys and values, in case values contain nested dictionaries.

Keys are converted to string and composed in hierarchical paths, separated by sep.
An initial prefix, if present, is prepended and separated by sep to the keys
of the passed and nested dictionaries. Order of elements is preserved.

Among other uses, the output of the function can be passed directly to
the constructor of dict to compose a new, flat dictionary without overhead.

:param iterator: the iterator to scan recursively
:param max_level: the maximum level of the recursion. 0 performs no flattening,
1 flattens only the first nested dictionaries, 2 only the first and second nested
dictionaries, and so on. A negative number represents no limit.
:param prefix: what to prepend, separate using sep, to all the returned keys
:param sep: the separator to use when concatenating keys
:param exclude_keys: full path of keys that should not be flattened
:yield: tuples of concatenated keys and unchanged values

Usage::

data = {1: 10, 2: 20, 3: { "a": "AAA", "b": "BBB" }, 4: 40}
list(flatten_iterator(iter(data.items())))
[('1', 10), ('2', 20), ('3.a', 'AAA'), ('3.b', 'BBB'), ('4', 40)]

dict(flatten_iterator(iter(data.items())))
{'1': 10, '2': 20, '3.a': 'AAA', '3.b': 'BBB', '4': 40}

dict(flatten_iterator(iter(data.items()), prefix="x"))
{'x.1': 10, 'x.2': 20, 'x.3.a': 'AAA', 'x.3.b': 'BBB', 'x.4': 40}
"""
for k, v in iterator:
prefix_k = str(k) if not prefix else prefix + sep + str(k)
if isinstance(v, dict) and max_level != 0 and prefix_k not in exclude_keys:
yield from flatten_iterator(
iter(v.items()),
max_level=max_level - 1,
prefix=prefix_k,
sep=sep,
exclude_keys=exclude_keys,
)
else:
yield prefix_k, v