Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions solution.py
Original file line number Diff line number Diff line change
@@ -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)
130 changes: 130 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
@@ -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])