#python #dictionary #for-loop #optimization #numba
#python #словарь #for-цикл #оптимизация #numba
Вопрос:
def formoutput(teams_id, patent_team):
"""
The function to compare team_id and patent_teams to form the default dictionary matching values
:param teams_id: {('3879797-2', '3930281-2'): 1, ('3930282-1', '3930282-2'): 2, ('3930288-1', '3930288-2'): 3, ... }
:param patent_team: {3930281: [[('3879797-2', '3930281-2')]], 3930282: [[('3930282-1', '3930282-2')]], 3930288: [[('3930288-1', '3930288-2')]], ... }
:return: defaultdict(<function formoutput.<locals>.<lambda> at 0x0000022A45228240>, {3930281: defaultdict(<class 'list'>, {'3879797-2': [1], '3930281-2': [1]}), 3930282: defaultdict(<class 'list'>, {'3930282-1': [2], '3930282-2': [2]}), 3930288: defaultdict(<class 'list'>, {'3930288-1': [3], '3930288-2': [3]}), 3930292: defaultdict(<class 'list'>, {'3861607-1': [4], '3861607-2': [4]}), ..}
"""
print("Forming Output")
print("Teams id =", teams_id)
print("Patent_team=", patent_team)
output_dict = defaultdict(lambda: defaultdict(list))
try:
for k,v in teams_id.items():
for a,b in patent_team.items():
for i in b:
if k in i:
for z in k:
output_dict[a][z].append(v)
except Exception as e:
print(e)
return output_dict
У меня есть функция, которой я передаю два аргумента в виде словарей python. Ключи первого словаря встречаются как значения во втором. Мне нужно сравнить, существует ли для каждого ключа из первого словаря значение во втором словаре, затем используйте ключ, значение из первого словаря и ключ из второго словаря, чтобы добавить значение в defaultdict. Пожалуйста, взгляните на приведенный выше код, это поможет лучше понять код.
Множество вложенных циклов делают код очень медленным. У меня более 50 миллионов пар ключ-значение в первом словаре. И более 3 миллионов ключей во втором словаре, каждый ключ содержит в среднем 3 значения.
Вся идея кода заключается в том, чтобы найти все возможные пары изобретателей, которые когда-либо работали над патентом в некоторой комбинации, и они требуются в качестве выходных данных с {patent_id: inventor_team, team_id}. В настоящее время выполнение одного и того же кода занимает часы. Я запустил его для 100 000 пар ключ-значение, и это заняло примерно 2000 секунд, что является большим временем.
Пожалуйста, предоставьте мне наилучший возможный подход для общего решения. Кроме того, каков наилучший способ обработки такого огромного объема данных?
Комментарии:
1. Я не могу не заметить, что в вашем внутреннем
defaultdict(list)
, похоже, никогда не содержится более одной записи в каждомlist
. Вы уверены, что вам вообще нуженlist
там?2. Кроме того, почему
patent_team
значения представляют собой один элемент,list
содержащий одноэлементный,list
содержащий один двухэлементныйtuple
? Может ли внутренний или внешнийlist
содержать более одного элемента? Могут ли внутренние два-tuple
иметь разное количество элементов?3. @ShadowRanger, я изначально пытался создать defaultdict (int), но для ключей, которые еще не существуют, у него нет метода append, поэтому мне нужно было добавить дополнительный код строк, чтобы проверить, существует ли ключ, и если нет, то инициализировать его в список, чтобы избавить себя от всего этого, я преобразовал его в список по умолчанию.
4. @ShadowRanger, да, списки могут содержать более одного элемента в списке, я просто распечатал несколько начальных значений для справки. Это может быть так. »’ 3930857: [[(‘3930857-1’, ‘3930857-2’), (‘3930857-1’, ‘3930857-3’), (‘3930857-2’, ‘3930857-3’)], [(‘3930857-1’, ‘3930857-2’, ‘3930857-3′)]], »’
Ответ №1:
Если перенос списка действительно излишен, и вы игнорируете случаи, когда соответствующего ключа нет в team_ids
, вы можете значительно сократить количество циклов и тестов членства здесь:
def formoutput(teams_id, patent_team):
"""
The function to compare team_id and patent_teams to form the default dictionary matching values
:param teams_id: {('3879797-2', '3930281-2'): 1, ('3930282-1', '3930282-2'): 2, ('3930288-1', '3930288-2'): 3, ... }
:param patent_team: {3930281: [[('3879797-2', '3930281-2')]], 3930282: [[('3930282-1', '3930282-2')]], 3930288: [[('3930288-1', '3930288-2')]], ... }
:return: defaultdict(<function defaultdict.copy>, {3930281: defaultdict(list, {'3879797-2': [1], '3930281-2': [1]}), 3930282: defaultdict(list, {'3930282-1': [2], '3930282-2': [2]}), 3930288: defaultdict(list, {'3930288-1': [3], '3930288-2': [3]}), 3930292: defaultdict(list, {'3861607-1': [4], '3861607-2': [4]}), ..}
...:
"""
print("Forming Output")
print("Teams id =", teams_id)
print("Patent_team=", patent_team)
# I hate lambdas, and as it happens, we don't need'em;
# defaultdict(list).copy is cleaner and faster
output_dict = defaultdict(defaultdict(list).copy)
try:
# [[pvs]] unpacks the superfluous(?) lists wrapping the tuple we care about
for pk, [[pvs]] in patent_team.items():
# Get the value to set once up front
try:
v = teams_id[pvs]
except KeyError:
continue # Don't have a value to set, so move to next
# Perform the first layer of dict lookup once since the key is the same
# each time to avoid cost of repeated lookup
pkdict = output_dict[pk]
for pv in pvs:
pkdict[pv].append(v)
except Exception as e:
print(e)
return output_dict
Я инвертировал циклы, поскольку patent_teams
ключи являются ключами внешнего результата defaultdict
, имеет смысл сначала выполнить цикл patent_teams
, избегая повторных обращений к output_dict
каждому patent_teams
ключу. Это также означает, что вы можете использовать значение из patent_teams
для прямого поиска того, что вам нужно из teams_id
, вместо перебора teams_id
для поиска.
Если list
перенос не является излишним, замените:
for pk, [[pvs]] in patent_team.items():
с:
for pk, pvs_lists in patent_team.items():
for pvs in chain.from_iterable(pvs_lists):
убедитесь, что вы включили импорт from itertools import chain
в верхней части вашего файла.
Комментарии:
1. спасибо за содержательный ответ, я попытался запустить его, но выдал исключение «слишком много значений для распаковки (ожидается 1)». Я пытался это исправить, вы можете мне с этим помочь.
2. @BalvaishwerSalaria: Да, при распаковке предполагались лишние списки. Поскольку кажется, что только один слой
list
является излишним (и это внутренний слой, поэтому вы не можете просто распаковать внешний слой), я добавил альтернативу.3. ваше решение отлично сработало, большое вам спасибо за ваши ответы. Это было отличное понимание, которое увеличило скорость выполнения во много раз. Было здорово изучить все материалы. Еще раз спасибо.
Ответ №2:
Два способа улучшения, посмотрите, какой из них удобен для вас:
Сначала я бы изменил порядок вашего цикла:
for a,b in patent_team.items():
for i in b:
for k in i:
if k in teams_id:
for z in k:
output_dict[a][z].append(teams_id[k])
потому что я бы предположил, что patent_teams
это меньший dict, чем teams_id
, и я могу использовать поиск O (1) для teams_id
, вместо итерации O (n) для каждого элемента.
Второй подход заключается в преобразовании ваших данных перед объединением. Вы можете попробовать преобразовать два словаря в табличную форму и поместить ее в pandas DataFrame или даже сохранить их в базе данных (SQLite в этом случае был бы удобен). Преимущество этого заключается в том, что вы, вероятно, выгрузите операцию объединения таблиц / фрейма данных из своего интерпретатора Python. Таким образом, быстрее.
Комментарии:
1. большое вам спасибо за решение, оно было таким простым и в то же время таким сложным. Я попробовал первый, он отлично сработал. Однако, как вы предлагаете следовать второму подходу, если бы вы могли его разъяснить.
2. Зависит от вашей проблемы. Если мне нужно будет делать это снова и снова или будут выполняться какие-то более сложные операции, база данных SQLite или фрейм данных pandas будут удобны. Особенно я могу сохранять их в файл и перемещать. Также гораздо проще просмотреть данные, чем обычный dict.