12

ある範囲の数値を取得して、重複のないトリプルを含むリストを返すことができるようにしたいと思います。xの各要素は、トリプルの各位置に1回表示される必要があります。目標は、次のようなものを取得することです。

get_combinations_without_duplicates(3) = [(0, 1, 2), (1, 2, 0), (2, 0, 1)]

range(3)の場合、これは単なるリストローテーションですが、より高い範囲の場合、より多くの可能な組み合わせがあります。これらの制約を満たすトリプルのリストをランダムに生成できるようにしたいと思います。

n = 4の場合、各トリプルの最初の要素を指定することから始めます。

[(0、)、(1、)、(2、)、(3、)]

最初のトリプルの2番目の要素は、0以外にすることができます。これらのいずれかを選択すると、次のトリプルのオプションが制限され、以下同様に続きます。目標は、数値を受け取り、この方法でトリプルを作成する関数を持つことですが、常に同じトリプルのセットを作成するとは限りません。つまり、最終結果はローテーションになる可能性があります。

[(0, 1, 2), (1, 2, 3), (2, 3, 0), (3, 0, 1),]

また

[(0, 2, 3), (1, 3, 0), (2, 0, 1), (3, 1, 2)]

この関数の実装は次のとおりです。

def get_combinations_without_duplicates(n):
    output = []
    second = range(n)
     third = range(n)
for i in range(n):
    triple = [i]
    #Get the second value of the triple, but make sure that it isn't a 
    #duplicate of the first value
    #in the triple or any value that has appeared in the second position of any triple
    choices_for_second = [number for number in second if number not in triple]
    #Randomly select a number from the allowed possibilities
    n_second = random.choice(choices_for_second) 
    #Append it to the triple
    triple.append(n_second)
    #Remove that value from second so that it won't be chosen for other triples
    second = [number for number in second if number != n_second]
    #Do the same for the third value
    choices_for_third = [number for number in third if number not in triple]
    n_third = random.choice(choices_for_third)
    triple.append(n_third)
    third = [number for number in third if number != n_third]
    output.append(tuple(triple))
return output

以下で指摘するように、このプロセスでは、機能しない組み合わせがランダムに選択されることがあります。次のようなことをすれば、それを処理できます。

def keep_trying(n):
    try:
        return get_combinations_without_duplicates(n)
    except IndexError:
        return keep_trying(n)

しかし、一般的にこれを行うためのより良い方法があるかどうか疑問に思っています。

4

6 に答える 6

5

これをもう一度試してみましょう。

いくつかの観察。

  1. タプルのソート済み配列では、最初の値は常にゼロになります。
  2. 配列の長さは常に、配列に存在するタプルの数と同じになります。
  3. これらをランダムに生成する必要があります。
  4. タプルは「ソートされた」順序で生成されます。

これらの仕様に基づいて、手続き型の方法を考え出すことができます。

  1. シリアル整数の 2 つのリストを生成します。1 つは選択用、もう 1 つはシード用です。
  2. シード リスト の各数値に対して[0, 1, 2, 3]、要素にまだない数値をランダムに追加および削除します。 [01, 13, 20, 32]
  3. シリアル整数の別のリストを生成し、繰り返します。[012, 130, 203, 321]

しかし、これはうまくいきません。いくつかの反復では、コーナーに戻り、数値を生成できません。例えば、[01, 13, 20, 32].. appending [3, 0, 1... crap, I'm stuck.

これを修正する唯一の方法は、行全体で真のシャッフルを行い、1 つが収まるまで再シャッフルすることです。これにはかなりの時間がかかる場合があり、セットが長くなるにつれて痛みが増すだけです.

したがって、手続き的に言えば:

解決策 1:ランダム生成

  1. リストに範囲を設定します。[0, 1, 2, 3]
  2. 別のリストを作成します。[0, 1, 2, 3]
  3. リストをシャッフルします。[1, 0, 2, 3]
  4. 合うものが見つかるまでシャッフルします... [1, 2, 3, 0]
  5. 3 番目の要素で繰り返します。

この手順では、コンピューターはソリューションを非常に迅速に検証できますが、ソリューションを非常に迅速に生成することはできません。ただし、これは真にランダムな回答を生成する 2 つの方法のうちの 1 つにすぎません。

したがって、最速の保証された方法は、生成手順ではなく、検証手順を利用することです。まず最初に、すべての可能な順列を生成します。

from itertools import permutations

n = 4
candidates = [i for i in permutations(xrange(n),3)]

それで。

解決策 2:ランダム検証

  1. 0 から始まるトリプレットを選択します。
  2. 0 で始まらないトリプレットをランダムにポップします。
  3. ランダムに選択されたトリプレットが中間解であるかどうかを確認します。
  4. そうでない場合は、別のトリプレットをポップします。
  5. はいの場合、トリプレットを追加し、REPOPULATE THE TRIPLET QUEUEを実行します。
  6. n 回繰り返します。# またはキューを使い果たすまで、n 回繰り返すと自然に TRUE になります

次のトリプレットの解は解セットにあることが数学的に保証されているため、それ自体を使い果たした場合、ランダム化された解が表示されるはずです。このアプローチの問題点は、考えられるすべての結果の確率が等しいという保証がないことです

解決策 3:反復検証

等確率の結果を得るには、ランダム化を取り除き、可能なすべての 3 タプルの組み合わせ (n-lists long) を生成し、それらのソリューション候補のそれぞれを検証します。

候補解のリストを検証してすべての解を生成し、そのリストから解をランダムにポップする 関数を作成します。

from itertools import combinations

results = [verify(i) for i in combinations(candidates, n)]
# this is 10626 calls to verify for n=4, 5 million for n=5 
# this is not an acceptable solution.  

ソリューション 1 も 3 も非常に高速ではありません (O(n**2)) が、基準を考えると、真にランダムなソリューションが必要な場合は、これが得られるのと同じくらい高速である可能性があります。解決策 2 は、これら 3 つの中で最速であることが保証され、多くの場合、解決策 1 または 3 を大幅に上回り、解決策 3 が最も安定した結果をもたらします。これらのアプローチのどれを選択するかは、出力で何をしたいかによって異なります。

その後:

最終的に、コードの速度は、コードをどの程度ランダムにするかによって決まります。要件を満たすタプル シリーズの非常に最初の (そして最初の 1 つだけの) インスタンスを吐き出すアルゴリズムは、順列を 1 回順番に攻撃するだけで、O(n) 時間で実行されるため、非常に高速に実行できます。 . ただし、ランダムには何もしません...

また、verify(i) の簡単なコードを次に示します。これは、2 つのタプルが同じインデックスに同じ番号を持たない可能性があるという観察に基づいています。

def verify(t):
    """ Verifies that a set of tuples satisfies the combinations without duplicates condition. """
    zipt = zip(*t)
    return all([len(i) == len(set(i)) for i in zipt])

n = 4 フルソリューションセット

((0, 1, 2), (1, 0, 3), (2, 3, 0), (3, 2, 1))
((0, 1, 2), (1, 0, 3), (2, 3, 1), (3, 2, 0))
((0, 1, 2), (1, 2, 3), (2, 3, 0), (3, 0, 1))
((0, 1, 2), (1, 3, 0), (2, 0, 3), (3, 2, 1))
((0, 1, 3), (1, 0, 2), (2, 3, 0), (3, 2, 1))
((0, 1, 3), (1, 0, 2), (2, 3, 1), (3, 2, 0))
((0, 1, 3), (1, 2, 0), (2, 3, 1), (3, 0, 2))
((0, 1, 3), (1, 3, 2), (2, 0, 1), (3, 2, 0))
((0, 2, 1), (1, 0, 3), (2, 3, 0), (3, 1, 2))
((0, 2, 1), (1, 3, 0), (2, 0, 3), (3, 1, 2))
((0, 2, 1), (1, 3, 0), (2, 1, 3), (3, 0, 2))
((0, 2, 1), (1, 3, 2), (2, 0, 3), (3, 1, 0))
((0, 2, 3), (1, 0, 2), (2, 3, 1), (3, 1, 0))
((0, 2, 3), (1, 3, 0), (2, 0, 1), (3, 1, 2))
((0, 2, 3), (1, 3, 2), (2, 0, 1), (3, 1, 0))
((0, 2, 3), (1, 3, 2), (2, 1, 0), (3, 0, 1))
((0, 3, 1), (1, 0, 2), (2, 1, 3), (3, 2, 0))
((0, 3, 1), (1, 2, 0), (2, 0, 3), (3, 1, 2))
((0, 3, 1), (1, 2, 0), (2, 1, 3), (3, 0, 2))
((0, 3, 1), (1, 2, 3), (2, 1, 0), (3, 0, 2))
((0, 3, 2), (1, 0, 3), (2, 1, 0), (3, 2, 1))
((0, 3, 2), (1, 2, 0), (2, 1, 3), (3, 0, 1))
((0, 3, 2), (1, 2, 3), (2, 0, 1), (3, 1, 0))
((0, 3, 2), (1, 2, 3), (2, 1, 0), (3, 0, 1))

n = 5 には 552 個の一意の解があります。こちらが最初の20件です。

((0, 1, 2), (1, 0, 3), (2, 3, 4), (3, 4, 0), (4, 2, 1))
((0, 1, 2), (1, 0, 3), (2, 3, 4), (3, 4, 1), (4, 2, 0))
((0, 1, 2), (1, 0, 3), (2, 4, 0), (3, 2, 4), (4, 3, 1))
((0, 1, 2), (1, 0, 3), (2, 4, 1), (3, 2, 4), (4, 3, 0))
((0, 1, 2), (1, 0, 4), (2, 3, 0), (3, 4, 1), (4, 2, 3))
((0, 1, 2), (1, 0, 4), (2, 3, 1), (3, 4, 0), (4, 2, 3))
((0, 1, 2), (1, 0, 4), (2, 4, 3), (3, 2, 0), (4, 3, 1))
((0, 1, 2), (1, 0, 4), (2, 4, 3), (3, 2, 1), (4, 3, 0))
((0, 1, 2), (1, 2, 0), (2, 3, 4), (3, 4, 1), (4, 0, 3))
((0, 1, 2), (1, 2, 0), (2, 4, 3), (3, 0, 4), (4, 3, 1))
((0, 1, 2), (1, 2, 3), (2, 0, 4), (3, 4, 0), (4, 3, 1))
((0, 1, 2), (1, 2, 3), (2, 0, 4), (3, 4, 1), (4, 3, 0))
((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 0), (4, 0, 1))
((0, 1, 2), (1, 2, 3), (2, 4, 0), (3, 0, 4), (4, 3, 1))
((0, 1, 2), (1, 2, 3), (2, 4, 1), (3, 0, 4), (4, 3, 0))
((0, 1, 2), (1, 2, 4), (2, 0, 3), (3, 4, 0), (4, 3, 1))
((0, 1, 2), (1, 2, 4), (2, 0, 3), (3, 4, 1), (4, 3, 0))
((0, 1, 2), (1, 2, 4), (2, 3, 0), (3, 4, 1), (4, 0, 3))
((0, 1, 2), (1, 2, 4), (2, 3, 1), (3, 4, 0), (4, 0, 3))
((0, 1, 2), (1, 2, 4), (2, 4, 3), (3, 0, 1), (4, 3, 0))

したがって、このようなソリューションを生成できますが、時間がかかります。これを利用する場合は、生成されたソリューションをそのままキャッシュし、必要なときにランダムに引き出します。ちなみに、n=5 は力ずくで完了するのに 1 分弱かかりました。解は O(n**2) であるため、n=6 で 1 時間以上、n=7 で 1 日かかると予想されます。真のランダム化されたソリューションを返す唯一の方法は、このようにすることです。

編集:等分布のないランダム解:

以下は、この問題を解決するために私が書いたコードで、Solution 2の実装です。これは部分的で不均等な分散ソリューションであり、十分な時間が与えられれば、保証された すべての可能なソリューションを生成するため、投稿することにしました。

def seeder(n):
    """ Randomly generates the first element in a solution. """
    seed = [0]
    a = range(1, n)
    for i in range(1, 3):
        seed.append(a.pop(random.randint(0,len(a)-1)))
    return [seed]

def extend_seed(seed, n):
    """ Semi-Randomly generates the next element in a solution. """
    next_seed = [seed[-1][0] + 1]
    candidates = range(0, n)
    for i in range(1, 3):
        c = candidates[:]
        for s in next_seed:
            c.remove(s)
        for s in seed:
            try:
                c.remove(s[i])
            except ValueError:
                pass
        next_seed.append(c.pop(random.randint(0,len(c)-1)))
    seed.append(next_seed)
    return seed

def combo(n):
    """ Extends seed until exhausted. 
    Some random generations return results shorter than n. """
    seed = seeder(n)
    while True:
        try:
            extend_seed(seed, n)
        except (ValueError, IndexError):
            return seed

def combos(n):
    """ Ensures that the final return is of length n. """
    while True:
        result = combo(n)
        if len(result) == n:
            return result
于 2012-10-23T17:53:58.187 に答える
2

基本的には、各行の最初の 3 つの数字 (ラテンの四角形) だけを気にすることを除いて、各行と各列に各数字が 1 回だけ含まれる数字のラテン方形、anxn グリッドが必要です。

アップデート:

math.stackexchange.com の質問で説明されているように、等しい確率でランダムなラテン方陣を生成することは自明ではないため、効果のないコード サンプルを削除しました。

SAGE プロジェクトは、その質問で言及されているアルゴリズムを実装しているため、コードを参照してインスピレーションを得ることができます。

または、本当に詳細を知りたい場合は、ランダムなラテン方形を生成する特定のケースについてこのペーパーを確認してください。

于 2012-10-23T23:24:41.310 に答える
1

実際、 itertools はすでにこれを解決しています。

import itertools

allp = [x for x in itertools.permutations(range(3))]
print allp

mylist = ['A','B','C']
allp2 = [x for x in itertools.permutations(mylist)]
print allp2

出力

[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
于 2012-10-23T18:11:57.737 に答える
0

あなたの問題に対する視点が違うだけです。これがうまくいくかどうかを確認してください

>>> from itertools import chain,combinations
>>> def get_combinations_without_duplicates(iterable):
        return (tuple(chain(*(set(iterable) - set(e) , e))) for e in combinations(iterable,2))

>>> list(get_combinations_without_duplicates(range(3)))
[(2, 0, 1), (1, 0, 2), (0, 1, 2)]
于 2012-10-23T17:57:29.187 に答える
0

これは deque.rotate を利用するアプローチです

>>> datax = []
>>> from collections import deque
>>> data_length = 10
>>> subset_length = 3
>>> for i in xrange(0, subset_length):
...     datax.append(deque(xrange(data_length)))
...
>>> for i in xrange(0, subset_length):
...     datax[i].rotate(i)
...
>>> print zip(*datax)
[(0, 9, 8), (1, 0, 9), (2, 1, 0), (3, 2, 1), (4, 3, 2), (5, 4, 3), (6, 5, 4), (7, 6, 5), (8, 7, 6), (9, 8, 7)]
于 2012-10-23T22:46:24.303 に答える
0

単純なリストの回転により、すべての n >= 3 に対して正しい解が得られます。

n = 5 の回転ソリューションを考えてみましょう。

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

各番号は各位置に 1 回だけ表示され、各位置にすべての番号が存在します。


一般にlen(get_combinations_without_duplicates(n)) == n、n >= 3 の場合

于 2012-10-23T19:04:28.993 に答える