diff options
Diffstat (limited to 'utils/programming_utils.py')
-rw-r--r-- | utils/programming_utils.py | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/utils/programming_utils.py b/utils/programming_utils.py new file mode 100644 index 0000000..a0a8fde --- /dev/null +++ b/utils/programming_utils.py @@ -0,0 +1,105 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T Intellectual Property +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +import collections +import itertools + + +class DotDict(dict): + """A dot-dict mixin to be able to access a dictionary via dot notation + source: https://stackoverflow.com/questions/2352181/how-to-use-a-dot-to-access-members-of-dictionary + """ + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + +class MetaSingleton(type): + """Singleton class (2nd Chapter) from Learning Python Design Patterns - 2nd ed. + Chetan Giridhar, Packt Publ. 2016""" + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +def namedtuple_with_defaults(typename, field_names, default_values=()): + """A namedtuple with default values -- Stack overflow recipe from Mark Lodato + http://stackoverflow.com/questions/11351032/named-tuple-and-optional-keyword-arguments + :param typename: Name for the class (same as for namedtuple) + :param field_names: Field names (same as for namedtuple) + :param default_values: Can be specified as a dictionary or as a list + :return: New namedtuple object + """ + T = collections.namedtuple(typename, field_names) + T.__new__.__defaults__ = (None,) * len(T._fields) + if isinstance(default_values, collections.Mapping): + prototype = T(**default_values) + else: + prototype = T(*default_values) + T.__new__.__defaults__ = tuple(prototype) + return T + + +def dot_notation(dict_like, dot_spec): + """Return the value corresponding to the dot_spec from a dict_like object + :param dict_like: dictionary, JSON, etc. + :param dot_spec: a dot notation (e.g. a1.b1.c1.d1 => a1["b1"]["c1"]["d1"]) + :return: the value referenced by the dot_spec + """ + attrs = dot_spec.split(".") # we split the path + parent = dict_like.get(attrs[0]) + children = ".".join(attrs[1:]) + if not (parent and children): # if no children or no parent, bail out + return parent + if isinstance(parent, list): # here, we apply remaining path spec to all children + return [dot_notation(j, children) for j in parent] + elif isinstance(parent, dict): + return dot_notation(parent, children) + else: + return None + + +def list_flatten(l): + """ + Flatten a complex nested list of nested lists into a flat list (DFS). + For example, [ [1, 2], [[[2,3,4], [2,3,4]], [3,4,5, 'hello']]] + will produce [1, 2, 2, 3, 4, 2, 3, 4, 3, 4, 5, 'hello'] + """ + return list(itertools.chain(*[list_flatten(j) if isinstance(j, list) else [j] for j in l])) + + +def inverted_dict(keys: list, key_val_dict: dict) -> dict: + """ + Get val -> [keys] mapping for the given keys using key_val_dict + :param keys: the keys we are interested in (a list) + :param key_val_dict: the key -> val mapping + :return: inverted dictionary of val -> [keys] (for the subset dict of given keys) + """ + res = {} + all_tuples = ((k, key_val_dict[k] if k in key_val_dict else 'no-parent-' + k) for k in keys) + for k, v in all_tuples: + if v in res: + res[v].append(k) + else: + res[v] = [k] + # making sure to remove duplicate keys + res = dict((v, list(set(k_list))) for v, k_list in res.items()) + return res |