Как упростить эти словарные понимания? - PullRequest
1 голос
/ 19 января 2020

Код ниже работает и приносит результаты, которые я хочу. Тем не менее, есть два набора двух словарных пониманий, которые я не могу преобразовать в два словарных понимания. Возможно ли это?

dicTfAll={1:{'c1': ['aa','bb','cc']},
         2:{'c1': ['dd','ee','ff']}}


dicTf={1:{'c2': ['aax','bbx','cc']},
         2:{'c2': ['ddy','eey','ff']},
         3: {'c2': ['xx', '11']}}

allKeys=list(dicTfAll.keys())
dicTfAllP1={item[0]:item[1]  for item in dicTf.items() if item[0] not in allKeys}
dicTfAllP2={item[0]:dict(dicTfAll[item[0]],**item[1])  for item in dicTf.items() if item[0] in allKeys}
dicTfAllP=dicTfAllP1
dicTfAllP.update(dicTfAllP2)
# I use at this point dicTfAllP to do a lot of calculations. 
# dicTfAllP has the same form but very different values. 
allKeys=list(dicTfAllP.keys())
listOfCompanies=['c1','c2']
outputCompanies={}
for company in listOfCompanies:
    theKeys=[key for key in allKeys if company in dicTfAllP[key]]
    outputCompanies[company]={token:key  for key in theKeys for token in dicTfAllP[key][company]}

Если быть точным, я хотел бы преобразовать эти строки ниже в одном словарном понимании [он генерирует вложенный словарь, который является объединением вышеупомянутых словарей]:

allKeys=list(dicTfAll.keys())
dicTfAllP1={item[0]:item[1]  for item in dicTf.items() if item[0] not in allKeys}
dicTfAllP2={item[0]:dict(dicTfAll[item[0]],**item[1])  for item in dicTf.items() if item[0] in allKeys}

Кроме того, я хотел бы преобразовать эти строки ниже также в одном словарном понимании [он строит вложенный словарь, который восстанавливает исходные словари (до слияния)]:

outputCompanies={}
for company in listOfCompanies:
    theKeys=[key for key in allKeys if company in dicTfAllP[key]]
    outputCompanies[company]={token:key  for key in theKeys for token in dicTfAllP[key][company]}

Я особенно волнуюсь об эффективности реализации. Что можно сделать, если мне нужно сохранить такую ​​структуру? Я должен сохранить эту структуру, потому что я должен сделать много вычислений между двумя наборами понимания dict.

Ответы [ 2 ]

2 голосов
/ 19 января 2020

Понимание - удивительная особенность python, но это не всегда лучший случай. Вместо того, чтобы создавать кучу переменных и объединять их, было бы неплохо обрабатывать их по одному. Я уверен, что из этого кода можно выжать больше сока, но это должно обеспечить хороший баланс читаемости и вычислительной мощности. Я проверил вывод, чтобы убедиться, что он совпадает с выводом вашего кода.

dicTfAll = {
    1: {'c1': ['aa', 'bb', 'cc']},
    2: {'c1': ['dd', 'ee', 'ff']}
}

dicTf = {
    1: {'c2': ['aax', 'bbx', 'cc']},
    2: {'c2': ['ddy', 'eey', 'ff']},
    3: {'c2': ['xx', '11']}
}

outputCompanies = {}

for d in [dicTfAll, dicTf]:
    for idx, records in d.items():
        for company, items in records.items():

            if company not in outputCompanies.keys():
                outputCompanies[company] = {}

            for item in items:
                outputCompanies[company][item] = idx

print(outputCompanies)
# {
#     'c2': {'11': 3, 'ddy': 2, 'eey': 2, 'cc': 1, 'xx': 3, 'ff': 2, 'bbx': 1, 'aax': 1}, 
#     'c1': {'aa': 1, 'bb': 1, 'cc': 1, 'dd': 2, 'ee': 2, 'ff': 2}
# }

Поскольку вы ищете более производительный код, вот сравнение времени выполнения с использованием %%timeit в лаборатории jupyter.

# My version
2.99 µs ± 30 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


# Original Version
6.39 µs ± 25.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Я также попробовал немного более компактную версию кода, но это заняло больше времени для запуска.

%%timeit
outputCompanies = defaultdict(dict)

for d in [dicTfAll, dicTf]:
    for idx, records in d.items():
        for company, items in records.items():
            outputCompanies[company].update({item: idx for item in items})

# 4.88 µs ± 22.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Еще один тест, который включает в себя понимание:

%%timeit
outputCompanies = {}

for d in [dicTfAll, dicTf]:
    for idx, records in d.items():
        for company, items in records.items():

            if company not in outputCompanies.keys():
                outputCompanies[company] = {}

            outputCompanies[company].update({
                item: idx for item in items
            })
# 4.99 µs ± 23.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Что касается некоторых комментариев к вашему коду, dict.keys() возвращает объект list, поэтому нет необходимости вызывать list(dict.keys()). Также нет необходимости создавать переменную allKeys, так как вы можете вызывать dict.keys() в своем понимании словаря. Компании жестко запрограммированы, что хорошо, если это единственный сценарий, но не лучший, если вы ожидаете, что набор данных будет расширяться со временем. Но если вы хотите жестко их кодировать, вы можете пропустить объявление переменной и просто набрать for company in ['c1','c2']:. Затем вы можете сохранить еще несколько переменных, создав dicTfAllP, равное первому пониманию, и затем обновив его вторым. Сложите все это вместе, и вы получите следующий код. Это более читабельно и немного легче следовать, но не намного более производительно.

%%timeit
dicTfAllP = {
    item[0]:item[1]
    for item 
    in dicTf.items()
    if item[0] not in dicTfAll.keys()
}

dicTfAllP.update({
    item[0]: dict(dicTfAll[item[0]], **item[1])
    for item 
    in dicTf.items() 
    if item[0] in dicTfAll.keys()
})

outputCompanies = {}
for company in ['c1','c2']:
    theKeys = [key for key in dicTfAllP.keys() if company in dicTfAllP[key]]
    outputCompanies[company] = {
        token:key
        for key in theKeys 
        for token in dicTfAllP[key][company]
    }
# 6.11 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
0 голосов
/ 07 февраля 2020

Вы можете построить dicTfAllP, используя это понимание:

dicTfAllP = {k:{**dicTfAll.get(k,{}),**dicTf.get(k,{})} for k in set(dicTfAll).union(dicTf)}

Для outputCompanies вам потребуется помощь groupby от itertools:

from itertools import groupby
outputCompanies = {c:dict((k,v) for _,k,v in vs) for c,vs in groupby(sorted( (c,v,n) for n,cs in dicTfAllP.items() for c,vs in cs.items() for v in vs),key=lambda x:x[0])}

Поскольку приведенная выше строка немного Вы можете разложить его, чтобы сохранить разборчивость:

companyPairs    = ((c,v,n) for n,cs in dicTfAllP.items() for c,vs in cs.items() for v in vs)
groupedData     = groupby(sorted(companyPairs),key=lambda x:x[0])
outputCompanies = {c:dict((k,v) for _,k,v in vs) for c,vs in groupedData }

Проверка промежуточных результатов позволит вам увидеть, что происходит.

...