我想从Scala数组中采样,样本大小可能远大于数组的长度。我怎样才能有效地?通过使用以下代码,运行时间与样本大小成线性关系,当样本量非常大时,如果我们需要多次进行采样,则速度很慢:
def getSample(dataArray: Array[Double], sampleSize: Int, seed: Int): Array[Double] =
{
val arrLength = dataArray.length
val r = new scala.util.Random(seed)
Array.fill(sampleSize)(dataArray(r.nextInt(arrLength)))
}
val myArr= Array(1.0,5.0,9.0,4.0,7.0)
getSample(myArr, 100000, 28)
答案 0 :(得分:1)
长度$ n $的任何给定元素在$ k $大小的样本中至少出现一次的概率是$ 1-(1-1 / n)^ k $。如果这个值接近1,当$ k $与$ 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)
在随机样本中出现的次数。这可能不适合所有应用程序,但对于某些用例,其中样本的形式为某种Iterable
超过(值,计数)对(可以通过data.view.zip(getSampleCounts(data, k, seed))
获得) )实际上非常方便,因为它经常使我们能够对样本组进行一次计算(因为它们是相同的。)例如,假设我有一个昂贵的函数f: T => Double
,我想计算{的样本均值{1}}应用于f
的大小$ k $抽奖的随机样本。然后我们可以做到以下几点:
data
此样本平均值的计算评估data.view.zip(getSampleCounts(data, k, seed)).map({case (x, count) => f(x)*count}).sum/k
$ n $而不是$ k $次(回想一下,我们假设$ k $与$ n $相比较大。)
请注意,f
最多会循环$ n $次,其中$ n $为getSampleCounts
。此外,假设在apache.commons.math3库中以合理的方式完成,在每次迭代中从二项分布中进行采样,其复杂度不应低于$ O(\ log k)$(反向CDF方法和二进制搜索)。因此,上述算法的复杂性为$ O(n \ log k)$,其中$ n $为data.length
,$ k $为您想要绘制的样本数。
答案 1 :(得分:0)
没有办法绕过它。如果您需要使用具有恒定时间元素访问权限的N个元素,那么无论如何,复杂性将为O(n)
(线性)。
您可以通过延迟来降低/摊还成本。例如,您可以返回Stream
或Iterator
,以便在访问时评估每个元素。如果您可以在使用时折叠该流,这将有助于节省内存使用量。换句话说,您可以跳过复制部分并直接使用初始数组 - 并非总是可行,取决于任务。
答案 2 :(得分:0)
要使此采样程序运行得更快,请使用Akka actor框架并行运行采样作业。 创建一个主actor,用于将采样工作分发给Worker actor,并连接来自不同worker的元素。因此,每个Worker actor都会准备/收集固定数量的样本元素,并将生成的集合作为不可变数组返回给master。收到“WorkDone”后,来自Worker的用户定义消息,主actor将元素连接到最终集合中。
答案 3 :(得分:0)
列表很容易。使用以下隐式函数
object ListImplicits {
implicit class SampledArray[T](in: List[T]) {
def sample(n: Int, seed:Option[Long]=None): List[T] = {
seed match {
case Some(s) => Random.setSeed(s)
case _ => // nothing
}
Random.shuffle(in).take(n)
}
}
}
然后导入对象并使用集合转换从Array切换到列表(略微开销):
import ListImplicits.SampledArray
val n = 100000
val list = (0 to n).toList.map(i => Random.nextInt())
val array = list.toArray
val t0 = System.currentTimeMillis()
array.toList.sample(5).toArray
val t1 = System.currentTimeMillis()
list.sample(5)
val t2 = System.currentTimeMillis()
println( "Array (conversion) => delta = " + (t1-t0) + " ms") // 10 ms
println( "List => delta = " + (t2-t1) + " ms") // 8 ms