Декартово произведение для двух словарей python

Итак, у меня есть два словаря.

dictionary_1 = {'status': ['online', 'Away', 'Offline'],
                'Absent':['yes', 'no', 'half day']}
dictionary_2 = {'healthy': ['yes', 'no'],
                'insane': ['yes', 'no']

Теперь мне нужно объединить их, чтобы получить новый словарь с:

{'status': ['online', 'online', 'away', 'away', 'Offline', 'Offline'],
 'Absent': ['yes', 'yes', 'no', 'no', 'half day', 'half day'],
 'healthy': ['yes', 'no', 'yes', 'no', 'yes', 'no'],
 'insane': ['yes', 'no', 'yes', 'no', 'yes', 'no']
}

Это обновление очень запоздалое, но я нашел способ сделать это без itertools, если кому-то интересно.

def cartesian_product(dict1, dict2):
    cartesian_dict = {}
    dict1_length = len(list(dict1.values())[0])
    dict2_length = len(list(dict2.values())[0])
    h = []
    for key in dict1:
        for value in dict1[key]:
            if not key in cartesian_dict:
                cartesian_dict[key] = []
                cartesian_dict[key].extend([value]*dict2_length)
            else:   
                cartesian_dict[key].extend([value]*dict2_length)
    for key in dict2:
        cartesian_dict[key] = dict2[key]*dict1_length
    return cartesian_dict

person user2989280    schedule 13.11.2013    source источник
comment
Ваши первые два сглаживают li, заархивированные сами с собой, ваши вторые два объединяют 3 копии li. Какой ты хочешь?   -  person roippi    schedule 13.11.2013
comment
Как получить три копии 'online'? Вы пытаетесь получить копию каждого status для каждого члена zip(healthy, insane), копию каждого Absent для каждого члена zip(healthy, insane), копию каждого healthy для каждого члена zip(status, Absent) и копию каждого insane для каждого члена zip(status, Absent)?   -  person abarnert    schedule 13.11.2013
comment
Пожалуйста, объясните свой декартовый продукт, ожидаемый результат не так очевиден.   -  person Ashwini Chaudhary    schedule 13.11.2013
comment
мне нужно, чтобы первый словарь повторял первое значение количество значений во втором словаре... каждый словарь будет иметь определенное количество ключей и определенное количество значений для каждого ключа. поэтому мне нужно, чтобы значения ключей в первом словаре повторялись столько раз, сколько есть значений для ключей во втором словаре.   -  person user2989280    schedule 13.11.2013
comment
Ваше редактирование делает это еще более запутанным. Куда делись «частично» и «восстановление» и как они волшебным образом оказались в выводе?   -  person roippi    schedule 13.11.2013
comment
Возможно, если вы сможете объяснить, почему вам это нужно, вам будет проще объяснить, как это сделать.   -  person abarnert    schedule 14.11.2013
comment
каждый словарь будет иметь определенное количество ключей и определенное количество значений для каждого ключа. У вашего текущего нет: есть два с 6 и два с 4.   -  person DSM    schedule 14.11.2013


Ответы (5)


Лучшее предположение, основанное на интерпретации @abarnert (и при условии, что значения healthy и insane в текущем выводе неверны, поскольку они имеют только четыре члена):

d1 = {'status': ['online', 'Away', 'Offline'] ,'absent':['yes', 'no', 'half day']}
d2 = {'healthy': ['yes', 'no'], 'insane': ['yes', 'no']}
d1_columns = zip(*d1.values())
d2_columns = zip(*d2.values())
col_groups = [c1+c2 for c1, c2 in itertools.product(d1_columns, d2_columns)]
rows = zip(*col_groups)
combined_keys = list(d1) + list(d2)
d_combined = dict(zip(combined_keys, rows))

который производит

>>> pprint.pprint(d_combined)
{'absent': ('yes', 'yes', 'no', 'no', 'half day', 'half day'),
 'healthy': ('yes', 'no', 'yes', 'no', 'yes', 'no'),
 'insane': ('yes', 'no', 'yes', 'no', 'yes', 'no'),
 'status': ('online', 'online', 'Away', 'Away', 'Offline', 'Offline')}

или, по вашему заказу,

>>> order = ["status", "absent", "healthy", "insane"]
>>> for k in order:
    print k, d_combined[k]
...     
status ('online', 'online', 'Away', 'Away', 'Offline', 'Offline')
absent ('yes', 'yes', 'no', 'no', 'half day', 'half day')
healthy ('yes', 'no', 'yes', 'no', 'yes', 'no')
insane ('yes', 'no', 'yes', 'no', 'yes', 'no')
person DSM    schedule 13.11.2013
comment
Хороший перевод на Python. В Python такой же беспорядок, как и в английском, но это не ваша вина… - person abarnert; 14.11.2013
comment
не понятно, каков ожидаемый результат, если d1.viewkeys() & d2.viewkeys() не пусто, т.е. есть общие ключи. - person jfs; 14.11.2013
comment
@ J.F.Sebastian: хорошая мысль. Вышеизложенное будет иметь смысл только в том случае, если они не пересекаются. - person DSM; 14.11.2013
comment
Сработало как шарм... Большое спасибо - person user2989280; 14.11.2013
comment
на самом деле мне нужно, чтобы значения были списком, а не кортежем. - person user2989280; 14.11.2013
comment
@user2989280 user2989280: так что сделай их списками. rows = [list(row) for row in rows], например. - person DSM; 14.11.2013

попробуйте это: он объединяет два значения dict, делает продукт, а затем повторно разделяет их, чтобы превратить в dict.

import itertools

dictionary_1 = {'status': ['online', 'Away', 'Offline'],
                'Absent':['yes', 'no', 'half day']}
dictionary_2 = {'healthy': ['yes', 'no', 'recovering'],
                'insane': ['yes', 'no', 'partially' ]}

keys = dictionary_1.keys() + dictionary_2.keys()

first_values = zip(*dictionary_1.values())
# [('online','yes'), ('Away','no'),('Offline','half day')]

second_values = zip(*dictionary_2.values())

# this product will replicate the first_values 
# as many times as second_values exists
values_list = [i1+i2 for(i1,i2) in itertools.product(first_values,second_values)]

#re-separate the value lists for dict.
values = zip(*values_list)

new_dict = {key:list(values[i]) for i,key in enumerate(keys)}  
person Garth5689    schedule 13.11.2013
comment
Хе. Я предполагаю, что есть один очевидный способ сделать это, даже в тех случаях, когда вообще не очевидно, что это такое. :^) - person DSM; 14.11.2013
comment
Да, мы, кажется, прибыли в одно и то же место! это довольно интересно, почти строка за строкой такая же, за исключением конструкции dict. не копирую, клянусь! :) - person Garth5689; 14.11.2013

Я столкнулся с этой проблемой некоторое время назад, делая тестовые примеры. У меня есть пакет на pip, который теперь называется «looper», который расширяет itertools с некоторой магией словаря и другими вещами, которые я нашел полезными.

https://pypi.python.org/pypi/looper

То, что вы хотите, не похоже на полное декартово произведение двух словарей, которое будет состоять из 36 элементов, объединяющих каждый ключ d1 [k1] * d1 [k2] * d2 [k1] * d2 [k2].

Вместо этого вы, кажется, хотите d1[k1,k2] * d2[k1,k2], равномерно перебирая n для каждого ключа. Это известно как функция zip, и dict_zip делает это для словарей.

from pprint import pprint
from looper import iterutil

dict_1 = {'status':  ['online', 'Away', 'Offline'],
          'Absent':  ['yes', 'no', 'half day']}
dict_2 = {'healthy': ['yes', 'no'],
          'insane':  ['yes', 'no']}

# the first thing to do is to zip the dictionaries up. This produces a dictionary for each value of n in d[k][n]
zipped_dict_1 = iterutil.dict_zip(**dict_1)
# {'Absent': 'yes', 'status': 'online'}
# {'Absent': 'no', 'status': 'Away'}
# {'Absent': 'half day', 'status': 'Offline'}
zipped_dict_2 = iterutil.dict_zip(**dict_2)
# {'healthy': 'yes', 'insane': 'yes'}
# {'healthy': 'no', 'insane': 'no'}


# Now the output is a list of flattened dictionaries, take the Cartesian product of them.
product_dict = iterutil.product(zipped_dict_1,zipped_dict_2) 
# ({'Absent': 'yes', 'status': 'online'}, {'healthy': 'yes', 'insane': 'yes'})
# ({'Absent': 'yes', 'status': 'online'}, {'healthy': 'no', 'insane': 'no'})
# ({'Absent': 'no', 'status': 'Away'}, {'healthy': 'yes', 'insane': 'yes'})
# ({'Absent': 'no', 'status': 'Away'}, {'healthy': 'no', 'insane': 'no'})
# ({'Absent': 'half day', 'status': 'Offline'}, {'healthy': 'yes', 'insane': 'yes'})
# ({'Absent': 'half day', 'status': 'Offline'}, {'healthy': 'no', 'insane': 'no'})

# The product function produces tuples which must be combined in to a final dictionary.
# Merge the dictionaries using imap
merged_dict =  iterutil.imap(lambda x: dict(x[0].items()+x[1].items()),product_dict)

for d in merged_dict:
    pprint(d)

ВЫХОД

{'Absent': 'yes', 'healthy': 'yes', 'insane': 'yes', 'status': 'online'}
{'Absent': 'yes', 'healthy': 'no', 'insane': 'no', 'status': 'online'}
{'Absent': 'no', 'healthy': 'yes', 'insane': 'yes', 'status': 'Away'}
{'Absent': 'no', 'healthy': 'no', 'insane': 'no', 'status': 'Away'}
{'Absent': 'half day', 'healthy': 'yes', 'insane': 'yes', 'status': 'Offline'}
{'Absent': 'half day', 'healthy': 'no', 'insane': 'no', 'status': 'Offline'}
person Syncryptic    schedule 03.04.2014

мне нужно, чтобы первый словарь повторял первое значение количество раз значений во втором словаре

Итак, вы хотите произвести каждое значение в первом словаре с помощью zip значений во втором словаре, и наоборот.

Для этого вам нужно разархивировать значения двух словарей, произвести результат, разархивировать его, заархивировать каждую половину полученного 2-кортежа с ключами из соответствующего исходного словаря, сгладить две результирующие итерации пар ключ-значение. в один и сделать из него словарь. (Вместо этого вы могли бы сгладить значения и заархивировать их в сглаженные ключи из двух словарей, но я не уверен, что это гарантирует правильный порядок…)

Звучит как большой беспорядок, но это то, о чем вы просите.

person abarnert    schedule 13.11.2013

Поместите свои словари в массив, затем сделайте что-то вроде этого:

dictionaries[dict_1,dict_2]

product = {}
arr = []
for d in dictionaries:
    for k in dictionaries[d]:
        arr.append(d.get(k))
        product[k] = None
for k in product:
    product[k] = arr
person Ethan    schedule 13.11.2013