Scalaリストまたは配列(RDDではない))からランダムにサンプリングしたいのですが、サンプルサイズはリストまたは配列の長さよりもはるかに長くなる可能性があります。これを行う方法効率的に?サンプルサイズは非常に大きくなる可能性があり、(異なるリスト/配列での)サンプリングは何度も実行する必要があるためです。
私はSpark RDDでそれを行うためにtakeSample()を使用できるRDDを知っていますが、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)時間)かかることに注意してください
サンプリングしたい場合なし置換-ランダムでZip、並べ替えO(n*log(n)
、ランダムを破棄、取る
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)
古典的な再帰を使用します。
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)
}
}
次のように、与えられた配列xs
について、内包表記にforを使用すると、
for (i <- 1 to sampleSize; r = (Math.random * xs.size).toInt) yield a(r)
ここでランダムジェネレーターは単位間隔内の値を生成することに注意してください。これは、配列のサイズ全体の範囲にスケーリングされ、配列のインデックス付けのためにInt
に変換されます。
注純粋な関数型ランダムジェネレーターの場合は、たとえば Scalaの関数型プログラミング のState Monadアプローチを検討してください こちら 。
注考慮してください [〜#〜] nicta [〜#〜] 、別の純粋な関数型乱数生成器、使用例たとえば here です。
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 }
キャストを削除するための編集は大歓迎です。
また、事前に具体的な型を知らなくても、アキュムレータのコレクションの空のインスタンスを作成する方法がある場合は、コメントしてください。そうは言っても、ビルダーはおそらくより効率的です。
パフォーマンスをテストしませんでしたが、次のコードはサンプリングを行うシンプルでエレガントな方法であり、サンプリングコードを取得するためだけにここに来る多くの人を助けることができると思います。最終サンプルのサイズに応じて「範囲」を変更するだけです。 pseude-randomnessで十分ではない場合は、内部リストでtake(1)を使用して範囲を広げることができます。
Random.shuffle((1 to 100).toList.flatMap(x => (Random.shuffle(yourList))))