diff --git a/solution.py b/solution.py new file mode 100644 index 0000000..cd8e0a1 --- /dev/null +++ b/solution.py @@ -0,0 +1,73 @@ +""" +DFS approach: go through each element until its leaf node is found, then add it to a dict +""" +def solution1(input) -> dict: + def flatten_container(elem, curr_path="", flattened_output={}): + if type(elem) != dict and type(elem) != list: #not iterable, reached leaf node + flattened_output[curr_path[:-1]] = elem + + if type(elem) == dict: + for key in elem: + flatten_container(elem[key], curr_path + f"{key}/", flattened_output) + + if type(elem) == list: + for i in range(len(elem)): + flatten_container(elem[i], curr_path + f"{i}/", flattened_output) + + return flattened_output + + return flatten_container(input) + +def solution2(input) -> dict: + """ Takes a subpath and converts it into an int type if possible, otherwise returns as is (str) """ + def make_int_if_array(subpath): + try: + return int(subpath) + except: + return subpath + + """ 4 cases and different operations required based on current underlying object and next underlying object """ + def initialize_next_object(next_subpath, curr_subpath, local_container): + if type(curr_subpath) == int and type(next_subpath) == int: #nested array + local_container.append([]) + if type(curr_subpath) == int and type(next_subpath) == str: #dict inside array + local_container.append({}) + if type(curr_subpath) == str and type(next_subpath) == int: #array inside dict + local_container[curr_subpath] = [] + if type(curr_subpath) == str and type(next_subpath) == str: #nested dict + local_container[curr_subpath] = {} + + def build_container(container, seen_path, subpaths, i, value): + local_container = container + for key in seen_path: + local_container = local_container[key] #run through all prior keys to index container down to most recent state + + subpath = subpaths[i] + if type(subpath) == int: + in_container = (subpath < len(local_container)) #check if index in array + else: + in_container = (subpath in local_container) #check if key in dict + + if not in_container: #only build subpath in the container if it doesn't exist already + if i == len(subpaths) - 1 and type(subpath) == int: + local_container.append(value) + elif i == len(subpaths) - 1 and type(subpath) == str: + local_container[subpath] = value + else: + initialize_next_object(subpaths[i+1], subpath, local_container) + + def reverse(paths, container={}): + try: + array = list(paths.keys())[0] + first_val = int(array.split("/")[0]) #checking first val, if it is array index then input is array + container = [] #set container to array when input is mdim array + except: + container = {} + + for path in paths: + subpaths = [make_int_if_array(subpath) for subpath in path.split("/")] #preprocess: cast array indices to int + for i in range(len(subpaths)): + build_container(container, [subpaths[j] for j in range(i)], subpaths, i, paths[path]) + return container + + return reverse(input) \ No newline at end of file diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..5407107 --- /dev/null +++ b/tests.py @@ -0,0 +1,130 @@ +import solution + +def run_unit_tests(testcases, expected_results): + for n in range(len(testcases)): + print(f"Running tests for problem{n+1}...") + tests, expected = testcases[n], expected_results[n] + for i in range(len(tests)): + if n +1 == 1: #test problem 1 + actual = solution.solution1(tests[i]) + if n + 1 == 2: # test problem 2 + actual = solution.solution2(tests[i]) + + if actual == expected[i]: + print(f"Test{i+1} passed") + else: + print(f"Test{i+1} failed") + print(actual) + +def run_both(testcases): + print(f"Running end-to-end tests...") + for i in range(len(testcases)): + test = testcases[i] + if test == solution.solution2(solution.solution1(test)): + print(f"End to End Test{i+1} passed") + + +""" +Test cases for Problem 1 (Flattening a multidimensional container into an associative array): + - solution1() is the top-level for flatten_container() function in solution.py which solves this +""" + +""" +test1 was provided in the github +""" + +test1 = { + 'one': + { + 'two': 3, + 'four': [ 5,6,7] + }, + 'eight': + { + 'nine': + { + 'ten':11 + } + } +} + +""" +test2 is a further nested case of test1 +""" + +test2 = { + 'one': + { + 'two': {'nested1_deep':{'nested2_deep1':3, 'nested2_deep2':9}}, + 'four': [ 5,6,7] + }, + 'eight': + { + 'nine': + { + 'ten':11 + } + } +} + +""" +test3 has a nested dict containing an array +""" + +test3 = { + 'one': + { + 'two': 3, + 'four': [ {'key':[3,4,7]},6,7], + }, + 'eight': + { + 'nine': + { + 'ten':11 + } + } +} + +""" +empty dict input -> should return empty output +""" +test4 = {} + +""" +first key encountered is not nested +""" +test5 = {"key1":5,"key2":[{"key3":[8,9]},2,3]} + +""" +input is not a dict +""" +test6 = [1,2,3] + +""" +array contains a nested array +""" +test7 = {"key1":5,"key2":[[8,9],2,3]} + +expected1 = { "one/two":3, "one/four/0":5, "one/four/1":6, "one/four/2": 7, "eight/nine/ten":11 } +expected2 = { "one/two/nested1_deep/nested2_deep1":3, "one/two/nested1_deep/nested2_deep2":9, "one/four/0":5, "one/four/1":6, "one/four/2": 7, "eight/nine/ten":11 } +expected3 = { "one/two": 3, "one/four/0/key/0":3, "one/four/0/key/1": 4, "one/four/0/key/2": 7, "one/four/1": 6, "one/four/2": 7, "eight/nine/ten":11 } +expected4 = {} +expected5 = {"key1": 5, "key2/0/key3/0":8, "key2/0/key3/1":9, "key2/1":2, "key2/2": 3} +expected6 = { '0':1, '1':2, '2':3} +expected7 = {"key1":5, "key2/0/0":8, "key2/0/1":9, "key2/1":2, "key2/2":3} + +""" +Test cases for Problem 2 (Reversing the associative array back into the original) + - solution2() is the top-level which solves problem2 + +Test cases for Problem 2 are output of function solution1 applied to each test case +Expected for each of Problem 2's outputs are the testcases themselves for Problem 1 +""" + +tests = [[test1, test2, test3, test4, test5, test6, test7], [expected1, expected2, expected3, expected4, expected5, expected6, expected7]] + +expected = [[expected1, expected2, expected3, expected4, expected5, expected6, expected7], [test1, test2, test3, test4, test5, test6, test7]] + +run_unit_tests(tests, expected) +run_both(tests[0])