我想从Scala列表或数组(不是RDD)中随机采样,样本大小可能比列表或数组的长度长得多,我怎样才能高效?因为样本量可能非常大,并且需要在很多次进行采样(在不同的列表/数组上)。
我知道Spark RDD我们可以使用takeSample()来实现它,是否有Scala列表/数组的等价物?
非常感谢。
答案 0 :(得分:24)
易于理解的版本如下所示:
import scala.util.Random
Random.shuffle(list).take(n)
Random.shuffle(array.toList).take(n)
// Seeded version
val r = new Random(seed)
r.shuffle(...)
答案 1 :(得分:3)
对于数组:
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)
对于列表,我只需将列表转换为Array并使用相同的函数。我怀疑你无论如何都可以为列表提高效率。
值得注意的是,使用列表的相同函数需要花费O(n ^ 2)时间,而将列表首先转换为数组需要花费O(n)时间
答案 2 :(得分:1)
使用for comprehension,对于给定的数组xs
,如下所示,
for (i <- 1 to sampleSize; r = (Math.random * xs.size).toInt) yield a(r)
注意,随机生成器在此处生成单位间隔内的值,这些值被缩放到超出数组大小的范围,并转换为Int
以便在数组上进行索引。
注意对于纯函数随机生成器,请考虑Functional Programming in Scala中的状态Monad方法,讨论here。
答案 3 :(得分:1)
使用经典递归。
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)
}
}
答案 4 :(得分:1)
如果你想在没有替换的情况下对进行采样 - 使用random进行压缩,排序O(n*log(n)
,丢弃randoms,采取
import 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)
答案 5 :(得分:0)
没有测试性能,但是以下代码是进行采样的一种简单而优雅的方法,我相信这里的许多代码只是可以帮助您获得采样代码。只需根据最终样本的大小更改“范围”即可。如果pseude-randomness不足以满足您的需要,则可以在内部列表中使用take(1)并扩大范围。
Random.shuffle((1 to 100).toList.flatMap(x => (Random.shuffle(yourList))))