【发布时间】:2022-01-20 22:43:56
【问题描述】:
我正在尝试将非结构化数据转换为 python 字典。数据是这样的:
main sub_main sub_main_1
AAA A-ABC ABC
AAA A-DEF A-DEF-GHI GHI
main sub_main sub_main_2
BBB B-ABC ABC
BBB B-DEF DEF
BBB B-X B-Y B-Z ""
main sub_main sub_main_3
CCC C-ABC ABC
CCC C-X C-Y C-Z ""
CCC C-PQR C-STU 2
C-LMN C-OPQ C-RST ""
CCC C-DEF C-DEF-GHI ""
CCC C-DEF C-DEF-JKL C-MNO 1
C-XYZ ""
main sub_main sub_main_4
DDD D-ABC DEF
DDD D-PQR STU
main sub_main sub_main_5
EEE E-ABC DEF
EEE E-PQR STU
main sub_main sub_main_6
FFF F-ABC F-DEF
FFF F-PQR F-STU
现在,这里有一些条件可以将此数据转换为嵌套的 Python 字典。
- 每行开头的空格定义字典节点级别。
- 每行可以有多个键。例如,
CCC C-X C-Y C-Z 1这应该有四个嵌套键,C-Z将有1作为值(如果是子节点,对于父节点,请检查下一点)。像这样:'CCC': {'C-X': {'C-Y: 'C-Z': 1}} - 如果下一行开头有更多空间,则当前行为父节点,下一行为子节点。在这种情况下,当前行的最后一项应合并到一个键中,并用空格作为它们之间的分隔符。像这样:
变成:main sub_main sub_main_2 BBB B-ABC ABC'main': {'sub_main sub_main_2': {'BBB': {'B-ABC': 'ABC'}}}
现在,这是预期的输出:
{'main': {'sub_main sub_main_1': {'AAA': {'A-ABC': 'ABC',
'A-DEF': {'A-DEF-GHI': 'GHI'}}},
'sub_main sub_main_2': {'BBB': {'B-ABC': 'ABC',
'B-DEF': 'DEF',
'B-X': {'B-Y': {'B-Z': ''}}}},
'sub_main sub_main_3': {'CCC': {'C-ABC': 'ABC',
'C-DEF': {'C-DEF-JKL': {'C-MNO 1': {'C-XYZ': ''}},
'C-DEF-GHI': ''},
'C-PQR': {'C-STU 2': {'C-LMN': {'C-OPQ': {'C-RST': ''}}}},
'C-X': {'C-Y': {'C-Z': ''}}}},
'sub_main sub_main_4': {'DDD': {'D-ABC': 'DEF',
'D-PQR': 'STU'}},
'sub_main sub_main_5': {'EEE': {'E-ABC': 'DEF',
'E-PQR': 'STU'}},
'sub_main sub_main_6': {'FFF': {'F-ABC': 'F-DEF',
'F-PQR': 'F-STU'}}}}
这是我正在使用的代码:
def set_data(dic, key_list, key_name, value):
"""
Set the value of key up to n depth
:param dic: Output dictionary
:param key_list: List of previous keys
:param key_name: key name
:param value: Value
:return:
"""
for key in key_list:
# Get the value as per key, if key is missing then set with blank dictionary
dic = dic.setdefault(key, {})
# Set the value of the key_name
dic[key_name] = value
def get_data(dic, key_list):
"""
Get the value of key up to n depth
:param dic: Output dictionary
:param key_list: List of previous keys
:param key_name: key name
:return:
"""
for key in key_list:
# Get the value as per key, if key is missing then set with blank dictionary
dic = dic.setdefault(key, {})
return dic
def get_space_counter(input_list):
"""
Get current space counter
:param input_list:
:return:
"""
found_space = True
space_counter = 0
for j in input_list:
if found_space and j == '':
space_counter += 1
else:
break
return space_counter
def set_val(temp, output, keys):
"""
Set key, value pair of data upto n-2 keys in temp list
:param temp: List of data
:param output: Output dictionary
:param keys: List of keys
:return:
"""
set_counter = 0
for set_counter, i in enumerate(temp[:-2], start=1):
if not get_dict_data(output, keys):
set_dict_data(output, keys, i, {})
keys.append(i)
return set_counter
def custom_parser(input):
"""
Parse unstructured data into a python dictionary
:param input: Input data
:return: Python dictionary
"""
# Initialize the variables
output = {}
counter = 0
keys = []
key_line_counter = 0
# Iterate through the input list data
for i, input_str in enumerate(input):
# Convert string into list based on empty space
split_list = input_str.strip('\n').split(' ')
# Get the initial space counter
current_space_counter = get_space_counter(split_list)
# Remove un-necessary space from the list
new_temp = list(filter(lambda x: x != '', split_list[counter:]))
try:
# Try to find the initial space counter of the next string input
next_split_list = input[i + 1].strip('\n').split(' ')
next_space_counter = get_space_counter(next_split_list)
except IndexError:
next_space_counter = current_space_counter
# If the current input space counter is less than the next input space counter,
# that means the current input is the parent node and next input is the child node
if current_space_counter < next_space_counter:
# If Number of keys in each line is not equal to the current space counter
# and the number of keys in each line is greater than 0 then pop the key from keys
if key_line_counter != current_space_counter and key_line_counter > 0:
for _ in range(key_line_counter + 1):
keys.pop()
# Get the number of keys in each line
set_counter = set_val(new_temp, output, keys)
key_line_counter = set_counter
# Generate key name, if the next line is the child node then in the current line,
# last two items merged into one as a key with space as a separator
key_name = f'{split_list[-2]} {split_list[-1]}'
# Slice the keys
keys = keys[:current_space_counter + set_counter + 1]
# Set the key, value pair in output dictionary
set_dict_data(output, keys, key_name, {})
# Append the key_name into the keys list
keys.append(key_name)
else:
# Get the number of keys in each line
set_counter = set_val(new_temp, output, keys)
# Set the key, value pair in output dictionary
set_dict_data(output, keys[:current_space_counter + set_counter + key_line_counter + 1], new_temp[-2],
new_temp[-1].replace('"', ''))
# As per the set_counter, pop the key from the keys list
for _ in range(set_counter):
keys.pop()
return output
if __name__ == '__main__':
print(custom_parser(input_data))
这是我得到的输出:
{'main': {'main': {'sub_main sub_main_5': {'EEE': {'E-ABC': 'DEF',
'E-PQR': 'STU'}},
'sub_main sub_main_6': {'FFF': {'F-ABC': 'F-DEF',
'F-PQR': 'F-STU'}}},
'sub_main sub_main_1': {'AAA': {'A-ABC': 'ABC',
'A-DEF': {'A-DEF-GHI': 'GHI'}}},
'sub_main sub_main_2': {'BBB': {'B-ABC': 'ABC',
'B-DEF': 'DEF',
'B-X': {'B-Y': {'B-Z': ''}}}},
'sub_main sub_main_3': {'CCC': {'C-ABC': 'ABC',
'C-DEF': {'C-DEF-JKL': {'C-MNO 1': {'C-XYZ': ''}}},
'C-PQR': {'C-STU 2': {'C-LMN': {'C-OPQ': {'C-RST': ''}},
'CCC': {'C-DEF': {},
'C-DEF-GHI': ''}}},
'C-X': {'C-Y': {'C-Z': ''}}},
'sub_main sub_main_4': {'DDD': {'D-ABC': 'DEF',
'D-PQR': 'STU'}}}}}
因此,如果您比较预期和实际输出(两者都在上面提供),而不是明确提及,您将了解我在问题中面临的问题。所以,请指导我如何解决这些问题。谢谢。
【问题讨论】:
-
在一种情况下:
CCC C-X C-Y C-Z 1->'CCC': {'C-X': {'C-Y: 'C-Z': 1}}每个空格代表一个嵌套字典。在另一个中,空格是键的一部分main sub_main sub_main_2->'main': {'sub_main sub_main_2':...我们如何区分?也就是说,为什么不是第二种情况:'main': {'sub_main': {'sub_main_2'... -
@Mark,如果是父项,则最后两项将合并为一项。这里
main sub_main sub_main_2是父节点,因此sub_main sub_main_2合并为一个,CCC C-X C-Y C-Z 1是子节点,因此它是嵌套字典格式。 -
我认为@Mark 在 cmets 中的第一个问题已经在文中进行了解释。我想知道问题是否已完全描述。例如,开头的空格数是否总是恰好增加 1?如果没有,更大的差距是否有意义?此外,
""似乎应该被解释为表示一个空字符串,但双引号可以出现在其他地方的文本中,如果是这样,它们会以某种方式转义吗? -
@Grismar,是的,空间总是增加 1。但它可以减少任意数量。此外,空字符串将始终作为值(最后一项),它不会出现在其他地方。
-
而
"不会出现在任何其他名称中,否则应该被解释为字符",对吗?
标签: python python-3.x