Preface: To help explain why I am doing this, I will explain the end goal. Essentially I have a list of accounts that are defined in a very specific syntax. Here are some examples:
Assets:Bank:Car
Assets:Bank:House
Assets:Savings:Emergency
Assets:Savings:Goals:Roof
Assets:Reserved
As can be seen above, an account can have any number of parents and children. The end goal is to parse the above accounts into a tree structure in Python that will be used for providing account auto-completion in the Sublime Text Editor (i.e, if I typed Assets: and then queried for auto-complete, I would be presented with a list as such: Bank, Savings, Reserved)
The Result: Using the account list from the preface, the desired result in Python would look something like below:
[
{
"Assets":[
{
"Bank":[
"Car",
"House"
]
},
{
"Savings":[
"Emergency",
{
"Goals":[
"Roof"
]
}
]
},
"Reserved"
]
}
]
Half-Solution: I was able to get two basic accounts to get added together using recursion. This works for adding these two: Assets:Bank:Car and Assets:Bank:House. However, once they start to differ it starts to fall apart and the recursion gets messy, so I'm not sure if it's the best way.
import re
def parse_account(account_str):
subs = account_str.split(":")
def separate(subs):
if len(subs) == 1:
return subs
elif len(subs):
return [{subs[0]: separate(subs[1:])}]
return separate(subs)
def merge_dicts(a, b):
# a will be a list with dictionaries and text values and then nested lists/dictionaries/text values
# b will always be a list with ONE dictionary or text value
key = b[0].keys()[0] # this is the dictionary key of the only dictionary in the b list
for item in a: # item is a dictionary or a text value
if isinstance(item, dict): # if item is a dictionary
if key in item:
# Is the value a list with a dict or a list with a text value
if isinstance(b[0][key][0], str):
# Extend the current list with the new value
item[key].extend(b[0][key])
else:
# Recurse to the next child
merge_dicts(item[key], b[0][key])
else:
return a
# Accounts have an "open [name]" syntax for defining them
text = "open Assets:Bank:Car\nopen Assets:Bank:House\nopen Assets:Savings:Emergency\nopen Assets:Savings:Goals:Roof\nopen Assets:Reserved"
EXP = re.compile("open (.*)")
accounts = EXP.findall(text) # This grabs all accounts
# Create a list of all the parsed accounts
dicts = []
for account in accounts:
dicts.append(parse_account(account))
# Attempt to merge two accounts together
final = merge_dicts(dicts[0], dicts[1])
print final
# In the future we would call: reduce(merge_dicts, dicts) to merge all accounts
I could be going about this in the completely wrong way and I would be interested in differing opinions. Otherwise, does anyone have insight into how to make this work with the remaining accounts in the example string?
SyntaxError(Reservedis a key without a value).print json.dumps(final)in order to make it more discernable.