5

サイズ N の集合 S から多くの M サイズのサブセットを生成する効率的な方法を探しています。

理想的には、これらすべてを生成したいのですが、他の計算に使用しているため、これは実行不可能になります。

代わりに、選択された K 個のサブセットが K 個のサブセット間のすべてのペアワイズ交差のサイズの合計を最小化するように、S の K 個の異なるサブセットを生成したいと考えています。

言い換えれば、K個のサブセットがあり、それらすべてのサブセットのペアごとの交差を取る. そして、これらすべてのセットのサイズを合計します。私はできる限り低い数値を取得します。

基本的には、これらのサブセットをできるだけ「遠く」に配置したいと考えています。どうやってこれをやろうか考えてみましたが、空白を描いています。

それまでの間それをシミュレートするために、私はこの関数を書きました

        def subset_split(full_set, M, K):
            np.random.seed(0) # repeatibility
            seen = set([])
            subset_list = []
            for kx in xrange(K):
                np.random.shuffle(full_set)
                failsafe = 0
                while True: 
                    np.random.shuffle(full_set)
                    subset = tuple(full_set[0:M])
                    if not subset in seen: 
                        seen.add(subset)
                        subset_list.append(subset)
                        break
                    failsafe += 1
                    if failsafe > 100:
                        break
            return subset_list

これは、これまでに見られなかった K 個のランダムなサブセットを生成するだけです。しかし、これはまさに私が望んでいるものではありません。なぜなら、これらの K 個のサブセットを反復可能にし、必要がない場合に誤ってそれぞれに近づけないようにしたいからです。

4

2 に答える 2

7

まあ、私はまだこれを楽しんでいます;-)

これが中断したところで、最小化しようとしていることが明らかになりました。

sum(n*(n-1)//2 for n in index2count.values())

すべての値が同じ場合 (可能な場合)、または 2 つの異なる値が 1 つ離れている場合 (if のようにlen(full_set) = 10、7 つの 3 と 3 つの 4) は最小限です。計算をまったく行わないジェネレーターを作成するには、これで十分index2countです。代わりhiに、2 つの値のうち大きい方のインデックスのセットとlo、残りのインデックス (loすべての (概念的な! 計算されていない) 値が同じ場合は空です)。

Kこれは引数を削除し、重複に対して別のアプローチをとります: 重複を無視します。ここで重複を追跡するのはコストがかかり、不器用です。重複は、「ランダムっぽい」ジェネレーターで実際に予想されるはずです。それが気になる場合は、別のルーチンでラップして重複を取り除きます。

出力例:

>>> from itertools import islice
>>> for s in islice(gen_subsets_special(set(range(5)), 1), 10):
...    print s
set([4])
set([3])
set([0])
set([1])
set([2])
set([0])
set([3])
set([1])
set([2])
set([4])

>>> for s in islice(gen_subsets_special(set(range(10, 20)), 3), 10):
...    print s
set([17, 18, 10])
set([11, 12, 14])
set([16, 19, 13])
set([12, 13, 15])
set([17, 10, 11])
set([16, 19, 15])
set([17, 18, 14])
set([16, 18, 13])
set([19, 12, 15])
set([10, 11, 14])

コードは次のとおりです。

def gen_subsets_special(full_set, M, seed=123456):
    # generate randomish M-subsets of full_set, "far apart".
    import random
    from random import sample
    random.seed(seed)
    elements = list(full_set)
    N = len(elements)
    hi = set(range(N))
    lo = set()
    while True:
        assert not (hi & lo)
        assert len(lo | hi) == N
        # First take indices from hi, then (if needed) from lo.
        if len(hi) > M:
            # We can take all of them from hi, with some left over.
            ixhi = set(sample(hi, M))
            ixlo = set()
            # The ixhi counts go down by 1, so move 'em to lo
            hi -= ixhi
            lo |= ixhi
            assert hi
        else:
            # We need to take everything in hi.
            ixhi = hi.copy()
            ixlo = set(sample(lo, M - len(ixhi)))
            hi |= lo - ixlo
            lo = ixlo
        assert not (ixlo & ixhi)
        ix = ixlo | ixhi
        assert len(ix) == M
        yield set(elements[i] for i in ix)

構築により、生成されたシーケンスのすべてのプレフィックスは、プレフィックス内のセットのすべてのペアの交差のサイズの合計を最小化します。または、今の私にはそう思われます;-)

最後に、より明らかに、すべてのインデックスを繰り返し循環するバリエーションです。これはおそらく私が実際に使用するものです:

def gen_subsets_special(full_set, M, seed=123456):
    # generate randomish M-subsets of full_set, "far apart".
    import random
    from random import sample
    random.seed(seed)
    elements = list(full_set)
    allix = set(range(len(elements)))
    takefrom = allix.copy()

    def destructive_sample(n):
        # Remove a random n-subset from takefrom, & return it.
        s = set(sample(takefrom, n))
        takefrom.difference_update(s)
        return s

    while True:
        if len(takefrom) >= M:
            # Get everything from takefrom.
            ix = destructive_sample(M)
        else:
            # We need to take all of takefrom, and more.
            ix = takefrom
            takefrom = allix - ix
            ix |= destructive_sample(M - len(ix))
        assert len(ix) == M
        yield set(elements[i] for i in ix)
于 2013-09-12T02:12:23.067 に答える
4

これは難しい問題だと思います。これは実用的なアプローチです。 の各要素が返された回数を追跡full_setし、「最も人気のない」(これまでのところ) 要素を使用して次のサブセットを生成しようとします。たとえば、以下のコードは以下を生成します。

>>> for s in gen_subsets_special(set(range(5)), 1, 20):
>>>    print s
set([0])
set([1])
set([2])
set([3])
set([4])

したがって、5 つの可能性のそれぞれを正確に 1 回生成し、20 個の可能性を生成する代わりに、終了します (他に可能性がないことを認識しています)。このコードは、K > N-choose-M の場合に例外を発生させるように簡単に変更できます。

より興味深い例:

>>> for s in gen_subsets_special(set(range(10, 20)), 3, 10):
>>>     print s
set([10, 11, 12])
set([13, 14, 15])
set([16, 17, 18])
set([11, 10, 19])
set([12, 13, 14])
set([16, 17, 15])
set([18, 19, 10])
set([11, 12, 13])
set([16, 14, 15])
set([17, 18, 19])

したがって、少なくともそれが不可能になるまで、重複しないサブセットを生成します;-)

これがコードです。派手な機能を使用しない、プレーンな Python (2.7.5) です。K を引数として渡さない方が慣用的です。ジェネレーターが生成するアイテムの数は通常、呼び出し元によって制御されます (呼び出し元が完了すると、ジェネレーターの再開が停止するだけです)。

def gen_subsets_special(full_set, M, K):
    # generate K M-subsets of full_set, "far apart".
    from itertools import combinations
    elements = list(full_set)
    # index2count[i] = # of returned subsets containing
    # elements[i]
    index2count = dict((i, 0) for i in range(len(elements)))
    seen = set()
    for _ in xrange(K):
        bycount = sorted(index2count, key=index2count.get)
        # the least popular indices are at the start;
        # combinations generates results in lexicographic
        # index order, so will return combinations containing
        # the least popular indices first
        for raw in combinations(bycount, M):
            raw = tuple(sorted(raw)) # normalize
            if raw not in seen:
                break
        else:
            # all M-combinations have already been seen
            return
        seen.add(raw)
        for i in raw:
            index2count[i] += 1
        yield set(elements[i] for i in raw)

生成されたシーケンスが再現可能かどうかは、list(full_set)これを実行するたびに同じリストを返すことに大きく依存することに注意してください。ただし、セットの要素が表示される順序は定義されていません。セット要素が比較をサポートしている場合、次を使用して再現性を得ることができます

    elements = sorted(full_set)

代わりは。

index2count後で: 返されたサブセットのすべての異なるペアの交点のサイズの合計は、ベクトルから簡単に計算できることに注意してください。

sum(n*(n-1)//2 for n in index2count.values())

クリア?特定の要素がサブセットの正確nに出現する場合、要素が n-choose に寄与するように、その要素がそれらの共通部分にあるサブセットの n-choose-2 (= n*(n-1)/2) ペアが存在します。合計数に -2。これが、ここでカウントのバランスをとるように努めることが役立つ理由の、より正式な理由です。

于 2013-09-11T04:16:39.063 に答える