From 91f093c9b33ff492618d43df70a5c77ca7f63429 Mon Sep 17 00:00:00 2001 From: rohit-lunavara Date: Tue, 16 Feb 2021 22:46:44 -0500 Subject: [PATCH 1/4] Added: container_utils.py with flatten and widen functions --- .gitignore | 1 + container_utils.py | 189 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 .gitignore create mode 100644 container_utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/container_utils.py b/container_utils.py new file mode 100644 index 0000000..d8ebd34 --- /dev/null +++ b/container_utils.py @@ -0,0 +1,189 @@ +""" +Author: Rohit Lunavara +Date: 02/16/2021 +""" + +SEPARATOR = "/" + +def flatten(container): + """ + Accepts a multi-dimensional container of any size and converts it into a one dimensional associative array whose keys are strings representing their value's path in the original container. + + E.g. + + Input: + flatten([ + { + "one": 1 + }, + { + "two": { + "three": 3 + } + }, + { + "four": [4, 5, {"five": [6]}] + } + ]) + + Output: + { + "0/one": 1, + "1/two/three": 3, + "2/four/0": 4, + "2/four/1": 5, + "2/four/2/five/0": 6 + } + """ + + flattened_container = {} + def recursive_flatten(container, current_key = None): + # Default list argument results in shared list object between function calls, therefore, we initialize it separately + if current_key is None: + current_key = [] + + if isinstance(container, dict): + for key, value in container.items(): + recursive_flatten(value, current_key + [key]) + + elif isinstance(container, list): + for index, value in enumerate(container): + recursive_flatten(value, current_key + [f"{index}"]) + + else: + string_key = SEPARATOR.join(current_key) + flattened_container[string_key] = container + + recursive_flatten(container) + return flattened_container + +def widen(container): + """ + Accepts a one dimensional associative array whose keys are strings representing their value's path in a multi-dimensional container and converts it into the original container based on the value's path. + + E.g. + + Input: + widen({ + "0/one": 1, + "1/two/three": 3, + "2/four/0": 4, + "2/four/1": 5, + "2/four/2/five/0": 6 + }) + + Output: + [ + { + "one": 1 + }, + { + "two": { + "three": 3 + } + }, + { + "four": [4, 5, {"five": [6]}] + } + ] + """ + def recursive_replacement(container): + """ + Recursively replaces dictionaries containing integer keys in string format with lists. + Handles cases where there could be multiple nested dictionaries and lists. + """ + if not isinstance(container, dict): + return container + + any_random_key = next(iter(container)) + + if isinstance(any_random_key, int): + list_size = int(max(container)) + 1 + list_container = [None] * (list_size) + for key, value in container.items(): + list_index = int(key) + list_container[list_index] = recursive_replacement(value) + return list_container + + else: + for key, value in container.items(): + container[key] = recursive_replacement(value) + return container + + widened_container = {} + # Store lists as dictionaries with integer keys, replace recursively later + for key, value in container.items(): + current_keys = key.split(SEPARATOR) + previous_container, current_container = None, widened_container + + for key in current_keys: + modified_key = int(key) if key.isdigit() else key + + if modified_key not in current_container: + current_container[modified_key] = {} + + previous_container, current_container = current_container, current_container[modified_key] + + previous_container[modified_key] = value + + return recursive_replacement(widened_container) + +if __name__ == "__main__": + # Test Cases + widened_containers = [ + { + "one": 1 + }, + { + 'one': + { + 'two': 3, + 'four': [5, 6, 7] + }, + 'eight': + { + 'nine': + { + 'ten': 11 + } + } + }, + [ + { + "one": 1 + }, + { + "two": { + "three": 3 + } + }, + { + "four": [4, 5, {"five": [6]}] + } + ], + + ] + flattened_containers = [ + { + "one": 1 + }, + { + 'one/two': 3, + 'one/four/0': 5, + 'one/four/1': 6, + 'one/four/2': 7, + 'eight/nine/ten': 11 + }, + { + '0/one': 1, + '1/two/three': 3, + '2/four/0': 4, + '2/four/1': 5, + '2/four/2/five/0': 6 + }, + + ] + + for index in range(len(widened_containers)): + assert flatten(widened_containers[index]) == flattened_containers[index] + assert widen(flattened_containers[index]) == widened_containers[index] From b7a5a4bd15cd71c8801aaf9365067685b09dfbf4 Mon Sep 17 00:00:00 2001 From: rohit-lunavara Date: Tue, 16 Feb 2021 23:01:42 -0500 Subject: [PATCH 2/4] Modified: container_utils.py with better readability --- container_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/container_utils.py b/container_utils.py index d8ebd34..d6dbb1e 100644 --- a/container_utils.py +++ b/container_utils.py @@ -100,14 +100,17 @@ def recursive_replacement(container): if isinstance(any_random_key, int): list_size = int(max(container)) + 1 list_container = [None] * (list_size) + for key, value in container.items(): list_index = int(key) list_container[list_index] = recursive_replacement(value) + return list_container else: for key, value in container.items(): container[key] = recursive_replacement(value) + return container widened_container = {} @@ -161,7 +164,6 @@ def recursive_replacement(container): "four": [4, 5, {"five": [6]}] } ], - ] flattened_containers = [ { @@ -181,7 +183,6 @@ def recursive_replacement(container): '2/four/1': 5, '2/four/2/five/0': 6 }, - ] for index in range(len(widened_containers)): From 84be43014349777d6fed2591d781e0c00595efce Mon Sep 17 00:00:00 2001 From: rohit-lunavara Date: Wed, 17 Feb 2021 01:08:31 -0500 Subject: [PATCH 3/4] Fixed: container_utils.py by adding checks for empty containers in both flatten and widen --- container_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/container_utils.py b/container_utils.py index d6dbb1e..d7d7b4e 100644 --- a/container_utils.py +++ b/container_utils.py @@ -35,6 +35,8 @@ def flatten(container): "2/four/2/five/0": 6 } """ + if len(container) == 0: + return {} flattened_container = {} def recursive_flatten(container, current_key = None): @@ -113,6 +115,9 @@ def recursive_replacement(container): return container + if len(container) == 0: + return container + widened_container = {} # Store lists as dictionaries with integer keys, replace recursively later for key, value in container.items(): @@ -134,6 +139,7 @@ def recursive_replacement(container): if __name__ == "__main__": # Test Cases widened_containers = [ + {}, { "one": 1 }, @@ -166,6 +172,7 @@ def recursive_replacement(container): ], ] flattened_containers = [ + {}, { "one": 1 }, From eb4c457640d8ee02de5085e59556eea9ba4cf28d Mon Sep 17 00:00:00 2001 From: rohit-lunavara Date: Wed, 17 Feb 2021 01:11:21 -0500 Subject: [PATCH 4/4] Added: container_utils.py contains additional test cases for empty containers --- container_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/container_utils.py b/container_utils.py index d7d7b4e..ad27090 100644 --- a/container_utils.py +++ b/container_utils.py @@ -139,7 +139,6 @@ def recursive_replacement(container): if __name__ == "__main__": # Test Cases widened_containers = [ - {}, { "one": 1 }, @@ -172,7 +171,6 @@ def recursive_replacement(container): ], ] flattened_containers = [ - {}, { "one": 1 }, @@ -195,3 +193,8 @@ def recursive_replacement(container): for index in range(len(widened_containers)): assert flatten(widened_containers[index]) == flattened_containers[index] assert widen(flattened_containers[index]) == widened_containers[index] + + assert flatten([]) == {} + assert flatten({}) == {} + assert widen([]) == [] + assert widen({}) == {}