我有一个序列
val input = Seq(1,3,4,5,9,11...)
我想随机选择它的一个子集。什么是最快的方式。
我目前正是这样实现的:
// ratio是整个组中子组的百分比
def randomSelect(ratio:Double): Boolean = {
val rr=scala.util.Random
if (rr.nextFloat() < ratio) true else false
}
val ratio = 0.3
val result = input.map(x=>(x, randomSelect(ratio))).filter(x._2).map(x=>x._1)
所以我首先为每个元素附加一个真/假标签,并过滤掉那些假元素,然后取回序列的子集。
有没有更快/更有利的方式?
答案 0 :(得分:1)
所以基本上有两种方法:
n
个元素p
您的解决方案是后者,可以简化为:
l.filter(_ => r.nextFloat < p)
(我正在调用列表l
,Random
r
的实例以及您从此处开始的比率p
。)
如果您想要准确地对n
元素进行抽样:
r.shuffle(l).take(n)
我比较了从1000个元素列表中选择的200个元素:
scala> val first = time{
| l.map(x => (x, r.nextFloat < p)).filter(_._2).map(_._1)
| }
Elapsed time: 3249507ns
scala> val second = time {
| r.shuffle(l).take(200)
| }
Elapsed time: 10640432ns
scala> val third = time{
| l.filter(_ => r.nextFloat < p)}
Elapsed time: 1689009ns
删除额外的两个maps
似乎可以将速度提高三分之一(这完全合情合理)。 shuffle-and-take方法明显较慢,但可以保证您有固定数量的元素。
如果你想进行更严格的调查(即平均多次试验,而不是1次),我从here借用了计时功能。
答案 1 :(得分:0)
如果您的列表不是很大,那么其他人建议的简单filter
就足够了:
list.filter(_ => Random.nextDouble < p)
如果你有一个大的列表,Random
的每元素调用可能会成为瓶颈。最小化调用的一种方法是生成随机gaps
(0,1,2,...),通过该随机数据采样将跳过元素。下面是Scala中的一个简单实现:
import scala.util.Random
import scala.math._
def gapSampling(list: List[Double], p: Double): List[Double] = {
def randomGap(p: Double): Double = {
val epsilon: Double = 1e-10
val u = max(Random.nextDouble, epsilon)
floor( log(u) / log(1 - p) )
}
@scala.annotation.tailrec
def samplingFcn(acc: List[Double], list: List[Double], p: Double): List[Double] = list match {
case Nil => acc
case _ =>
val gap = randomGap(p).toInt
val l = list.drop(gap + 1)
val accNew = l.headOption match {
case Some(e) => e :: acc
case None => acc
}
samplingFcn(accNew, l, p)
}
samplingFcn(List[Double](), list, p).reverse
}
val list = (1 to 100).toList.map(_.toDouble)
gapSampling(list, 0.3)
// res1: List[Double] = List(
// 2.0, 5.0, 7.0, 14.0, 15.0, 18.0, 20.0, 25.0, 26.0, 28.0, 33.0,
// 35.0, 42.0, 43.0, 47.0, 48.0, 50.0, 55.0, 56.0, 59.0, 62.0,
// 69.0, 72.0, 75.0, 76.0, 79.0, 82.0, 93.0, 96.0, 97.0, 98.0
// )
有关此类差距抽样的更多详细信息,请参见here。