web-dev-qa-db-ja.com

計算する前にいくつかの値を含む組み合わせを削除する

リストと除外要素が与えられた場合、これらの要素を含む組み合わせの計算を無視することは可能ですか?

例1

_l = [1, 2, 3, 4, 5]_が与えられた場合、_size 4_のすべての組み合わせを計算し、計算する前に_(1, 3)_を含む組み合わせを除外したいと思います。

結果は次のようになります。

_    All results:            Wanted results:

    [1, 2, 3, 4]            [1, 2, 4, 5]
    [1, 2, 3, 5]            [2, 3, 4, 5]
    [1, 2, 4, 5]
    [1, 3, 4, 5]
    [2, 3, 4, 5]
_

1およびを含むすべての組み合わせが削除されました。

例2

@EricDuminilによる提案

_l = [1, 2, 3, 4, 5, 6]_、_size 4_、およびの結果

  • 2列目の_(1, 2, 3)_を除く
  • 3列目の_(1, 2)_を除く

    _All results:        Wanted results 1            Wanted results 2
                        (Excluding [1, 2, 3]):      (Excluding [1, 2])
    
    [1, 2, 3, 4]        [1, 2, 4, 5]                [1, 3, 4, 5]
    [1, 2, 3, 5]        [1, 2, 4, 6]                [1, 3, 4, 6]
    [1, 2, 3, 6]        [1, 2, 5, 6]                [1, 3, 5, 6]
    [1, 2, 4, 5]        [1, 3, 4, 5]                [1, 4, 5, 6]
    [1, 2, 4, 6]        [1, 3, 4, 6]                [2, 3, 4, 5]
    [1, 2, 5, 6]        [1, 3, 5, 6]                [2, 3, 4, 6]
    [1, 3, 4, 5]        [1, 4, 5, 6]                [2, 3, 5, 6]
    [1, 3, 4, 6]        [2, 3, 4, 5]                [2, 4, 5, 6]
    [1, 3, 5, 6]        [2, 3, 4, 6]                [3, 4, 5, 6]
    [1, 4, 5, 6]        [2, 3, 5, 6]                                
    [2, 3, 4, 5]        [2, 4, 5, 6]                                
    [2, 3, 4, 6]        [3, 4, 5, 6]                                
    [2, 3, 5, 6]           
    [2, 4, 5, 6]           
    [3, 4, 5, 6]        
    _

1 and 2 andを含むすべての組み合わせが必要な結果から削除されました1

1および2を含むすべての組み合わせは、必要な結果から削除されました2

計算する組み合わせははるかに大きいですが、時間がかかるため、これらの除外を使用してこの時間を短縮したいと思います。

試したソリューション

方法1でも、組み合わせは計算されます

方法2で、 組み合わせ関数 を変更しようとしましたが、計算する前に除外リストを無視する適切な方法を見つけることができませんでした。

_            Method 1                    |               Method 2
                                        |               
def main():                             |   def combinations(iterable, r):
    l = list(range(1, 6))               |       pool = Tuple(iterable)
    comb = combinations(l, 4)           |       n = len(pool)
                                        |       if r > n:
    for i in comb:                      |           return
        if set([1, 3]).issubset(i):     |       indices = list(range(r))
            continue                    |       yield Tuple(pool[i] for i in indices)
        else                            |       while True:
            process()                   |           for i in reversed(range(r)):
                                        |               if indices[i] != i + n - r:
                                        |                   break
                                        |               else:
                                        |                   return
                                        |           indices[i] += 1
                                        |           for j in range(i+1, r):
                                        |               indices[j] = indices[j-1] + 1
                                        |           yield Tuple(pool[i] for i in indices)
_

編集:

まず第一に、あなたの助けに感謝します、私は制約についての詳細を与えるのを忘れました。

  • 出力の順序は関係ありません。たとえば、結果が_[1, 2, 4, 5] [2, 3, 4, 5]_または_[2, 3, 4, 5] [1, 2, 4, 5]_の場合、重要ではありません。

  • 組み合わせの要素は、(可能であれば)_[1, 2, 4, 5] [2, 3, 4, 5]_ではなく_[2, 1, 5, 4] [3, 2, 4, 5]_でソートする必要がありますが、組み合わせは後でソートできるため、重要ではありません。

  • 除外リストは、表示されるべきではない組み合わせで一緒にであるすべてのアイテムのリストです。たとえば、除外リストが_(1, 2, 3)_の場合、1と2とを含むすべての組み合わせを計算しないでください。ただし、ではなく1と2との組み合わせは許可されます。その場合、_(1, 2)_と_(1, 2, 3)_を含む組み合わせを除外すると、_(1, 2, 3)_によってフィルタリングされるすべての組み合わせがすでに_(1, 2)_によってフィルタリングされているため、まったく役に立ちません。

  • 複数の除外リスト組み合わせに複数の制約を使用しているため、可能である必要があります。

テストされた回答

@tobias_kこのソリューションは、除外リスト_(1, 2, 3)_をOR除外と見なします。これは、よく理解していれば_(1, 2), (2, 3) and (1, 3)_が除外されることを意味します。これはケースでは役立ちますが、現在はそうではありません。問題、詳細を説明するために質問を変更しました。混乱して申し訳ありません。あなたの回答では、指定したリスト_(1, 2)_と_(1, 3)_のみを除外として使用することはできません。ただし、これの大きな利点は解決策は、複数の除外を許可することです。

@Kasramvdと@mikuszefskiあなたの解決策は私が望むものに本当に近いです、それが複数の除外リストを含んでいるなら、それは答えでしょう。

ありがとう

18
SyedElec

(私の 前の回答 は質問の制約を実際には満たしていないことが判明したので、ここに別の回答があります。アプローチが大きく異なり、元の回答であるため、これを別の回答として投稿しますそれでも他の人を助けるかもしれません。)

再帰する前に毎回これを再帰的に実装して、組み合わせに別の要素を追加し、それが除外セットの1つに違反するかどうかを確認できます。これは無効な組み合わせを生成せず、重複する除外セット((1,3), (1,5)など)および3つ以上の要素を持つ除外セット((2,4,5)など)で機能し、それらすべてを除くすべての組み合わせを許可します)。

def comb_with_excludes(lst, n, excludes, i=0, taken=()):
    if n == 0:
        yield taken  # no more needed
    Elif i <= len(lst) - n:
        t2 = taken + (lst[i],)  # add current element
        if not any(e.issubset(t2) for e in excludes):
            yield from comb_with_excludes(lst, n-1, excludes, i+1, t2)
        if i < len(lst) - n:  # skip current element
            yield from comb_with_excludes(lst, n, excludes, i+1, taken)

例:

>>> lst = [1, 2, 3, 4, 5, 6]
>>> excludes = [{1, 3}, {1, 5}, {2, 4, 5}]
>>> list(comb_with_excludes(lst, 4, excludes))
[[1, 2, 4, 6], [2, 3, 4, 6], [2, 3, 5, 6], [3, 4, 5, 6]]

さて、私は今それを計りました、そしてこれはあなたがすでにしているように、フィルター付きのジェネレーター式でitertools.combinationを素朴に使用するよりもかなり遅いことがわかりました:

def comb_naive(lst, r, excludes):
    return (comb for comb in itertools.combinations(lst, r)
                 if not any(e.issubset(comb) for e in excludes))

Pythonでの組み合わせの計算は、ライブラリ(おそらくCで実装されている)を使用して後で結果をフィルタリングするよりも遅いです。除外できる組み合わせの量に応じて、これmightの方が速い場合もありますが、正直なところ疑問があります。

Kasramvdの回答 のように、サブ問題にitertools.combinationsを使用できると、より良い結果が得られる可能性がありますが、複数の非分離除外セットの場合は、より困難です。 1つの方法は、リスト内の要素を2つのセットに分割することです。制約があるものとないものです。次に、両方にitertoolc.combinationsを使用しますが、重要な要素の組み合わせについてのみ制約を確認します。結果を確認してフィルタリングする必要がありますが、その一部にすぎません。 (ただし、1つの注意点:結果は順番に生成されず、生成された組み合わせ内の要素の順序も多少混乱します。)

def comb_with_excludes2(lst, n, excludes):
    wout_const = [x for x in lst if not any(x in e for e in excludes)]
    with_const = [x for x in lst if     any(x in e for e in excludes)]
    k_min, k_max = max(0, n - len(wout_const)), min(n, len(with_const))
    return (c1 + c2 for k in range(k_min, k_max)
                    for c1 in itertools.combinations(with_const, k)
                    if not any(e.issubset(c1) for e in excludes)
                    for c2 in itertools.combinations(wout_const, n - k))

これは、再帰的な純粋なPythonソリューションよりもはるかに優れていますが、上記の例の「ナイーブ」アプローチほど優れていません。

>>> lst = [1, 2, 3, 4, 5, 6]
>>> excludes = [{1, 3}, {1, 5}, {2, 4, 5}]
>>> %timeit list(comb_with_excludes(lst, 4, excludes))
10000 loops, best of 3: 42.3 µs per loop
>>> %timeit list(comb_with_excludes2(lst, 4, excludes))
10000 loops, best of 3: 22.6 µs per loop
>>> %timeit list(comb_naive(lst, 4, excludes))
10000 loops, best of 3: 16.4 µs per loop

ただし、結果は入力に大きく依存します。制約がこれらの要素のいくつかにのみ適用される、より大きなリストの場合、このアプローチは実際には単純なアプローチよりも高速です。

>>> lst = list(range(20))
>>> %timeit list(comb_with_excludes(lst, 4, excludes))
10 loops, best of 3: 15.1 ms per loop
>>> %timeit list(comb_with_excludes2(lst, 4, excludes))
1000 loops, best of 3: 558 µs per loop
>>> %timeit list(comb_naive(lst, 4, excludes))
100 loops, best of 3: 5.9 ms per loop
1
tobias_k

(これはOPが望んでいることを正確に実行しないことが判明しました。他の人を助けるかもしれないので、これをここに残しておきます。)


相互に排他的な要素を含めるには、それらをリスト内のリストにラップし、それらの combinations を取得してから、 product サブリストの組み合わせの:

>>> from itertools import combinations, product
>>> l = [[1, 3], [2], [4], [5]]
>>> [c for c in combinations(l, 4)]
[([1, 3], [2], [4], [5])]
>>> [p for c in combinations(l, 4) for p in product(*c)]
[(1, 2, 4, 5), (3, 2, 4, 5)]

より複雑な例:

>>> l = [[1, 3], [2, 4, 5], [6], [7]]
>>> [c for c in combinations(l, 3)]
[([1, 3], [2, 4, 5], [6]),
 ([1, 3], [2, 4, 5], [7]),
 ([1, 3], [6], [7]),
 ([2, 4, 5], [6], [7])]
>>> [p for c in combinations(l, 3) for p in product(*c)]
[(1, 2, 6),
 (1, 4, 6),
 ... 13 more ...
 (4, 6, 7),
 (5, 6, 7)]

これにより、後で除外される「ジャンク」の組み合わせは生成されません。ただし、各「排他的」グループから最大で1つの要素が必要であると想定しています。 2番目の例では、2,4,5との組み合わせだけでなく、2,44,5、または2,5との組み合わせも防止します。また、1,31,5のいずれかを排他的に持つことはできません(または少なくとも簡単ではありません)が、3,5は許可します。 (これらのケースに拡張することは可能かもしれませんが、それがどのように行われるかはまだわかりません。)


これを関数でラップして、(推定)形式とはわずかに異なる入力形式を導き出し、一致するジェネレーター式を返すことができます。ここで、lstは要素のリスト、rは組み合わせごとのアイテム数、exclude_groupsは相互に排他的な要素のグループのリストです。

from itertools import combinations, product

def comb_with_excludes(lst, r, exclude_groups):
    ex_set = {e for es in exclude_groups for e in es}
    tmp = exclude_groups + [[x] for x in lst if x not in ex_set]
    return (p for c in combinations(tmp, r) for p in product(*c))

lst = [1, 2, 3, 4, 5, 6, 7]
excludes = [[1, 3], [2, 4, 5]]
for x in comb_with_excludes(lst, 3, excludes):
    print(x)
5
tobias_k

アルゴリズムの観点から、有効なアイテムの除外とリセットを分離し、各セットの組み合わせを個別に計算して、必要な長さに基づいて結果を連結することができます。このアプローチでは、除外されたすべてのアイテムを一度に組み合わせて含めることは完全に拒否されますが、実際の注文は省略されます。

from itertools import combinations

def comb_with_exclude(iterable, comb_num, excludes):
    iterable = Tuple(iterable)
    ex_len = len(excludes)
    n = len(iterable)

    if comb_num < ex_len or comb_num > n:
        yield from combinations(iterable, comb_num)

    else:
        rest = [i for i in iterable if not i in excludes]
        ex_comb_rang = range(0, ex_len)
        rest_comb_range = range(comb_num, comb_num - ex_len, -1)
        # sum of these pairs is equal to the comb_num
        pairs = Zip(ex_comb_rang, rest_comb_range)

        for i, j in pairs:
            for p in combinations(excludes, i):
                for k in combinations(rest, j):
                    yield k + p
       """
       Note that instead of those nested loops you could wrap the combinations within a product function like following:
       for p, k in product(combinations(excludes, i), combinations(rest, j)):
            yield k + p
       """

デモ:

l = [1, 2, 3, 4, 5, 6, 7, 8]
ex = [2, 5, 6]
print(list(comb_with_exclude(l, 6, ex)))

[(1, 3, 4, 7, 8, 2), (1, 3, 4, 7, 8, 5), (1, 3, 4, 7, 8, 6), (1, 3, 4, 7, 2, 5), (1, 3, 4, 8, 2, 5), (1, 3, 7, 8, 2, 5), (1, 4, 7, 8, 2, 5), (3, 4, 7, 8, 2, 5), (1, 3, 4, 7, 2, 6), (1, 3, 4, 8, 2, 6), (1, 3, 7, 8, 2, 6), (1, 4, 7, 8, 2, 6), (3, 4, 7, 8, 2, 6), (1, 3, 4, 7, 5, 6), (1, 3, 4, 8, 5, 6), (1, 3, 7, 8, 5, 6), (1, 4, 7, 8, 5, 6), (3, 4, 7, 8, 5, 6)]

l = [1, 2, 3, 4, 5]
ex = [1, 3]
print(list(comb_with_exclude(l, 4, ex)))

[(2, 4, 5, 1), (2, 4, 5, 3)]

他の答えとのベンチマーク:

結果:このアプローチは他のアプローチよりも高速です

# this answer
In [169]: %timeit list(comb_with_exclude(lst, 3, excludes[0]))
100000 loops, best of 3: 6.47 µs per loop

# tobias_k
In [158]: %timeit list(comb_with_excludes(lst, 3, excludes))
100000 loops, best of 3: 13.1 µs per loop

# Vikas Damodar
In [166]: %timeit list(combinations_exc(lst, 3))
10000 loops, best of 3: 148 µs per loop

# mikuszefski
In [168]: %timeit list(sub_without(lst, 3, excludes[0]))
100000 loops, best of 3: 12.52 µs per loop
4
Kasramvd

私はあなたの要件に従って組み合わせを編集しようとしました:

def combinations(iterable, r):
   # combinations('ABCD', 2) --> AB AC AD BC BD CD
   # combinations(range(4), 3) --> 012 013 023 123
   pool = Tuple(iterable)
   n = len(pool)
   if r > n:
      return
   indices = list(range(r))
   # yield Tuple(pool[i] for i in indices)
   while True:
       for i in reversed(range(r)):
           if indices[i] != i + n - r:
               break
    else:
        return
    indices[i] += 1
    for j in range(i+1, r):
        indices[j] = indices[j-1] + 1
    # print(Tuple(pool[i] for i in indices ), "hai")
    if 1 in Tuple(pool[i] for i in indices ) and 3  in Tuple(pool[i] for i in indices ):
        pass
    else:
        yield Tuple(pool[i] for i in indices)


d = combinations(list(range(1, 6)),4)
for i in d:
   print(i)

次のようなものが返されます:

(1、2、4、5)(2、3、4、5)

1
Vikas P

2番目のループ時間を節約するために、次のコードを使用して組み合わせ中に除外を行いました。除外された要素のインデックスをセットとして渡す必要があります。

更新作業フィドル

from itertools import permutations

def combinations(iterable, r, combIndeciesExclusions=set()):
    pool = Tuple(iterable)
    n = len(pool)
    for indices in permutations(range(n), r):
        if ( len(combIndeciesExclusions)==0 or not combIndeciesExclusions.issubset(indices)) and sorted(indices) == list(indices):
            yield Tuple(pool[i] for i in indices)


l = list(range(1, 6))
comb = combinations(l, 4, set([0,2]))
print list(comb)
1
CME64

私の答えはここにある他のいくつかと似ていると思いますが、これは私が並行していじったものでした

from itertools import combinations, product

"""
with help from
https://stackoverflow.com/questions/374626/how-can-i-find-all-the-subsets-of-a-set-with-exactly-n-elements
https://stackoverflow.com/questions/32438350/python-merging-two-lists-with-all-possible-permutations
https://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
"""
def sub_without( S, m, forbidden ):
    out = []
    allowed = [ s for s in S if s not in forbidden ]
    N = len( allowed )
    for k in range( len( forbidden ) ):
        addon = [ list( x ) for x in combinations( forbidden, k) ]
        if N + k >= m:
            base = [ list( x ) for x in combinations( allowed, m - k ) ]
            leveltotal = [ [ item for sublist in x for item in sublist ] for x in product( base, addon ) ]
            out += leveltotal
    return out

val = sub_without( range(6), 4, [ 1, 3, 5 ] )

for x in val:
    print sorted(x)

>>
[0, 1, 2, 4]
[0, 2, 3, 4]
[0, 2, 4, 5]
[0, 1, 2, 3]
[0, 1, 2, 5]
[0, 2, 3, 5]
[0, 1, 3, 4]
[0, 1, 4, 5]
[0, 3, 4, 5]
[1, 2, 3, 4]
[1, 2, 4, 5]
[2, 3, 4, 5]
0
mikuszefski

アルゴリズム的に、除外されたアイテムに含まれないリスト内のアイテムの組み合わせを計算してから、除外されたアイテムのそれぞれの組み合わせを残りのアイテムの組み合わせに追加する必要があります。もちろん、このアプローチには多くのチェックが必要であり、インデックスを追跡する必要があります。これをpythonで実行しても、パフォーマンスに顕著な違いはありません(の欠点として知られています)。 制約充足問題 )。(combinationを使用してそれらを計算し、不要なアイテムを除外するのではなく)。

そのため、ほとんどの場合、これが最善の方法だと思います。

In [77]: from itertools import combinations, filterfalse

In [78]: list(filterfalse({1, 3}.issubset, combinations(l, 4)))
Out[78]: [(1, 2, 4, 5), (2, 3, 4, 5)]
0
Kasramvd