diff --git a/badges/test_coverage.svg b/badges/test_coverage.svg index d76f04f02a173e1c79189ce9e75fcc71f7519589..d1f89653c3addd0101c7595d0cb602cccbd3f8d2 100644 --- a/badges/test_coverage.svg +++ b/badges/test_coverage.svg @@ -15,7 +15,7 @@ <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="31.5" y="14">coverage</text> - <text x="80" y="15" fill="#010101" fill-opacity=".3">51%</text> - <text x="80" y="14">51%</text> + <text x="80" y="15" fill="#010101" fill-opacity=".3">53%</text> + <text x="80" y="14">53%</text> </g> </svg> diff --git a/binarycpython/tests/main.py b/binarycpython/tests/main.py index fadaabf3a6f717dd757f985c093b05b1528ac56a..01d8358dac2bc2fdab84fe895a66dd653e79794c 100755 --- a/binarycpython/tests/main.py +++ b/binarycpython/tests/main.py @@ -24,7 +24,17 @@ from binarycpython.tests.test_dicts import ( test_merge_dicts, test_setopts, test_AutoVivicationDict, - test_inspect_dict + test_inspect_dict, + test_custom_sort_dict, + test_filter_dict, + test_filter_dict_through_values, + test_prepare_dict, + test_normalize_dict, + test_multiply_values_dict, + test_count_keys_recursive, + test_keys_to_floats, + test_recursive_change_key_to_float, + test_recursive_change_key_to_string ) from binarycpython.tests.test_ensemble import ( test_binaryc_json_serializer, diff --git a/binarycpython/tests/test_dicts.py b/binarycpython/tests/test_dicts.py index 204464ce432752451b30c81c907c657dbffc9075..3146c6b2e1356ace8a75952605a3b1179056a77a 100644 --- a/binarycpython/tests/test_dicts.py +++ b/binarycpython/tests/test_dicts.py @@ -1,17 +1,10 @@ """ Unittests for dicts module -TODO: keys_to_floats -TODO: recursive_change_key_to_float -TODO: recursive_change_key_to_string TODO: _nested_set TODO: _nested_get -TODO: _recursive_normalize_floats -TODO: multiply_float_values TODO: subtract_dicts -TODO: count_keys_recursive TODO: update_dicts -TODO: multiply_values_dict """ import os @@ -31,7 +24,13 @@ from binarycpython.utils.dicts import ( filter_dict, filter_dict_through_values, prepare_dict, - custom_sort_dict + custom_sort_dict, + multiply_values_dict, + keys_to_floats, + count_keys_recursive, + recursive_change_key_to_float, + recursive_change_key_to_string, + multiply_float_values ) TMP_DIR = temp_dir("tests", "test_dicts") @@ -406,8 +405,144 @@ class test_normalize_dict(unittest.TestCase): self.assertEqual(sum(list(res_1.values())), 1.0) +class test_multiply_values_dict(unittest.TestCase): + """ + Unittests for function multiply_values_dict + """ + + def test_multiply_values_dict(self): + with Capturing() as output: + self._test_multiply_values_dict() + + def _test_multiply_values_dict(self): + """ + Test multiply_values_dict + """ + + input_1 = {'a': 1, 'b': {'c': 10}} + desired_output_1 = {'a': 2, 'b': {'c': 20}} + + output_1 = multiply_values_dict(input_1, 2) + + # + self.assertEqual(output_1, desired_output_1) + + +class test_count_keys_recursive(unittest.TestCase): + """ + Unittests for function count_keys_recursive + """ + + def test_count_keys_recursive(self): + with Capturing() as output: + self._test_count_keys_recursive() + + def _test_count_keys_recursive(self): + """ + Test count_keys_recursive + """ + + # + input_1 = {'a': 2, 'b': {'c': 20, 'd': {'aa': 1, 'bb': 2}}} + output_1 = count_keys_recursive(input_1) + + # + self.assertEqual(output_1, 6) + +class test_keys_to_floats(unittest.TestCase): + """ + Unittests for function keys_to_floats + """ + + def test_keys_to_floats(self): + with Capturing() as output: + self._test_keys_to_floats() + + def _test_keys_to_floats(self): + """ + Test keys_to_floats + """ + + input_1 = {'a': 1, '1': 2, '1.0': 3, 'b': {4: 10, '5': 1}} + output_1 = keys_to_floats(input_1) + + desired_output_1 = {'a': 1, 1.0: 3, 'b': {4.0: 10, 5.0: 1}} + + self.assertEqual(output_1, desired_output_1) + +class test_recursive_change_key_to_float(unittest.TestCase): + """ + Unittests for function recursive_change_key_to_float + """ + + def test_recursive_change_key_to_float(self): + with Capturing() as output: + self._test_recursive_change_key_to_float() + + def _test_recursive_change_key_to_float(self): + """ + Test recursive_change_key_to_float + """ + + input_1 = {'a': 1, '1': 2, '1.0': 3, 'b': {4: 10, '5': 1}} + output_1 = recursive_change_key_to_float(input_1) + + desired_output_1 = OrderedDict([('a', 1), (1.0, 3), ('b', OrderedDict([(4.0, 10), (5.0, 1)]))]) + + self.assertEqual(output_1, desired_output_1) + +class test_recursive_change_key_to_string(unittest.TestCase): + """ + Unittests for function recursive_change_key_to_string + """ + + def test_recursive_change_key_to_string(self): + with Capturing() as output: + self._test_recursive_change_key_to_string() + + def _test_recursive_change_key_to_string(self): + """ + Test recursive_change_key_to_string + """ + + input_1 = {'a': 1, '1': 2, '1.0': 3, 'b': {4: 10, '5': 1, 6: 10}} + output_1 = recursive_change_key_to_string(input_1, "{:.2E}") + + desired_output_1 = OrderedDict([('a', 1), + ('1.00E+00', 3), + ('b', + OrderedDict([('4.00E+00', 10), + ('5.00E+00', 1), + ('6.00E+00', 10)]))]) + + self.assertEqual(output_1, desired_output_1) + +class test_multiply_float_values(unittest.TestCase): + """ + Unittests for function multiply_float_values + """ + + def test_multiply_float_values(self): + with Capturing() as output: + self._test_multiply_float_values() + + def _test_multiply_float_values(self): + """ + Test multiply_float_values + """ + + # Test with all valid input + input_1 = {1: 2.2, '2': {'a': 2, 'b': 10, 'c': 0.5}} + multiply_float_values(input_1, 2) + desired_output_1 = {1: 4.4, '2': {'a': 2, 'b': 10, 'c': 1.0}} + + # + self.assertEqual(input_1, desired_output_1) + # Test with unrecognised input: + input_2 = {1: 2.2, '2': {'a': 2, 'b': 10, 'c': 0.5, 'd': dummy('david')}} + _ = multiply_float_values(input_2, 2) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/binarycpython/tests/tmp_functions.py b/binarycpython/tests/tmp_functions.py index 88581f3709a81e66accec56eb73a39a0c59517ad..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/binarycpython/tests/tmp_functions.py +++ b/binarycpython/tests/tmp_functions.py @@ -1,22 +0,0 @@ - - - - -# class TestDistributions(unittest.TestCase): -# """ -# Unittest class - -# # https://stackoverflow.com/questions/17353213/init-for-unittest-testcase -# """ - -# def __init__(self, *args, **kwargs): -# """ -# init -# """ -# super(TestDistributions, self).__init__(*args, **kwargs) - - - - - - diff --git a/binarycpython/utils/dicts.py b/binarycpython/utils/dicts.py index 8452faf435d67e61812c22f422d650a11166dfad..1671e15c28426e90d4d3579e09dd38f7b6368b3c 100644 --- a/binarycpython/utils/dicts.py +++ b/binarycpython/utils/dicts.py @@ -7,26 +7,29 @@ import collections import astropy.units as u import numpy as np -def keys_to_floats(json_data): +from typing import Union + + +NUMERIC = Union[int, float, complex, np.number] + +def keys_to_floats(input_dict: dict) -> dict: """ Function to convert all the keys of the dictionary to float to float we need to convert keys to floats: - this is ~ a factor 10 faster than David's recursive_change_key_to_float routine, probably because this version only does the float conversion, nothing else. - """ - - # assumes nested dicts ... - # new_data = {} + this is ~ a factor 10 faster than David's ``recursive_change_key_to_float`` routine, probably because this version only does the float conversion, nothing else. + + Args: + input_dict: dict of which we want to turn all the keys to float types if possible - # but this copies the variable type, but has some - # pointless copying - # new_data = copy.copy(json_data) - # new_data.clear() + Returns: + new_dict: dict of which the keys have been turned to float types where possible + """ # this adopts the type correctly *and* is fast - new_data = type(json_data)() + new_dict = type(input_dict)() - for k, v in json_data.items(): + for k, v in input_dict.items(): # convert key to a float, if we can # otherwise leave as is try: @@ -37,7 +40,7 @@ def keys_to_floats(json_data): # act on value(s) if isinstance(v, list): # list data - new_data[newkey] = [ + new_dict[newkey] = [ keys_to_floats(item) if isinstance(item, collections.abc.Mapping) else item @@ -45,20 +48,27 @@ def keys_to_floats(json_data): ] elif isinstance(v, collections.abc.Mapping): # dict, ordereddict, etc. data - new_data[newkey] = keys_to_floats(v) + new_dict[newkey] = keys_to_floats(v) else: # assume all other data are scalars - new_data[newkey] = v + new_dict[newkey] = v - return new_data + return new_dict -def recursive_change_key_to_float(input_dict): +def recursive_change_key_to_float(input_dict: dict) -> dict: """ Function to recursively change the key to float This only works if the dict contains just sub-dicts or numbers/strings. + Does not work with lists as values + + Args: + input_dict: dict of which we want to turn all the keys to float types if possible + + Returns: + new_dict: dict of which the keys have been turned to float types where possible """ new_dict = collections.OrderedDict() @@ -80,26 +90,36 @@ def recursive_change_key_to_float(input_dict): return new_dict -def recursive_change_key_to_string(input_dict, custom_format="{:g}"): +def recursive_change_key_to_string(input_dict: dict, custom_format: str = "{:g}"): """ - Function to recursively change the key back to a string but this time in a format that we decide + Function to recursively change the key back to a string but this time in a format that we decide. We'll try to turn a string key into a float key before formatting the key + + Args: + input_dict: dict of which we want to turn all the keys to string types (with a custom format) + custom_format: custom format used when turning the key to strings + + Returns: + new_dict: dict of which the keys have been turned to string types where possible """ new_dict = collections.OrderedDict() for key in input_dict: + # Try to turn into a float + try: + string_key = float(key) + except ValueError: + string_key = key + + # Turn into string with new format + if not isinstance(string_key, str): + string_key = custom_format.format(string_key) + + # If dictionary type, call function again if isinstance(input_dict[key], (dict, collections.OrderedDict)): - if isinstance(key, (int, float)): - string_key = custom_format.format(key) - new_dict[string_key] = recursive_change_key_to_string(input_dict[key]) - else: - new_dict[key] = recursive_change_key_to_string(input_dict[key]) + new_dict[string_key] = recursive_change_key_to_string(input_dict[key], custom_format) else: - if isinstance(key, (int, float)): - string_key = custom_format.format(key) - new_dict[string_key] = input_dict[key] - else: - new_dict[key] = input_dict[key] + new_dict[string_key] = input_dict[key] return new_dict @@ -130,22 +150,22 @@ def _nested_get(dic, keys): return dic[keys[-1]] -def _recursive_normalize_floats(path, d, const, parent=None, ignore=None): +def _recursive_normalize_floats(path, input_dict, factor, parent=None, ignore=None): """ - function to walk through the dictionary, multiplying only float values by a const + Function to walk through the dictionary, multiplying only float values by a factor """ if not parent: - parent = d + parent = input_dict - for k, v in d.items(): + for k, v in input_dict.items(): if ignore and k in ignore: continue if isinstance(v, float): path.append(k) - # must be a float, multiply by the constant - _nested_set(parent, path, v * const) + # must be a float, multiply by the factor + _nested_set(parent, path, v * factor) path.pop() elif isinstance(v, (str, int)): path.append(k) @@ -160,7 +180,7 @@ def _recursive_normalize_floats(path, d, const, parent=None, ignore=None): elif isinstance(v, collections.abc.Mapping): path.append(k) # nested dict - _recursive_normalize_floats(path, v, const, parent=parent) + _recursive_normalize_floats(path, v, factor, parent=parent) path.pop() else: print( @@ -170,17 +190,17 @@ def _recursive_normalize_floats(path, d, const, parent=None, ignore=None): ) -def multiply_float_values(d, const, ignore=None): +def multiply_float_values(input_dict, factor, ignore=None): """ A function to recursively multiply values of a (nested) dictionary that are floats by a constant. Nested dictionaries call this function recursively. Args: - d: the dictionary - const: the constant that multiplies float values + input_dict: the dictionary + factor: the constant that multiplies float values """ path = [] - _recursive_normalize_floats(path, d, const, parent=d, ignore=ignore) + _recursive_normalize_floats(path, input_dict, factor, parent=input_dict, ignore=ignore) def subtract_dicts(dict_1: dict, dict_2: dict) -> dict: @@ -208,7 +228,7 @@ def subtract_dicts(dict_1: dict, dict_2: dict) -> dict: dict_2: second dictionary Returns: - Subtracted dictionary, i.e. ``dict_1 - dict_2`` + Subtracted dictionary, i.e. ``dict_1 - dict_2`` """ # Set up new dict @@ -391,9 +411,15 @@ def inspect_dict( return structure_dict -def count_keys_recursive(input_dict): +def count_keys_recursive(input_dict: dict) -> int: """ - Function to count the total number of keys in a dictionary + Function to recursively count the total number of keys in a dictionary. + + Args: + input_dict: dictionary that we want to know the total amount of keys from. + + Returns: + local_count: total amount of keys within the input_dict. """ local_count = 0 @@ -401,6 +427,7 @@ def count_keys_recursive(input_dict): local_count += 1 if isinstance(input_dict[key], (dict, collections.OrderedDict)): local_count += count_keys_recursive(input_dict[key]) + return local_count @@ -643,11 +670,20 @@ def update_dicts(dict_1: dict, dict_2: dict) -> dict: return new_dict -def multiply_values_dict(input_dict, factor): +def multiply_values_dict(input_dict: dict, factor: NUMERIC): """ Function that goes over dictionary recursively and multiplies the value if possible by a factor - If the key equals "general_info", the multiplication gets skipped + If the key equals "general_info", the multiplication gets skipped. + + This function changes the values in-place, so the original dict is modified + + Args: + input_dict: dictionary of which we want to multiply the values by <factor> + factor: factor that we want to multiply the values with + + Returns: + multiplied_dict: dict containing the multiplied keys. This is the same object as we passed as input. """ for key in input_dict: @@ -661,7 +697,7 @@ def multiply_values_dict(input_dict, factor): return input_dict -def custom_sort_dict(input_dict): +def custom_sort_dict(input_dict: dict) -> dict: """ Returns a dictionary that is ordered, but can handle numbers better than normal OrderedDict