Je veux échantillonner au hasard dans une liste ou un tableau Scala (pas un RDD), la taille de l'échantillon peut être beaucoup plus longue que la longueur de la liste ou du tableau, comment puis-je faire ceci efficacement ? Parce que la taille de l'échantillon peut être très grande et que l'échantillonnage (sur différentes listes/tableaux) doit être effectué un grand nombre de fois.
Je sais que pour un RDD Spark, nous pouvons utiliser takeSample (), existe-t-il un équivalent pour Scala list/array?
Merci beaucoup.
Une version facile à comprendre ressemblerait à ceci:
import scala.util.Random
Random.shuffle(list).take(n)
Random.shuffle(array.toList).take(n)
// Seeded version
val r = new Random(seed)
r.shuffle(...)
Pour les tableaux:
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)))
}
Créez un générateur de nombres aléatoires (rnd
) en fonction de votre graine. Ensuite, remplissez un tableau avec des nombres aléatoires allant de 0 à la taille de votre tableau.
La dernière étape consiste à appliquer chaque valeur aléatoire à l'opérateur d'indexation de votre tableau d'entrée. Son utilisation dans le REPL pourrait ressembler à ceci:
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)
Pour les listes, je voudrais simplement convertir la liste en tableau et utiliser la même fonction. Je doute que vous puissiez être beaucoup plus efficace pour les listes de toute façon.
Il est important de noter que la même fonction utilisant des listes prendrait O (n ^ 2) fois, alors que convertir d'abord la liste en tableaux prendrait O(n) temps
Si vous voulez goûter sans remplacement - Zip avec des randoms, triez O(n*log(n)
, jetez les randoms, prenez
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)
Utilisation de la récursion classique.
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)
}
}
En utilisant un pour la compréhension, pour un tableau donné xs
comme suit,
for (i <- 1 to sampleSize; r = (Math.random * xs.size).toInt) yield a(r)
Notez que le générateur aléatoire produit ici des valeurs dans l'intervalle d'unité, qui sont mises à l'échelle pour s'étendre sur la taille du tableau et converties en Int
pour l'indexation sur le tableau.
Note Pour un générateur aléatoire purement fonctionnel, considérons par exemple l’approche State Monad de Programmation fonctionnelle en Scala , discutée ici .
Note Considérons également NICTA , un autre générateur de valeur aléatoire purement fonctionnel, son utilisation est illustrée par exemple ici .
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)
}
}
}
Non plus:
SeqHelpers
, par exemple, avec une spécification Scalatestimport your.pkg.SeqHelpers._
Ensuite, les éléments suivants devraient fonctionner:
Seq(1 to 100: _*) sample 10 foreach { println }
Les modifications pour supprimer le casting sont les bienvenues.
De même, s'il existe un moyen de créer une instance vide de la collection pour l'accumulateur, sans connaître le type concret à l'avance, veuillez commenter. Cela dit, le constructeur est probablement plus efficace.
N'a pas testé les performances, mais le code suivant est un moyen simple et élégant de procéder à l'échantillonnage. Je pense que cela peut aider beaucoup de personnes qui viennent ici simplement pour obtenir un code d'échantillonnage. Il suffit de changer la "plage" en fonction de la taille de votre échantillon final. Si le pseudo-aléatoire ne vous suffit pas, vous pouvez utiliser take (1) dans la liste interne et augmenter la plage.
Random.shuffle((1 to 100).toList.flatMap(x => (Random.shuffle(yourList))))