Преобразование неструктурированного списка имен и данных во вложенный словарь
У меня есть "неструктурированный" список, который выглядит так:
info = [
'Joe Schmoe',
'W / M / 64',
'Richard Johnson',
'OFFICER',
'W / M /48',
'Adrian Stevens',
'? / ? / 27'
]
Неструктурированный в том, что список состоит из наборов:
- (Имя, должность, демографическая информация) или триплеты
- (Имя, демографическая информация) пар.
В последнем случае, Officer=False
и в первом Officer=True
, Строки демографической информации представляют Race / Gender / Age
, с NaN
s представлены буквальными вопросительными знаками. Вот где я хотел бы добраться:
res = {
'Joe Schmoe': {
'race': 'W',
'gender': 'M',
'age': 64,
'officer': False
},
'Richard Johnson': {
'race': 'W',
'gender': 'M',
'age': 48,
'officer': True
},
'Adrian Stevens': {
'race': 'NaN',
'gender': 'NaN',
'age': 27,
'officer': False
}
}
Прямо сейчас я построил две функции для этого. Первый ниже и обрабатывает строки демографической информации. (Я в порядке с этим; просто поместил это здесь для справки.)
import re
def fix_demographic(info):
# W / M / ?? --> W / M / NaN
# ?/M/? --> NaN / M / NaN
# Keep as str NaN rather than np.nan for now
race, gender, age = re.split('\s*/\s*', re.sub('\?+', 'NaN', info))
return race, gender, age
Вторая функция деконструирует список и выбрасывает его значения в разные места в словарном результате:
demographic = re.compile(r'(\w+|\?+)\s*\/\s*(\w+|\?+)\s*\/\s*(\w+|\?+)')
def parse_victim_info(info: list):
res = defaultdict(dict)
for i in info:
if not demographic.fullmatch(i) and i.lower() != 'officer':
# We have a name
previous = 'name'
name = i
if i.lower() == 'officer':
res[name]['officer'] = True
previous = 'officer'
if demographic.fullmatch(i):
# We have demographic info; did "OFFICER" come before it?
if previous == 'name':
res[name]['officer'] = False
race, gender, age = fix_demographic(i)
res[name]['race'] = race
res[name]['gender'] = gender
res[name]['age'] = int(age) if age.isnumeric() else age
previous = None
return res
>>> parse_victim_info(info)
defaultdict(dict,
{'Adrian Stevens': {'age': 27,
'gender': 'NaN',
'officer': False,
'race': 'NaN'},
'Richard Johnson': {'age': 48,
'gender': 'M',
'officer': True,
# ... ...
Эта вторая функция кажется слишком многословной и утомительной для того, что она делает.
Есть ли лучший способ для этого, который может более разумно запомнить категоризацию последнего значения, увиденного в итерации?
2 ответа
Такие вещи очень хорошо подходят для генератора:
Код:
def find_triplets(data):
data = iter(data)
while True:
name = next(data)
demo = next(data)
officer = demo == 'OFFICER'
if officer:
demo = next(data)
yield name, officer, demo
Тестовый код:
info = [
'Joe Schmoe',
'W / M / 64',
'Lillian Schmoe',
'W / F / 60',
'Richard Johnson',
'OFFICER',
'W / M /48',
'Adrian Stevens',
'? / ? / 27'
]
for x in find_triplets(info):
print(x)
Результаты:
('Joe Schmoe', False, 'W / M / 64')
('Lillian Schmoe', False, 'W / F / 60')
('Richard Johnson', True, 'W / M /48')
('Adrian Stevens', False, '? / ? / 27')
Преобразование триплетов кортежей в dict
:
import re
def fix_demographic(info):
# W / M / ?? --> W / M / NaN
# ?/M/? --> NaN / M / NaN
# Keep as str NaN rather than np.nan for now
race, gender, age = re.split('\s*/\s*', re.sub('\?+', 'NaN', info))
return dict(race=race, gender=gender, age=age)
data_dict = {name: dict(officer=officer, **fix_demographic(demo))
for name, officer, demo in find_triplets(info)}
print(data_dict)
Результаты:
{
'Joe Schmoe': {'officer': False, 'race': 'W', 'gender': 'M', 'age': '64'},
'Lillian Schmoe': {'officer': False, 'race': 'W', 'gender': 'F', 'age': '60'},
'Richard Johnson': {'officer': True, 'race': 'W', 'gender': 'M', 'age': '48'},
'Adrian Stevens': {'officer': False, 'race': 'NaN', 'gender': 'NaN', 'age': '27'}
}
Ты можешь использовать itertools.groupby
в Python3:
import itertools
import re
info = [
'Joe Schmoe',
'W / M / 64',
'Lillian Schmoe',
'W / F / 60',
'Richard Johnson',
'OFFICER',
'W / M /48',
'Adrian Stevens',
'? / ? / 27'
]
data = [list(b) for a, b in itertools.groupby(info, key=lambda x:x.count('/') > 0 or x == 'OFFICER')]
final_data = {data[i][0]:{**{a:'NaN' if b == '?' else (int(b) if b.isdigit() else b) for a, b in zip(['race', 'gender', 'age'], filter(None, re.split('\s+|/', [h for h in data[i+1] if h.count('/') > 0][0])))}, **{"officer":"OFFICER" in data[i+1]}} for i in range(0, len(data), 2)}
Выход:
{'Joe Schmoe': {'race': 'W', 'gender': 'M', 'age': 64, 'officer': False}, 'Lillian Schmoe': {'race': 'W', 'gender': 'F', 'age': 60, 'officer': False}, 'Richard Johnson': {'race': 'W', 'gender': 'M', 'age': 48, 'officer': True}, 'Adrian Stevens': {'race': 'NaN', 'gender': 'NaN', 'age': 27, 'officer': False}}