我希望从Scala列表或数组(不是RDD)中随机抽样,样本大小可以比列表或数组的长度长得多,我该如何以 高效 的方式实现?因为样本大小可能非常大,并且需要在不同的列表/数组上进行大量抽样。
我知道对于Spark RDD,我们可以使用takeSample()来实现,是否有与Scala列表/数组等价的方法呢?
非常感谢。
我希望从Scala列表或数组(不是RDD)中随机抽样,样本大小可以比列表或数组的长度长得多,我该如何以 高效 的方式实现?因为样本大小可能非常大,并且需要在不同的列表/数组上进行大量抽样。
我知道对于Spark RDD,我们可以使用takeSample()来实现,是否有与Scala列表/数组等价的方法呢?
非常感谢。
一个容易理解的版本看起来像这样:
import scala.util.Random
Random.shuffle(list).take(n)
Random.shuffle(array.toList).take(n)
// Seeded version
val r = new Random(seed)
r.shuffle(...)
对于数组:
import scala.util.Random
import scala.reflect.ClassTag
def takeSample[T:ClassTag](a:Array[T],n:Int,seed:Long) = {
val rnd = new Random(seed)
Array.fill(n)(a(rnd.nextInt(a.size)))
}
根据已给的种子生成一个随机数生成器(rnd
)。接着,用从0到数组大小之间的随机数填充该数组。
最后一步是将每个随机值应用于输入数组的索引运算符。在REPL中使用它可能如下所示:
scala> val myArray = Array(1,3,5,7,8,9,10)
myArray: Array[Int] = Array(1, 3, 5, 7, 8, 9, 10)
scala> takeSample(myArray,20,System.currentTimeMillis)
res0: scala.collection.mutable.ArraySeq[Int] = ArraySeq(7, 8, 7, 3, 8, 3, 9, 1, 7, 10, 7, 10,
1, 1, 3, 1, 7, 1, 3, 7)
对于列表,我会将其转换为数组然后使用相同的函数。我怀疑你无论如何都不可能得到更高效的列表。
需要注意的是,使用列表的相同功能将花费 O(n^2) 的时间,而首先将列表转换为数组将花费 O(n) 的时间
takeSample
方法不必要地创建了包含索引的数组,然后进行映射。你应该考虑像这样做:Array.fill(n)(a(rng.nextInt(a.size)))
。 - Jason Scott Lenderman() => a(rnd.nextInt(a.size))
更改为 a(rnd.nextInt(a.size))
,并添加了 T
的类标签,以便数组的构建可以正常工作。现在请尝试 :) 对于不便我感到抱歉。 - Feliximport scala.util.Random
val l = Seq("a", "b", "c", "d", "e")
val ran = l.map(x => (Random.nextFloat(), x))
.sortBy(_._1)
.map(_._2)
.take(3)
import scala.util.Random
def takeSample[T](a: List[T], n: Int): List[T] = {
n match {
case n: Int if n <= 0 => List.empty[T]
case n: Int => a(Random.nextInt(a.size)) :: takeSample(a, n - 1)
}
}
takeSample(List(1,2,3),10000)
,但是它会因为不是尾递归而崩溃。 - Felixxs
,如下所示,for (i <- 1 to sampleSize; r = (Math.random * xs.size).toInt) yield a(r)
Int
以便在数组上进行索引。虽然没有测试性能,但以下代码是一种简单而优雅的抽样方法,我相信它可以帮助那些只想获取抽样代码的人。根据您最终样本的大小来更改“range”。如果伪随机性不够满足您的需求,可以在内部列表中使用take(1)并增加范围。
Random.shuffle((1 to 100).toList.flatMap(x => (Random.shuffle(yourList))))
package your.pkg
import your.pkg.SeqHelpers.SampleOps
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable
import scala.language.{higherKinds, implicitConversions}
import scala.util.Random
trait SeqHelpers {
implicit def withSampleOps[E, CC[_] <: Seq[_]](cc: CC[E]): SampleOps[E, CC] = SampleOps(cc)
}
object SeqHelpers extends SeqHelpers {
case class SampleOps[E, CC[_] <: Seq[_]](cc: CC[_]) {
private def recurse(n: Int, builder: mutable.Builder[E, CC[E]]): CC[E] = n match {
case 0 => builder.result
case _ =>
val element = cc(Random.nextInt(cc.size)).asInstanceOf[E]
recurse(n - 1, builder += element)
}
def sample(n: Int)(implicit cbf: CanBuildFrom[CC[_], E, CC[E]]): CC[E] = {
require(n >= 0, "Cannot take less than 0 samples")
recurse(n, cbf.apply)
}
}
}
要实现这个功能:
SeqHelpers
混入到Scalatest规范中import your.pkg.SeqHelpers._
然后以下内容应该可以正常工作:
Seq(1 to 100: _*) sample 10 foreach { println }
欢迎进行编辑以删除强制转换。
如果有一种方法可以在不预先知道具体类型的情况下创建集合的空实例作为累加器,请留言评论。尽管如此,建议使用构建器更为高效。