2014-12-17 3 views
2

У меня есть словарь, который имеет n ключей, каждая клавиша содержит список из n строк.Создание n вложенных циклов из словаря списков

Я хочу перебирать все комбинации строк, давая ключ и связанную с ним строку.

Это пример словаря с 3 ключами, но я хочу обобщить на словарь с n ключами.

dict = {'key1':['str0','str1','str2','strn'],'key2':['apples','bananas','mangos'],'key3':['spam','eggs','more spam']} 

for str1 in dict['key1']: 
    for str2 in dict['key2']: 
     for str3 in dict['key3']: 
      print 'key1'+"."+str1,'key2'+"."+str2,'key3'+"."+str3 

Пожалуйста, при ответе на вопрос могли бы вы описать, как ответ работает как я достаточно новое для питона и не знаю, что все инструменты, доступные еще!

Ожидаемый результат:

key1.str0 key2.apples key3.spam 
key1.str0 key2.apples key3.eggs 
key1.str0 key2.apples key3.more spam 
key1.str0 key2.bananas key3.spam 
key1.str0 key2.bananas key3.eggs 
... 

Ожидаемый результат для п-мерного итератора:

key1.str0 key2.apples key3.spam ... keyn.string0 
key1.str0 key2.apples key3.spam ... keyn.string1 
key1.str0 key2.apples key3.spam ... keyn.string2 
... 
key1.str0 key2.apples key3.spam ... keyn.stringn 
... 
+2

Можете ли вы включить ожидаемый выход? – thefourtheye

+1

Он хочет, чтобы он был обобщен с n ключами – MattG

ответ

3

Вы должны использовать itertools.product, который выполняет Cartesian product, что имя, что вы пытаетесь делать.

from itertools import product 

# don't shadow the built-in name `dict` 
d = {'key1': ['str0','str1','str2','strn'], 'key2': ['apples','bananas','mangos'], 'key3': ['spam','eggs','more spam']} 

# dicts aren't sorted, but you seem to want key1 -> key2 -> key3 order, so 
# let's get a sorted copy of the keys 
keys = sorted(d.keys()) 
# and then get the values in the same order 
values = [d[key] for key in keys] 

# perform the Cartesian product 
# product(*values) means product(values[0], values[1], values[2], ...) 
results = product(*values) 

# each `result` is something like ('str0', 'apples', 'spam') 
for result in results: 
    # pair up each entry in `result` with its corresponding key 
    # So, zip(('key1', 'key2', 'key3'), ('str0', 'apples', 'spam')) 
    # yields (('key1', 'str0'), ('key2', 'apples'), ('key3', 'spam')) 
    # schematically speaking, anyway 
    for key, thing in zip(keys, result): 
     print key + '.' + thing, 
    print 

Обратите внимание: нигде мы не зафиксировали количество ключей в словаре. Вы можете избежать сортировки, если используете collections.OrderedDict вместо dict.


Вот еще один вариант, если вы хотите, ключи, прикрепленные к их значениям:

from itertools import product 

d = {'key1': ['str0','str1','str2','strn'], 'key2': ['apples','bananas','mangos'], 'key3': ['spam','eggs','more spam']} 

foo = [[(key, value) for value in d[key]] for key in sorted(d.keys())] 

results = product(*foo) 
for result in results: 
    for key, value in result: 
     print key + '.' + value, 
    print 

Здесь мы построим список списков (ключ, значение) кортежи, а затем применить декартово произведение к спискам. Таким образом, связь между ключами и значениями полностью содержится в results. Подумайте об этом, возможно, это лучше, чем первый способ, который я опубликовал.

+0

Мне нравится это решение, но есть ли способ сделать его более элегантным, поскольку в настоящее время ключи должны оставаться в том же порядке между 'results = product (* values)' и циклом for. Есть ли вообще ссылка «ключи» и «результат», кроме как «zip (ключи, результат)» –

+0

@alexmcf См. Мое редактирование (под строкой). Да - вы можете взять продукт по спискам (ключ, значение), а не спискам значений. Вероятно, это лучше, чем я изначально опубликовал. – senshin

+0

просто записка для любого, кто использует это в будущем ...Он будет терпеть неудачу, если у вас есть какие-то пустые списки в dict –

1

Вы можете использовать рекурсивную функцию:

# make a list of all the keys 
keys = list(dict.keys()) 

def f(keys, dict, depth=0, to_print=[]): 
    if depth < len(keys): 
     for item in dict[keys[depth]]: 
      to_print.append(keys[depth] + '.' + item + ' ') 
      f(keys, dict, depth + 1, to_print) 
      del to_print[-1] 
    else: 
     # you can format the output as you wish; here i'm just printing the list 
     print to_print 


# call the function 
f(keys, dict) 
+0

Спасибо, это решает проблему, но я не просто хотел распечатать длинный список, я хотел сделать другие вещи с переменными, так что итератор, спрятанный в функции, делает его менее переносимым решением. –

+0

Хорошо. Вы все еще можете делать другие вещи с переменными в предложении else. – Valdrinit

1

Я собирался для более функционального стиля, это в основном так же, как @ ответ Senshin в:

import itertools 
from pprint import pprint 
def foo(d): 
    def g(item): 
     """create and return key.value1, key.value2, ..., key.valueN iterator 

     item is a (key, value) dictionary item, assumes value is a sequence/iterator 
     """ 
     # associate the key with each value - (key, value0) ... (key, valueN) 
     kay_vees = itertools.izip(itertools.repeat(item[0]), item[1]) 
     return itertools.imap('.'.join, kay_vees) 

    # generator that produces n data sets from the dict 
    a = itertools.imap(g, d.iteritems()) 

    # cartesian product of the datasets 
    return itertools.product(*a) 

Использование:

c = list(foo(d)) 
pprint(c) 

for thing in foo(d): 
    print thing 

После использования, итераторы должны быть переопределены - foo вернет новый итератор для каждого вызова.

Смежные вопросы