2012-07-03 7 views
8

módulo de Python 'aleatorio' tiene una función random.choiceEmulate Python en .NET

random.choice(seq)
devolver un elemento aleatorio de la secuencia de SEC no vacío. Si seq está vacío, aumenta IndexError.

¿Cómo puedo emular esto en .NET?

public T RandomChoice<T> (IEnumerable<T> source) 

Edición: He oído esto como una pregunta de la entrevista hace unos años, pero hoy en día el problema se ha producido de forma natural en mi trabajo. La pregunta de la entrevista se declaró con limitaciones

  • 'la secuencia es demasiado largo para guardar en la memoria'
  • 'sólo se puede reproducir indefinidamente la secuencia de una vez'
  • 'la secuencia no tiene una longitud/método de recuento '(à la .NET IEnumerable)
+0

¿Estás diciendo que quieres una función que te devuelva * exactamente lo que hace Python *? O quieres una función con * el mismo contrato *? Es decir, ¿sería feliz si la función .NET devolviera diferentes elementos de lo que Python haría? – AakashM

+2

Solo para comentar sobre las respuestas proporcionadas, @MattHickford, tal vez deberías considerar, además de un 'IEnumerable' incluir una sobrecarga' IList' (o hacer una comprobación en el 'IEnumerable' si es un' IList') para que puedas evite tener que enumerar y crear una colección copiada. EDITAR: También puede agregar una sobrecarga 'params' para sacar una lista hecha en tiempo de compilación:' RandomChoice ("apple", "pear", "orange") ' –

+0

AakashM, solicito un análogo .NET del Python función. ¿Qué es un contrato? –

Respuesta

12

Para hacer un método que repite la fuente una sola vez, y no tiene que asignar memoria para almacenarlo temporalmente, cuente cuántos elementos ha iterado y determine la probabilidad de que el elemento actual sea el resultado:

public T RandomChoice<T> (IEnumerable<T> source) { 
    Random rnd = new Random(); 
    T result = default(T); 
    int cnt = 0; 
    foreach (T item in source) { 
    cnt++; 
    if (rnd.Next(cnt) == 0) { 
     result = item; 
    } 
    } 
    return result; 
} 

Cuando usted está en el primer elemento, la probabilidad es 1/1 que se debe utilizar (ya que es el único elemento que haya visto hasta aquí). Cuando se encuentra en el segundo elemento, la probabilidad es 1/2 de que deba reemplazar el primer elemento, y así sucesivamente.


Esto, naturalmente, utilizar un poco más de la CPU, ya que crea un número aleatorio por artículo, no sólo un único número al azar para seleccionar un elemento, como dasblinkenlight señaló. Puede comprobar si la fuente implementa IList<T>, como sugiere Dan Tao, y el uso de una aplicación que utiliza las capacidades para obtener la longitud de los elementos de la colección y de acceso por el índice:

public T RandomChoice<T> (IEnumerable<T> source) { 
    IList<T> list = source as IList<T>; 
    if (list != null) { 
    // use list.Count and list[] to pick an item by random 
    } else { 
    // use implementation above 
    } 
} 

Nota: Usted debe considerar el envío de la Random instancia en el método. De lo contrario, obtendrá la misma semilla al azar si llama al método dos veces demasiado cerca en el tiempo, ya que la semilla se crea a partir de la hora actual.


El resultado de una prueba de funcionamiento, recogiendo un número de una matriz que contiene 0 - 9, 1000000 veces, para mostrar que la distribución de los números elegidos no está sesgada:

0: 100278 
1: 99519 
2: 99994 
3: 100327 
4: 99571 
5: 99731 
6: 100031 
7: 100429 
8: 99482 
9: 100638 
+2

Ah, bien. ¡Recuerdo haber tenido que hacer algo así una vez! El caso es que creo que deberás mostrar las matemáticas para convencer a todos los lectores de que esto es correcto (no es inmediatamente intuitivo que sea correcto, al menos para mí). –

+0

También recomendaría seguir optimizando para el caso donde 'fuente' es un' IList ', ya que obviamente será más rápido acceder aleatoriamente a un elemento por índice que iterar sobre la lista si no es necesario. –

+2

+1 ¡Muy bonito! Aquí hay un [enlace a una prueba simple por inducción] (http://propersubset.com/2010/04/choosing-random-elements.html) que este algoritmo selecciona un elemento aleatorio con la probabilidad de '1/N'. Es posible que desee observar que este algoritmo ahorra memoria para la variable de temperatura a expensas de utilizar CPU adicional para generar números aleatorios 'N' en lugar de uno solo. No importa con el RNG regular, pero usar uno criptográficamente fuerte puede convertir esto en una compensación. – dasblinkenlight

0

Bien, obtenga una lista de todos los elementos en la secuencia. pregunte un generador de números aleatorios para el índice, elemnt de retorno por índice. Defina qué es Sequence: IEnumerable sería más obvio, pero necesita materializarlo en una lista y luego conocer el número de elementos para el generador de números aleatorios. Esto es por cierto., No emular, es implementar.

¿Es esta una pregunta de curso de estudio para principiantes?

1
private static Random rng = new Random(); 

... 
return source.Skip(rng.next(source.Count())).Take(1); 
+2

1) No olvide el bloqueo. 2) Su código requiere enumeraciones múltiples de una secuencia, que generalmente se debe evitar. 3) 'Take (1)' devuelve una secuencia de elemento único. Deberías usar 'First()' en su lugar. – CodesInChaos

6

Para evitar la iteración a través de la secuencia dos veces (una vez para el recuento y una vez que el elemento) es probablemente una buena idea guardar la secuencia en una matriz antes de conseguir su elemento aleatorio:

public static class RandomExt { 
    private static Random rnd = new Random(); 
    public static T RandomChoice<T> (this IEnumerable<T> source) { 
     var arr = source.ToArray(); 
     return arr[rnd.Next(arr.Length)]; 
    } 
    public static T RandomChoice<T> (this ICollection<T> source) { 
     return source[rnd.Next(rnd.Count)]; 
    } 
} 

EDIT Implementado un very good idea by Chris Sinclair.

+0

Me gusta especialmente esto como un método de extensión. +1 –

+0

¿Por qué 'IList ' en lugar de 'ICollection '? – CodesInChaos

+0

@CodeInChaos Tienes razón, 'ICollection ' es más general. – dasblinkenlight

1
 public T RandomChoice<T> (IEnumerable<T> source) 
     { 
      if (source == null) 
      { 
       throw new ArgumentNullException("source"); 
      } 

      var list = source.ToList(); 

      if (list.Count < 1) 
      { 
       throw new MissingMemberException(); 
      } 

      var rnd = new Random(); 
      return list[rnd.Next(0, list.Count)]; 
     } 

o la extensión

public static T RandomChoice<T> (this IEnumerable<T> source) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException("source"); 
     } 

     var list = source.ToList(); 

     if (list.Count < 1) 
     { 
      throw new MissingMemberException(); 
     } 

     var rnd = new Random(); 
     return list[rnd.Next(0, list.Count)]; 
    } 
1

me gustaría ir con dasblinkenlight's answer, con un pequeño cambio: aprovechar el hecho de que ya source podría ser una colección indexada, en el que caso de que realmente no es necesario poblar una nueva matriz (o lista):

public static class RandomExt 
{ 
    public static T Choice<T>(this Random random, IEnumerable<T> sequence) 
    { 
     var list = sequence as IList<T> ?? sequence.ToList(); 
     return list[random.Next(list.Count)]; 
    } 
} 

Tenga en cuenta que también he modificado la interfaz de la respuesta antes mencionada para que sea más compatible con la versión de Python que REFERE nced en su pregunta:

var random = new Random(); 
var numbers = new int[] { 1, 2, 3 }; 
int randomNumber = random.Choice(numbers); 

Editar: Me gusta Guffa's answer aún mejor, en realidad.

0

Suponiendo una tiene un método de extensión IEnumerable.MinBy:

var r = new Random(); 
return source.MinBy(x=>r.Next()) 

el método MinBy no guarda la secuencia de la memoria, que funciona como IEnumerable.Min hacer un iter ation (ver MoreLinq o elsewhere)

+0

Esto no es muy diferente de lo que ha sugerido @dasblinkenlight. Esto también implica crear un número de números aleatorios y luego decidir cuándo terminar (aunque los controles son diferentes en ambos casos). –