web-dev-qa-db-ja.com

Scalaリストまたは配列からランダムにサンプリングする方法は?

Scalaリストまたは配列(RDDではない))からランダムにサンプリングしたいのですが、サンプルサイズはリストまたは配列の長さよりもはるかに長くなる可能性があります。これを行う方法効率的に?サンプルサイズは非常に大きくなる可能性があり、(異なるリスト/配列での)サンプリングは何度も実行する必要があるためです。

私はSpark RDDでそれを行うためにtakeSample()を使用できるRDDを知っていますが、Scalaリスト/配列に相当するものはありますか?

どうもありがとうございました。

15
Carter

わかりやすいバージョンは次のようになります。

import scala.util.Random

Random.shuffle(list).take(n)
Random.shuffle(array.toList).take(n)

// Seeded version
val r = new Random(seed)
r.shuffle(...)
25
Marius Soutier

アレイの場合:

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)時間)かかることに注意してください

3
Felix

サンプリングしたい場合なし置換-ランダムで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)
1
KevinKatz

古典的な再帰を使用します。

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)
    }
}
1
thomas pocreau

次のように、与えられた配列xsについて、内包表記にforを使用すると、

for (i <- 1 to sampleSize; r = (Math.random * xs.size).toInt) yield a(r)

ここでランダムジェネレーターは単位間隔内の値を生成することに注意してください。これは、配列のサイズ全体の範囲にスケーリングされ、配列のインデックス付けのためにIntに変換されます。

純粋な関数型ランダムジェネレーターの場合は、たとえば Scalaの関数型プログラミング のState Monadアプローチを検討してください こちら

考慮してください [〜#〜] nicta [〜#〜] 、別の純粋な関数型乱数生成器、使用例たとえば here です。

1
elm
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)
    }
  }
}

どちらか:

  • Mixin SeqHelpers、たとえば、Scalatest仕様
  • 含むimport your.pkg.SeqHelpers._

次に、以下が機能するはずです:

Seq(1 to 100: _*) sample 10 foreach { println }

キャストを削除するための編集は大歓迎です。

また、事前に具体的な型を知らなくても、アキュムレータのコレクションの空のインスタンスを作成する方法がある場合は、コメントしてください。そうは言っても、ビルダーはおそらくより効率的です。

0
Darren Bishop

パフォーマンスをテストしませんでしたが、次のコードはサンプリングを行うシンプルでエレガントな方法であり、サンプリングコードを取得するためだけにここに来る多くの人を助けることができると思います。最終サンプルのサイズに応じて「範囲」を変更するだけです。 pseude-randomnessで十分ではない場合は、内部リストでtake(1)を使用して範囲を広げることができます。

Random.shuffle((1 to 100).toList.flatMap(x => (Random.shuffle(yourList))))

0
ruhsuzbaykus