2

条件付き積を生成できるようにしたい。この答えにとても似ています: リストのリストのすべての組み合わせ

使いたかったitertools.product(*listOfLists)。ただし、私の問題は、1つのリストに1つの要素が含まれているということは、製品について他のリストを参照する必要があることを意味します。

例:

colors = ['red', 'blue', 'green']
fruits = ['apple', 'orange', 'banana']
locations = ['indoors', 'outdoors']

indoor_choices = ['bathroom', 'bedroom', 'kitchen']
green_choices = ['forest', 'light', 'dark']

ここでは、色、フルーツ、場所のすべての可能な選択を常に検討したいと思います。ただし、「indoor」の場合は、indoor_choicesも考慮し、「green」の場合は、より具体的な緑の色も選択します。これは、一部のブランチが分岐し続け、他のブランチが分岐しない可能性のツリーのようなものです。

したがって、上記のこのばかげた例では、次のようなforループを実行できます。

for c in colors:
    for f in fruits:
        for l in locations:
            # etc

しかし、2つの異なるカテゴリがこの選択に基づいて分岐する可能性がある場合に何が起こるかという問題に遭遇します。

単純な(ハッキーな)解決策は、条件を手動でコーディングし、その中にforループを配置することです。

for c in colors:
    for f in fruits:
        for l in locations:

            if c == 'green' and l == 'indoor':
                for gc in green_choices:
                     for ic in indoor_choices:
                         # output

            elif c == 'green':
                for gc in green_choices:
                    # output

            elif l == 'indoor':
                for gc in green_choices:
                    # output

            else:
                # output

しかし、M個のリストに追加の分岐があるN個のリストがある場合の恐怖を想像してみてください。さらに悪いことに、ネストされた追加の分岐があります...基本的にこのハックは拡張できません。

何か案は?この問題は、一見難しいことが証明されています。

4

4 に答える 4

6

再帰ジェネレータを使用して、これを行う方法は次のとおりです。

def prod(terms, expansions):
    if not terms: # base case
        yield ()
        return

    t = terms[0] # take the first term

    for v in expansions[t]: # expand the term, to get values
        if v not in expansions: # can the value can be expanded?
            gen = prod(terms[1:], expansions) # if not, we do a basic recursion
        else:
            gen = prod(terms[1:] + [v], expansions) # if so, we add it to terms

        for p in gen: # now we get iterate over the results of the recursive call
            yield (v,) + p # and add our value to the start

これを呼び出して、例で必要な製品を生成する方法は次のとおりです。

expansions = {
        'colors':['red', 'blue', 'green'],
        'fruits':['apple', 'orange', 'banana'],
        'locations':['indoors', 'outdoors'],
        'indoors':['bathroom', 'bedroom', 'kitchen'],
        'green':['forest', 'light', 'dark']
    }

terms = ["colors", "locations"] # fruits omitted, to reduce the number of lines

for p in prod(terms, expansions):
    print(p)

出力:

('red', 'indoors', 'bathroom')
('red', 'indoors', 'bedroom')
('red', 'indoors', 'kitchen')
('red', 'outdoors')
('blue', 'indoors', 'bathroom')
('blue', 'indoors', 'bedroom')
('blue', 'indoors', 'kitchen')
('blue', 'outdoors')
('green', 'indoors', 'forest', 'bathroom')
('green', 'indoors', 'forest', 'bedroom')
('green', 'indoors', 'forest', 'kitchen')
('green', 'indoors', 'light', 'bathroom')
('green', 'indoors', 'light', 'bedroom')
('green', 'indoors', 'light', 'kitchen')
('green', 'indoors', 'dark', 'bathroom')
('green', 'indoors', 'dark', 'bedroom')
('green', 'indoors', 'dark', 'kitchen')
('green', 'outdoors', 'forest')
('green', 'outdoors', 'light')
('green', 'outdoors', 'dark')
于 2013-03-01T04:12:00.153 に答える
1

「余分な」選択肢を事後的に追加できます(Python 3構文)。

def choice_product(choices, *iterables):
    for v in itertools.product(*iterables):
        ks = set(v) & choices.keys()
        if ks:
            choice_iters = [choices[k] for k in ks]
            for p in choice_product(choices, *choice_iters):
                yield v + p
        else:
            yield v

これはitertools.product効率のために使用します。

次のように定義choicesします

choices = {'indoors' : ['bathroom', 'bedroom', 'kitchen'],
           'green': ['forest', 'light', 'dark']}

これは繰り返されます:

>>> for i in choice_product({'c': 'de', 'e': 'fg'}, 'ab', 'cd'):
...     print(i)
... 
('a', 'c', 'd')
('a', 'c', 'e', 'f')
('a', 'c', 'e', 'g')
('a', 'd')
('b', 'c', 'd')
('b', 'c', 'e', 'f')
('b', 'c', 'e', 'g')
('b', 'd')
于 2013-03-01T04:40:50.220 に答える
1

実際の問題が実際に例と同じである場合は、組み合わせを4つの製品に分析できます。

is_green = ['green']
not_green = ['red', 'blue']
is_indoors = ['indoors']
not_indoors = ['outdoors']

p1 = itertools.product([not_green, fruits, not_indoors])
...
p2 = itertools.product([is_green, fruits, not_indoors, green_choices])
...
p3 = itertools.product([not_green, fruits, is_indoors, indoor_choices])
...
p4 = itertools.product([is_green, fruits, is_indoors, green_choices, indoor_choices])

それで全部です!

ここで、4つの「特別な」ケースを作成する必要がないように一般化する場合は、@ DavidRobinsonが提案したように、特定の値とそれらが開く追加の選択肢との関係をキャプチャできます。

import itertools

colors = ['red', 'blue', 'green']
fruits = ['apple', 'orange', 'banana']
locations = ['indoors', 'outdoors']

indoor_choices = ('bathroom', 'bedroom', 'kitchen')
green_choices = ('forest', 'light', 'dark')

choices = [colors, fruits, locations]
more_choices = { 'indoors': indoor_choices, 'green': green_choices }
for p in itertools.product(*choices):
    m = [more_choices[k] for k in p if k in more_choices]
    for r in itertools.product([p],*m):
        print list(r[0]) + list(r[1:])

選択肢やmore_choicesが大きい場合は、必然的に問題が発生することに注意してください。

于 2013-03-01T03:23:01.150 に答える
1

を使用した再帰的な実装を次に示しyieldます。@Blckknghtのソリューションほどきれいではないと思いますが、役立つかもしれません。

colors = ["red","blue","green"]
fruits = ["apple","orange", "banana"]
locations = ["indoors","outdoors"]

green_subtypes = ["forest", "light", "dark"]
indoor_locations = ["bathroom","bedroom","kitchen"]

def gen(state):
  if len(state)==0:
    for c in colors:
       s = [c]
       for y in gen(s):
         yield y
  elif len(state)==1:
    for x in fruits:
      s = state + [x]
      for y in gen(s):
        yield y
  elif len(state)==2:
    for x in locations:
      s = state + [x]
      for y in gen(s):
        yield y
  else:
    # If we're green and we haven't looped through the green options already 
    # (the check is a bit dodgy and could do with being moved into a flag inside state)
    if state[0]=='green' and len(set(state).intersection(set(green_subtypes)))==0:
      for x in green_subtypes:
        s = state + [x]
        for y in gen(s):
          yield y
    # If we're indoors and we haven't looped through the indoor options already 
    # (the check is a bit dodgy and could do with being moved into a flag inside state)
    elif state[2]=='indoors' and len(set(state).intersection(set(indoor_locations)))==0:
      for x in indoor_locations:
        s = state + [x]
        for y in gen(s):
          yield y
    else:
      yield state

for x in gen([]):
  print(x)
于 2013-03-01T04:27:39.407 に答える