从Scala中的Seq中选择元素子集的有效方法

时间:2018-04-18 22:31:28

标签: scala

我有一个序列

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)

所以我首先为每个元素附加一个真/假标签,并过滤掉那些假元素,然后取回序列的子集。

有没有更快/更有利的方式?

2 个答案:

答案 0 :(得分:1)

所以基本上有两种方法:

  • 随机选择n个元素
  • 以概率p
  • 包含或排除每个元素

您的解决方案是后者,可以简化为:

l.filter(_ => r.nextFloat < p)

(我正在调用列表lRandom 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