Pythonには、_[0, 100)
_からの置換なしにランダムにサンプリングするmy_sample = random.sample(range(100), 10)
があります。
n
のような数値をサンプリングし、置換せずに(以前にサンプリングしたn
を含めずに)もう1つサンプリングしたいとします。これを超効率的に行うにはどうすればよいですか。
pdate:「合理的に効率的」から「超効率的」に変更されました(ただし、一定の要素は無視されます)
OPからの読者への注意:最初に受け入れられた回答 を見てロジックを理解し、この回答を理解してください。
Aaaaaandと完全性のために:これは ネクロマンサーの答え の概念ですが、禁止された数値のリストを入力として受け取るように調整されています。これは 私の以前の回答 と同じコードですが、数値を生成する前に、forbid
から状態を構築します。
O(f+k)
とメモリO(f+k)
です。明らかに、これはforbid
(sorted/set)のフォーマットを必要とせずに可能な限り最速のものです。これにより、何らかの形でこれが勝者になると思います^^。forbid
がセットの場合、繰り返し推定方法はO(k⋅n/(n-(f+k)))
の方が速く、f+k
のO(k)
に非常に近い非常にn
に近い。forbid
がソートされている場合、 私のばかげたアルゴリズム は次のように高速です:import random
def sample_gen(n, forbid):
state = dict()
track = dict()
for (i, o) in enumerate(forbid):
x = track.get(o, o)
t = state.get(n-i-1, n-i-1)
state[x] = t
track[t] = x
state.pop(n-i-1, None)
track.pop(o, None)
del track
for remaining in xrange(n-len(forbid), 0, -1):
i = random.randrange(remaining)
yield state.get(i, i)
state[i] = state.get(remaining - 1, remaining - 1)
state.pop(remaining - 1, None)
使用法:
gen = sample_gen(10, [1, 2, 4, 8])
print gen.next()
print gen.next()
print gen.next()
print gen.next()
オーバーラップせずに複数のサンプルが必要になることが事前にわかっている場合、random.shuffle()
でlist(range(100))
を実行するのが最も簡単です(Python 3-= list()
をスキップできます= Python 2)、必要に応じてスライスをはがします。
s = list(range(100))
random.shuffle(s)
first_sample = s[-10:]
del s[-10:]
second_sample = s[-10:]
del s[-10:]
# etc
そうでない場合、@ Chronialの回答はかなり効率的です。
サンプリングされた数が母集団よりもはるかに少ない場合は、サンプリングして、それが選択されているかどうかを確認し、選択されている間繰り返します。これはばかげているように聞こえるかもしれませんが、同じ数を選択する可能性が指数関数的に減衰する可能性があるため、選択されていない割合が少しでもある場合はO(n)
よりもはるかに高速です。
Pythonは、PRNGとしてMersenne Twisterを使用しています。 良い十分 。完全に別のものを使用して、予測可能な方法で重複しない数値を生成できます。
二次残差x² mod p
は、2x < p
およびp
が素数の場合に一意です。
今回もその[p = 3 mod 4
]を指定すると、残りのp - (x² % p)
を「反転」すると、結果は残りのスペースになります。
これは非常に説得力のある数値の広がりではないので、パワーを増やし、ファッジ定数を追加すれば、分布はかなり良くなります。
最初に素数を生成する必要があります:
from itertools import count
from math import ceil
from random import randrange
def modprime_at_least(number):
if number <= 2:
return 2
number = (number // 4 * 4) + 3
for number in count(number, 4):
if all(number % factor for factor in range(3, ceil(number ** 0.5)+1, 2)):
return number
素数を生成するコストについて心配するかもしれません。 10⁶要素の場合、これには10分の1ミリ秒かかります。 [None] * 10**6
の実行にはそれよりも時間がかかります。計算は1回だけなので、これは実際の問題ではありません。
さらに、アルゴリズムは素数の正確な値を必要としません。必要なのは、入力された数値よりも大きい定数定数のみです。これは、値のリストを保存して検索することで可能になります。線形スキャンを行う場合はO(log number)
であり、バイナリ検索を行う場合はO(log number of cached primes)
です。実際、 ギャロッピング を使用すると、これをO(log log number)
に下げることができます。これは基本的に定数(log log googol = 2
)です。
次に、ジェネレータを実装します
def sample_generator(up_to):
prime = modprime_at_least(up_to+1)
# Fudge to make it less predictable
fudge_power = 2**randrange(7, 11)
fudge_constant = randrange(prime//2, prime)
fudge_factor = randrange(prime//2, prime)
def permute(x):
permuted = pow(x, fudge_power, prime)
return permuted if 2*x <= prime else prime - permuted
for x in range(prime):
res = (permute(x) + fudge_constant) % prime
res = permute((res * fudge_factor) % prime)
if res < up_to:
yield res
そして、それが機能することを確認してください:
set(sample_generator(10000)) ^ set(range(10000))
#>>> set()
さて、これの素晴らしい点は、プライマシーテスト(およそO(√n)
であるn
は要素の数)を無視すると、このアルゴリズムは時間の複雑さO(k)
、ここで、k
はサンプルのサイズとO(1)
のメモリ使用量です。技術的にはO(√n + k)
ですが、実際にはO(k)
です。
実績のあるPRNGは必要ありません。これはPRNGは線形合同ジェネレーターよりもはるかに優れています(これは一般的です Javaが使用しています ))が、メルセンヌツイスターほど証明されていません。
最初に、別の機能を持つアイテムを生成しません。これにより、チェックではなく数学を通じて重複を回避します。次のセクションでは、この制限を削除する方法を示します。
Shortメソッドは不十分である必要があります(k
はn
に近づく必要があります)。 k
が半分だけn
の場合は、元の提案をそのまま使用してください。
極端なメモリ節約。これは一定のメモリを必要とします... O(k)
ですらありません!
次のアイテムを生成するための一定の時間。これも実際には定数でかなり高速です。組み込みのメルセンヌツイスターほど高速ではありませんがasですが、2倍以内です。
涼しさ。
この要件を削除するには:
最初に、別の機能を持つアイテムを生成しません。これにより、チェックではなく数学を通じて重複を回避します。
私は可能な限り最良のアルゴリズムを作成しましたおよびスペースの複雑さ、これは以前のジェネレーターの単純な拡張です。
以下がその概要です(n
は数字のプールの長さ、k
は「外部」キーの数):
O(√n)
; O(log log n)
すべての妥当な入力これは、O(√n)
のコストが原因で、アルゴリズムの複雑さに関して技術的に完全ではない、私のアルゴリズムの唯一の要因です。実際には、事前計算によってO(log log n)
まで低下するため、これは問題になりません。これは、一定の時間に計り知れないほど近い値です。
イテラブルを一定の割合で使い切った場合、コストは無料で償却されます。
これは実際的な問題ではありません。
O(1)
鍵生成時間明らかにこれを改善することはできません。
O(k)
鍵生成時間外部から生成されたキーがあり、このジェネレータがすでに生成したキーであってはならないという要件のみがある場合、これらは「外部キー」と呼ばれます。外部キーは完全にランダムであると想定されます。そのため、プールからアイテムを選択できるすべての関数で選択できます。
任意の数の外部キーがあり、完全にランダムになる可能性があるため、完全なアルゴリズムの最悪のケースはO(k)
です。
O(k)
外部キーが完全に独立していると想定される場合、それぞれは別個の情報項目を表します。したがって、すべてのキーを保存する必要があります。アルゴリズムは、キーが見つかるたびにキーを破棄するため、ジェネレーターの存続期間を通じてメモリコストはクリアされます。
まあ、それは私のアルゴリズムの両方です。実際には非常に簡単です。
def sample_generator(up_to, previously_chosen=set(), *, Prune=True):
prime = modprime_at_least(up_to+1)
# Fudge to make it less predictable
fudge_power = 2**randrange(7, 11)
fudge_constant = randrange(prime//2, prime)
fudge_factor = randrange(prime//2, prime)
def permute(x):
permuted = pow(x, fudge_power, prime)
return permuted if 2*x <= prime else prime - permuted
for x in range(prime):
res = (permute(x) + fudge_constant) % prime
res = permute((res * fudge_factor) % prime)
if res in previously_chosen:
if Prune:
previously_chosen.remove(res)
Elif res < up_to:
yield res
変更は追加するのと同じくらい簡単です:
if res in previously_chosen:
previously_chosen.remove(res)
渡したset
に追加することで、いつでもpreviously_chosen
に追加できます。実際には、潜在的なプールに戻すためにセットから削除することもできますが、これによりsample_generator
がまだ生成していないか、Prune=False
でスキップした場合にのみ機能します。
あります。すべての要件を満たしていることは簡単で、要件が絶対的なものであることは簡単です。セットがない場合でも、オーバーヘッドは増加しますが、入力をセットに変換することで最悪のケースに対応できます。
統計的に言えば、このPRNGが実際にどれほど優れているか、気になった。
いくつかのクイック検索により、これらの3つのテストを作成することができました。これらはすべて良い結果を示しているようです!
まず、いくつかの乱数:
N = 1000000
my_gen = list(sample_generator(N))
target = list(range(N))
random.shuffle(target)
control = list(range(N))
random.shuffle(control)
これらは0
から10⁶-1
までの10⁶の数字の「シャッフル」リストです。1つはファッジされた楽しいPRNGを使用し、もう1つはベースラインとしてMersenne Twisterを使用しています。 3番目はコントロールです。
これは、線に沿った2つの乱数間の平均距離を調べるテストです。違いはコントロールと比較されます:
from collections import Counter
def birthdat_calc(randoms):
return Counter(abs(r1-r2)//10000 for r1, r2 in Zip(randoms, randoms[1:]))
def birthday_compare(randoms_1, randoms_2):
birthday_1 = sorted(birthdat_calc(randoms_1).items())
birthday_2 = sorted(birthdat_calc(randoms_2).items())
return sum(abs(n1 - n2) for (i1, n1), (i2, n2) in Zip(birthday_1, birthday_2))
print(birthday_compare(my_gen, target), birthday_compare(control, target))
#>>> 9514 10136
これは、それぞれの分散よりも小さいです。
5つの数値を順番に取り、要素の順序を確認するテストを次に示します。120の可能なすべての順序に均等に配分する必要があります。
def permutations_calc(randoms):
permutations = Counter()
for items in Zip(*[iter(randoms)]*5):
sorteditems = sorted(items)
permutations[Tuple(sorteditems.index(item) for item in items)] += 1
return permutations
def permutations_compare(randoms_1, randoms_2):
permutations_1 = permutations_calc(randoms_1)
permutations_2 = permutations_calc(randoms_2)
keys = sorted(permutations_1.keys() | permutations_2.keys())
return sum(abs(permutations_1[key] - permutations_2[key]) for key in keys)
print(permutations_compare(my_gen, target), permutations_compare(control, target))
#>>> 5324 5368
これも、それぞれの分散よりも小さくなります。
これは、「実行」の長さを確認するテストです。連続的な増加または減少のセクション。
def runs_calc(randoms):
runs = Counter()
run = 0
for item in randoms:
if run == 0:
run = 1
Elif run == 1:
run = 2
increasing = item > last
else:
if (item > last) == increasing:
run += 1
else:
runs[run] += 1
run = 0
last = item
return runs
def runs_compare(randoms_1, randoms_2):
runs_1 = runs_calc(randoms_1)
runs_2 = runs_calc(randoms_2)
keys = sorted(runs_1.keys() | runs_2.keys())
return sum(abs(runs_1[key] - runs_2[key]) for key in keys)
print(runs_compare(my_gen, target), runs_compare(control, target))
#>>> 1270 975
ここでの分散は非常に大きく、いくつかの実行で、私は両方の均等な広がりを見せています。そのため、このテストは合格です。
線形合同ジェネレーターは、おそらく「より実り多い」と私に言われました。これが正確な記述かどうかを確認するために、自分でLCGを不適切に実装しました。
LCG(AFAICT)は、通常のジェネレーターのようなものであり、循環式にはなりません。したがって、私が調べたほとんどのリファレンスは、別名。ウィキペディアは、特定の期間の強力なLCGを作成する方法ではなく、期間を定義するもののみをカバーしました。これは結果に影響を与えた可能性があります。
ここに行く:
from operator import mul
from functools import reduce
# Credit http://stackoverflow.com/a/16996439/1763356
# Meta: Also Tobias Kienzler seems to have credit for my
# edit to the post, what's up with that?
def factors(n):
d = 2
while d**2 <= n:
while not n % d:
yield d
n //= d
d += 1
if n > 1:
yield n
def sample_generator3(up_to):
for modulier in count(up_to):
modulier_factors = set(factors(modulier))
multiplier = reduce(mul, modulier_factors)
if not modulier % 4:
multiplier *= 2
if multiplier < modulier - 1:
multiplier += 1
break
x = randrange(0, up_to)
fudge_constant = random.randrange(0, modulier)
for modfact in modulier_factors:
while not fudge_constant % modfact:
fudge_constant //= modfact
for _ in range(modulier):
if x < up_to:
yield x
x = (x * multiplier + fudge_constant) % modulier
素数をチェックすることはもうありませんが、因子を使って奇妙なことをする必要があります。
modulier ≥ up_to > multiplier, fudge_constant > 0
a - 1
はmodulier
...のすべての要素で割り切れる必要があります.fudge_constant
はcoprimeで、modulier
でなければなりません。これらはLCGのルールではなく、全期間のLCGであり、明らかにmod
ulierに等しいことに注意してください。
私はそのようにしました:
modulier
を少なくともup_to
試し、条件が満たされたときに停止します????
multiplier
を????
の製品とし、重複を削除しますmultiplier
がmodulier
以上の場合は、次のmodulier
に進みますfudge_constant
をmodulier
よりも小さい数にして、ランダムに選択しますfudge_constant
にある????
から因子を削除しますこれはそれを生成する良い方法ではありませんが、低いfudge_constant
sとmultiplier
がより一般的であるという事実を除けば、なぜそれが数値の品質に影響を与えるのかはわかりませんこれらのための完璧なジェネレーターは作るかもしれません。
とにかく、結果はappallingです。
print(birthday_compare(lcg, target), birthday_compare(control, target))
#>>> 22532 10650
print(permutations_compare(lcg, target), permutations_compare(control, target))
#>>> 17968 5820
print(runs_compare(lcg, target), runs_compare(control, target))
#>>> 8320 662
要約すると、私のRNGは優れており、線形合同ジェネレーターはそうではありません。 Javaは線形合同法ジェネレーターを使用しますが、下位ビットのみを使用します)ことを考えると、私のバージョンは十分以上であると期待します。
はい、ここに行きます。これは、可能な限り最速の非確率的アルゴリズムでなければなりません。ランタイムはO(k⋅log²(s) + f⋅log(f)) ⊂ O(k⋅log²(f+k) + f⋅log(f)))
およびスペースO(k+f)
です。 f
は禁止番号の量、s
は禁止番号の最長ストリークの長さです。そのための予想はより複雑ですが、明らかにf
に拘束されます。 s^log₂(s)
がf
よりも大きいと想定する場合、またはs
が再び確率的であるという事実に不満がある場合は、ログ部分を2分割検索に変更できます。 _forbidden[pos:]
_を取得するにはO(k⋅log(f+k) + f⋅log(f))
を使用します。
ここでの実際の実装はO(k⋅(k+f)+f⋅log(f))
です。リストへの挿入forbid
はO(n)
です。これは、リストを blist sortedlist に置き換えることで簡単に修正できます。
また、このアルゴリズムは途方もなく複雑であるため、コメントも追加しました。 lin
部分はlog
部分と同じですが、log²(s)
時間ではなくs
が必要です。
_import bisect
import random
def sample(k, end, forbid):
forbidden = sorted(forbid)
out = []
# remove the last block from forbidden if it touches end
for end in reversed(xrange(end+1)):
if len(forbidden) > 0 and forbidden[-1] == end:
del forbidden[-1]
else:
break
for i in xrange(k):
v = random.randrange(end - len(forbidden) + 1)
# increase v by the number of values < v
pos = bisect.bisect(forbidden, v)
v += pos
# this number might also be already taken, find the
# first free spot
##### linear
#while pos < len(forbidden) and forbidden[pos] <=v:
# pos += 1
# v += 1
##### log
while pos < len(forbidden) and forbidden[pos] <= v:
step = 2
# when this is finished, we know that:
# • forbidden[pos + step/2] <= v + step/2
# • forbidden[pos + step] > v + step
# so repeat until (checked by outer loop):
# forbidden[pos + step/2] == v + step/2
while (pos + step <= len(forbidden)) and \
(forbidden[pos + step - 1] <= v + step - 1):
step = step << 1
pos += step >> 1
v += step >> 1
if v == end:
end -= 1
else:
bisect.insort(forbidden, v)
out.append(v)
return out
_
これを、Veedracが提案した「ハック」(およびPythonのデフォルト実装)と比較します。これには、スペースO(f+k)
があり、(n/(n-(f+k))
は 「推測」 )時間:
f+k)))">
私はただ---(これをプロットしました for _k=10
_およびかなり大きな_n=10000
_です(これは、より大きなn
に対してのみ極端になります)。そして、私は言わなければなりません:これは楽しい挑戦のように思えたので、これを実装しただけですが、これがどれほど極端であるかに驚いています:
ズームインして何が起こっているのか見てみましょう:
はい–生成した9998番目の数値の推測はさらに高速です。最初のプロットでわかるように、私の1行でも_f/n
_が大きいほど高速であることに注意してください(ただし、大きなn
にはかなり恐ろしいスペース要件があります)。
要点を理解するには:ここで時間を費やしているのは、セットを生成することだけです。これは、Veedracのメソッドのf
係数だからです。
ですから、ここでの時間を無駄にせずに、Veedracの方法が単に進むべき道であると納得させることができたと思います。私はその確率的な部分があなたを困らせる理由をある程度理解することができますが、ハッシュマップ(= python dict
s)や他の多くのアルゴリズムが同様の方法で機能し、それらがうまくいっているようです。
あなたは繰り返しの数の違いを恐れるかもしれません。上記のように、これは 幾何分布 と_p=n-f/n
_に従います。したがって、標準偏差(=結果が期待される平均から逸脱することを「期待するはずの量」)は
これは基本的には平均と同じです(_√f⋅n < √n² = n
_)。
****編集**:
私はs
が実際にはn/(n-(f+k))
でもあることに気づきました。したがって、私のアルゴリズムのより正確なランタイムはO(k⋅log²(n/(n-(f+k))) + f⋅log(f))
です。上記のグラフが与えられているのでそれはいいです、それはそれがO(k⋅log(f+k) + f⋅log(f))
よりかなり速いという私の直感を証明します。ただし、f⋅log(f)
はランタイムで最も重要な部分であるため、上記の結果についても何も変更されません。
OK、最後にもう一度試してください;-)基本シーケンスを変更することを犠牲にして、これは追加のスペースを必要とせず、sample(n)
呼び出しごとにn
に比例した時間を必要とします。
_class Sampler(object):
def __init__(self, base):
self.base = base
self.navail = len(base)
def sample(self, n):
from random import randrange
if n < 0:
raise ValueError("n must be >= 0")
if n > self.navail:
raise ValueError("fewer than %s unused remain" % n)
base = self.base
for _ in range(n):
i = randrange(self.navail)
self.navail -= 1
base[i], base[self.navail] = base[self.navail], base[i]
return base[self.navail : self.navail + n]
_
小さなドライバー:
_s = Sampler(list(range(100)))
for i in range(9):
print s.sample(10)
print s.sample(1)
print s.sample(1)
_
実際、これは再開可能なrandom.shuffle()
を実装し、n
要素が選択された後に一時停止します。 base
は破棄されませんが、置換されます。
以下は、明示的に差分セットを構築しない方法です。ただし、@ Veedracの「受け入れ/拒否」ロジックの形式を使用します。進むにつれて基本シーケンスを変更するつもりがない場合は、それは避けられないと思います。
def sample(n, base, forbidden):
# base is iterable, forbidden is a set.
# Every element of forbidden must be in base.
# forbidden is updated.
from random import random
nusable = len(base) - len(forbidden)
assert nusable >= n
result = []
if n == 0:
return result
for elt in base:
if elt in forbidden:
continue
if nusable * random() < n:
result.append(elt)
forbidden.add(elt)
n -= 1
if n == 0:
return result
nusable -= 1
assert False, "oops!"
ここに小さなドライバーがあります:
base = list(range(100))
forbidden = set()
for i in range(10):
print sample(10, base, forbidden)
これは、最初に Tim Peters によって投稿され、 Eric によって整えられ、次に necromancer によって適切にスペース最適化された、Knuthシャッフルの私のバージョンです。
私は確かに彼のコードがとてもきれいだと思ったので、これはエリックのバージョンに基づいています:)。
import random
def shuffle_gen(n):
# this is used like a range(n) list, but we don’t store
# those entries where state[i] = i.
state = dict()
for remaining in xrange(n, 0, -1):
i = random.randrange(remaining)
yield state.get(i,i)
state[i] = state.get(remaining - 1,remaining - 1)
# Cleanup – we don’t need this information anymore
state.pop(remaining - 1, None)
使用法:
out = []
gen = shuffle_gen(100)
for n in range(100):
out.append(gen.next())
print out, len(set(out))
Wikipediaの "Fisher--Yates shuffle#Modern method" に基づいて、シャッフルジェネレーターを実装できます。
def shuffle_gen(src):
""" yields random items from base without repetition. Clobbers `src`. """
for remaining in xrange(len(src), 0, -1):
i = random.randrange(remaining)
yield src[i]
src[i] = src[remaining - 1]
次に、itertools.islice
を使用してスライスできます。
>>> import itertools
>>> sampler = shuffle_gen(range(100))
>>> sample1 = list(itertools.islice(sampler, 10))
>>> sample1
[37, 1, 51, 82, 83, 12, 31, 56, 15, 92]
>>> sample2 = list(itertools.islice(sampler, 80))
>>> sample2
[79, 66, 65, 23, 63, 14, 30, 38, 41, 3, 47, 42, 22, 11, 91, 16, 58, 20, 96, 32, 76, 55, 59, 53, 94, 88, 21, 9, 90, 75, 74, 29, 48, 28, 0, 89, 46, 70, 60, 73, 71, 72, 93, 24, 34, 26, 99, 97, 39, 17, 86, 52, 44, 40, 49, 77, 8, 61, 18, 87, 13, 78, 62, 25, 36, 7, 84, 2, 6, 81, 10, 80, 45, 57, 5, 64, 33, 95, 43, 68]
>>> sample3 = list(itertools.islice(sampler, 20))
>>> sample3
[85, 19, 54, 27, 35, 4, 98, 50, 67, 69]
編集: @TimPetersおよび@Chronialによる以下のよりクリーンなバージョンを参照してください。マイナーな編集がこれを上に押し上げました。
これが、インクリメンタルサンプリングの最も効率的なソリューションであると私が信じているものです。以前にサンプリングされた数値のリストの代わりに、呼び出し元によって維持される状態は、インクリメンタルサンプラーで使用できるディクショナリと、範囲内に残っている数値のカウントで構成されます。
以下は、実証的な実装です。他のソリューションと比較して:
O(log(number_previously_sampled))
ですO(number_previously_sampled)
ですコード:
import random
def remove (i, n, state):
if i == n - 1:
if i in state:
t = state[i]
del state[i]
return t
else:
return i
else:
if i in state:
t = state[i]
if n - 1 in state:
state[i] = state[n - 1]
del state[n - 1]
else:
state[i] = n - 1
return t
else:
if n - 1 in state:
state[i] = state[n - 1]
del state[n - 1]
else:
state[i] = n - 1
return i
s = dict()
for n in range(100, 0, -1):
print remove(random.randrange(n), n, s)
これは@necromancerのクールなソリューションを書き直したものです。正しく使用するためにクラスでラップし、より多くのdictメソッドを使用してコード行をカットします。
_from random import randrange
class Sampler:
def __init__(self, n):
self.n = n # number remaining from original range(n)
# i is a key iff i < n and i already returned;
# in that case, state[i] is a value to return
# instead of i.
self.state = dict()
def get(self):
n = self.n
if n <= 0:
raise ValueError("range exhausted")
result = i = randrange(n)
state = self.state
# Most of the fiddling here is just to get
# rid of state[n-1] (if it exists). It's a
# space optimization.
if i == n - 1:
if i in state:
result = state.pop(i)
Elif i in state:
result = state[i]
if n - 1 in state:
state[i] = state.pop(n - 1)
else:
state[i] = n - 1
Elif n - 1 in state:
state[i] = state.pop(n - 1)
else:
state[i] = n - 1
self.n = n-1
return result
_
基本的なドライバーは次のとおりです。
_s = Sampler(100)
allx = [s.get() for _ in range(100)]
assert sorted(allx) == list(range(100))
from collections import Counter
c = Counter()
for i in range(6000):
s = Sampler(3)
one = Tuple(s.get() for _ in range(3))
c[one] += 1
for k, v in sorted(c.items()):
print(k, v)
_
およびサンプル出力:
_(0, 1, 2) 1001
(0, 2, 1) 991
(1, 0, 2) 995
(1, 2, 0) 1044
(2, 0, 1) 950
(2, 1, 0) 1019
_
眼球では、その分布は良好です(懐疑的な場合はカイ2乗検定を実行します)。ここでのいくつかの解決策は、各確率を等しい確率で与えない(たとえそれらが等しい確率でnの各kサブセットを返すとしても)ので、その点でrandom.sample()
とは異なります。
合理的に高速なワンライナー(O(n + m)
、n = range、m = old samplesize):
next_sample = random.sample(set(range(100)).difference(my_sample), 10)
これがコア関数の1つにまだ実装されていないのは驚くべきことですが、ここに、置換されずにサンプル値とリストを返すクリーンバージョンがあります。
def sample_n_points_without_replacement(n, set_of_points):
sampled_point_indices = random.sample(range(len(set_of_points)), n)
sampled_point_indices.sort(reverse=True)
sampled_points = [set_of_points[sampled_point_index] for sampled_point_index in sampled_point_indices]
for sampled_point_index in sampled_point_indices:
del(set_of_points[sampled_point_index])
return sampled_points, set_of_points
これはサイドノートです。リストを置き換えずにサンプリングするまったく同じ問題(sample_space
と呼びます)を解決したいとしますが、まだサンプリングしていない要素のセットを均一にサンプリングするのではなく、 、初期確率分布p
が与えられます。これは、空間全体にわたってサンプリングした場合の、分布のi^th
要素をサンプリングする確率を示します。
次に、numpyを使用した次の実装は数値的に安定しています。
import numpy as np
def iterative_sampler(sample_space, p=None):
"""
Samples elements from a sample space (a list)
with a given probability distribution p (numPy array)
without replacement. If called until StopIteration is raised,
effectively produces a permutation of the sample space.
"""
if p is None:
p = np.array([1/len(sample_space) for _ in sample_space])
try:
assert isinstance(sample_space, list)
assert isinstance(p, np.ndarray)
except AssertionError:
raise TypeError("Required types: \nsample_space: list \np type: np.ndarray")
# Main loop
n = len(sample_space)
idxs_left = list(range(n))
for i in range(n):
idx = np.random.choice(
range(n-i),
p= p[idxs_left] / p[idxs_left].sum()
)
yield sample_space[idxs_left[idx]]
del idxs_left[idx]
短くて簡潔です。皆さんの考えを教えてください!