長度爲$ n $的數組中的任何給定元素在大小爲$ k $的樣本中至少出現過一次的概率爲$ 1-(1-1/n)^ k $。如果這個值接近1,相對於$ N $ $ķ$大發生,那麼下面的算法可能會根據您的需要是一個不錯的選擇:
import org.apache.commons.math3.random.MersennseTwister
import org.apache.commons.math3.distribution.BinomialDistribution
def getSampleCounts[T](data: Array[T], k: Int, seed: Long): Array[Int] = {
val rng = new MersenneTwister(seed)
val counts = new Array[Int](data.length)
var i = k
do {
val j = new BinomialDistribution(rng.nextLong(), i, 1.0/i)
counts(i) = j
i -= j
} while (i > 0)
counts
}
注意,這種算法不返回一個樣品。相反,它會返回一個Array[Int]
,它的$ i $ -th條目等於data(i)
出現在隨機樣本中的次數。這可能不適用於所有應用程序,但對於某些使用情況(例如,可通過data.view.zip(getSampleCounts(data, k, seed))
獲得)的某種類型的Iterable
以上(value,count)對的樣例,實際上非常方便,因爲它經常使我們能夠對樣本組進行一次計算(因爲它們相同)。例如,假設我有一個昂貴的函數f: T => Double
,並且我想計算應用於大小爲$ k $的隨機樣本的樣本均值f
data
。然後我們可以做到以下幾點:
data.view.zip(getSampleCounts(data, k, seed)).map({case (x, count) => f(x)*count}).sum/k
這個計算的樣本均值計算f
$ N $,而不是$ķ$倍(還記得我們假設$ķ$相比,$ n $的大。)
請注意,getSampleCounts
將最多循環$ n $次,其中$ n $爲data.length
。另外,假設在apache.commons.math3庫中以合理的方式完成每次迭代中二項分佈的採樣,則複雜度應不低於$ O(\ log k)$(反CDF方法和二分搜索)。 )因此,上述算法的複雜性是$ O(n \ log k)$,其中$ n $是data.length
,$ k $是要繪製的樣本數。
你確定這是你的瓶頸?即使有100萬個樣本,上述程序在47 ms內完成。這些操作速度非常快,所以我的另一個問題是爲什麼你甚至需要打算預先計算大量的操作。爲什麼不能根據需要隨時計算它們? – JimN