如何有效地从Scala数组中进行采样

时间:2015-10-06 01:33:46

标签: arrays scala sample

我想从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)

4 个答案:

答案 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)(线性)。

您可以通过延迟来降低/摊还成本。例如,您可以返回StreamIterator,以便在访问时评估每个元素。如果您可以在使用时折叠该流,这将有助于节省内存使用量。换句话说,您可以跳过复制部分并直接使用初始数组 - 并非总是可行,取决于任务。

答案 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