web-dev-qa-db-fra.com

Comment échantillonner au hasard à partir d'une liste ou d'un tableau Scala?

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.

12
Carter

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(...)
24
Marius Soutier

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

3
Felix

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)
1
KevinKatz

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

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 .

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

Non plus: 

  • Mixin SeqHelpers, par exemple, avec une spécification Scalatest
  • Inclure import 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.

0
Darren Bishop

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))))

0
ruhsuzbaykus