2009-10-17 13 views
10

Tengo un valor, digamos 20010. Quiero dividir aleatoriamente este valor en 24 horas. Así que, básicamente, divide el valor en una gran matriz de 24 casillas en la que todas las ranuras son aleatoriamente grandes.Valor dividido en 24 partes de tamaño aleatorio usando C#

¿Cuál podría ser una buena manera de resolver esto usando C#?

+0

¿Puede explicar esto un poco más? – BobbyShaftoe

+2

Creo que ==> Toma el valor 20010 y divídelo aleatoriamente en 24 valores separados, de modo que al sumarlos obtengas el valor original. – Gregory

Respuesta

23

Draw 23 (no 24) números al azar, (sin duplicados), en el rango de 1 a 20009. Agregar 0 y 20010 de la lista y el orden de estos números, la diferencia entre cada dos números consecutivos le da una valor de ranura

Un enfoque en línea también es posible dibujando un valor a la vez y restándolo del "bote", dibujando de nuevo cuando el número es mayor que la cantidad restante. Sin embargo, este enfoque puede conducir a una mayor desviación del tamaño de las ranuras.

1

Esto le dará una aleatoriedad un tanto "decreciente" cuanto mayor sea el índice. ¿Puede asignar al azar las posiciones de la lista si es necesario? Depende de lo que necesites hacer con eso.

int initialValue = 20010; 
var values = new List<int>(); 

Random rnd = new Random(); 
int currentRemainder = initialValue; 

for (int i = 0; i < 21; i++) 
{ 
    //get a new value; 
    int val = rnd.Next(1, currentRemainder - (21 - i)); 

    currentRemainder -= val; 
    values.Add(val); 
} 

values.Add(currentRemainder); 

//initialValue == values.Sum() 
+1

Puede considerar cambiar el nombre de currentRemainder a algo así como remainingPortion. Usar el resto puede ser un poco confuso, porque no hay división involucrada. Solo un pensamiento. –

+0

Buen punto, hecho. – Gregory

+0

Hay una cuestión de distribución. ¿Es aceptable cuando el primer número aleatorio es 20010 - 21, que cada número "aleatorio" después es un "1"? –

11

Suponiendo que no desea tener mucho (ningún) control sobre la distribución de tamaños, aquí hay un enfoque que funcionaría (pseudocódigo).

  1. Crear una lista de 24 valores aleatorios, generados como usted quiera, a cualquier escala
  2. hallar la suma de esta lista
  3. crear su lista definitiva de escalar los 24 valores aleatorios en contra de su total de

Notas

  • Si utiliza la aritmética de punto flotante, puede estar apagado por uno o dos. Para evitar esto, no use la escala para completar el último valor, en su lugar, llénelo con el total restante.
  • Si necesita un control más estricto sobre la distribución, utilice un método diferente para generar su matriz inicial, pero el resto no necesita cambiar.
+0

+1 buena solución compañero (no estaba seguro de que esta es la pregunta en realidad) – Letterman

+0

Estoy tratando de entender esta sugerencia, pero no entiendo qué significa "escalando los 24 valores aleatorios contra su total". ¿Alguien podría dar un ejemplo de lo que eso significa exactamente en el código? – Max

+2

double maxValue = 100; var values ​​= new List {1000, 2000, 3000, 4000, 5000}; double scalingFactor = maxValue/values.Sum(); var scale values ​​= values.Select (itm => itm * scalingFactor); var scaleValuesSum = scaleValues.Sum(); – Gregory

3

Si quiere estar seguro de que no está sesgando el proceso sin mucho análisis, puede crear una matriz de 24 elementos, inicializar cada elemento a 0 y luego agregar 1 a uno de los elementos al azar 20010 veces.

Todo depende del tipo de distribuciones que desee ver, pero no creo que ninguna de las otras técnicas recomendadas hasta ahora resultará en que los "cubos" de una hora sean estadísticamente indistinguibles.

23

Aquí es una solución funcional usando el algoritmo de MJV:

static int[] GetSlots(int slots, int max) 
{ 
    return new Random().Values(1, max) 
         .Take(slots - 1) 
         .Append(0, max) 
         .OrderBy(i => i) 
         .Pairwise((x, y) => y - x) 
         .ToArray(); 
} 

public static IEnumerable<int> Values(this Random random, int minValue, int maxValue) 
{ 
    while (true) 
     yield return random.Next(minValue, maxValue); 
} 

public static IEnumerable<TResult> Pairwise<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> resultSelector) 
{ 
    TSource previous = default(TSource); 

    using (var it = source.GetEnumerator()) 
    { 
     if (it.MoveNext()) 
      previous = it.Current; 

     while (it.MoveNext()) 
      yield return resultSelector(previous, previous = it.Current); 
    } 
} 

public static IEnumerable<T> Append<T>(this IEnumerable<T> source, params T[] args) 
{ 
    return source.Concat(args); 
} 
+3

Este código parece demasiado inteligente a la mitad. ¿Es arte? – PeterAllenWebb

+0

En el ojo del espectador, supongo. Me gusta mucho que la implementación principal se lea casi exactamente como mjv lo describe. – dahlbyk

+0

en realidad, creo que esta es una solución elegante y directa .. inteligente, pero no "demasiado" inteligente. – headsling

2

Otra opción sería la de generar un número aleatorio entre 0 y el número de destino. Luego, agrega cada "pieza" a una lista. Elija la "pieza" más grande y córtela en dos, usando otro número aleatorio. Seleccione el más grande de la lista (ahora con tres piezas) y continúe hasta que tenga el número deseado de piezas.

List<int> list = new List<int>(); 
list.Add(2010); 
Random random = new Random(); 
while (list.Count() < 24) 
{ 
    var largest = list.Max(); 
    var newPiece = random.Next(largest - 1); 
    list.Remove(largest); 
    list.Add(newPiece); 
    list.Add(largest - newPiece); 
} 
1
class Numeric 
    def n_rands(n) 
    raw = (1..n).map { |x| rand } 
    raw.map { |x| x * to_f/raw.sum.to_f }.map { |x| x.to_i }.tap do |scaled| 
     scaled[-1] = self - scaled[0..-2].sum 
    end 
    end 
end 

puts 1000.n_rands(10).inspect # [22, 70, 180, 192, 4, 121, 102, 179, 118, 12] 
4

He calculado el tamaño medio de cada uno de los 24 cubos de más de 100 ensayos para cada uno de los algoritmos propuestos aquí. Pensé que era interesante que tres de las cuatro parecen dar como resultado 20010/24 artículos por cubo en promedio, pero el método ingenuo que describí converge a ese promedio más rápidamente. Esto tiene un sentido intuitivo para mí. Ese método es algo así como nevar aleatoriamente en 24 cubos, y por lo tanto es probable que resulte en cubos que son aproximadamente del mismo tamaño. Los otros son más como piratear al azar en una longitud de madera.

Bevan: [751, 845, 809, 750, 887, 886, 838, 868, 837, 902, 841, 812, 818, 774, 815, 857, 752, 815, 896, 872, 833, 864, 769, 894] 
Gregory: [9633, 5096, 2623, 1341, 766, 243, 159, 65, 21, 19, 16, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] 
mjv: [895, 632, 884, 837, 799, 722, 853, 749, 915, 756, 814, 863, 842, 642, 820, 805, 659, 862, 742, 812, 768, 816, 721, 940] 
peterallenwebb: [832, 833, 835, 829, 833, 832, 837, 835, 833, 827, 833, 832, 834, 833, 836, 833, 838, 834, 834, 833, 834, 832, 836, 830] 

Y aquí es el código Python: importación aleatoria

N = 20010; 

def mjv(): 
    gaps = [ random.randrange(0, N) for i in range(0, 24) ] 
    gaps = gaps + [0, N] 
    gaps.sort() 
    value = [ gaps[i+1] - gaps[i] for i in range(0, 24) ] 
    return value 

def gregory(): 
    values = [] 
    remainingPortion = N 

    for i in range(0, 23): 
     val = random.randrange(1, remainingPortion - (23 - i)) 
     remainingPortion = remainingPortion - val 
     values.append(val) 

    values.append(remainingPortion) 

    return values 


def peterallenwebb(): 
    values = [0 for i in range(0, 24) ] 

    for i in range(0, N): 
     k = random.randrange(0, 24) 
     values[k] = values[k] + 1 

    return values 

def bevan(): 
    values = []; 
    sum = 0.0 

    for i in range(0, 24): 
     k = random.random() 
     sum = sum + k 
     values.append(k); 

    scaleFactor = N/sum 

    for j in range(0, 24): 
     values[j] = int(values[j] * scaleFactor) 

    return values 


def averageBucketSizes(method): 
    totals = [0 for i in range(0, 24)] 
    trials = 100 

    for i in range(0,trials): 
     values = method() 

     for j in range(0, 24): 
      totals[j] = totals[j] + values[j]  

    for j in range(0, 24): 
     totals[j] = totals[j]/trials 

    return totals; 


print 'Bevan: ', averageBucketSizes(bevan) 
print 'Gregory: ', averageBucketSizes(gregory) 
print 'mjv: ', averageBucketSizes(mjv) 
print 'peterallenwebb: ', averageBucketSizes(peterallenwebb) 

Avísame si hay algún error. Voy a volver a correr.

+0

Esto es apenas legible. Este código me recuerda los programas que escribí en la escuela secundaria. – zvolkov

+0

Disculpa por la falta de espacio en blanco horizontal. Este es solo mi segundo programa python. He añadido espacios en blanco ahora que me doy cuenta de que a Python no le importa. Aún así, no creo que marque una gran diferencia. ¿Cómo escribirías el alg de mjv en python, por ejemplo? – PeterAllenWebb

+0

@zkolkov podría haber querido decir que es unidiomático. Por ejemplo, bevan es más pythonic como: 'def bevan (N, sample_size): values ​​= [random.random() for _ in range (sample_size)] scaleFactor = N/sum (valores) return [int (value * scaleFactor) para el valor en valores] 'mjv:' def mjv (N, sample_size): espacios = [0] + ordenados (random.randrange (0, N) para _ en rango (sample_size-1)) + [ N] return [huecos [i + 1] - huecos [i] para i dentro del rango (tamaño_de_muestra)] 'o' def mjv (N, tamaño): huecos = ordenados (random.randrange (0, N) para _ en rango (tamaño-1)) return [y - x para x, y en zip ([0] + huecos, espacios + [N])] ' – hughdbrown

2

Aquí hay otra solución que creo que funcionaría muy bien para esto. Cada vez que se invoca el método, devolverá otro conjunto de valores distribuidos aleatoriamente.

public static IEnumerable<int> Split(int n, int m) 
{ 
    Random r = new Random(); 
    int i = 0; 

    var dict = Enumerable.Range(1, m - 1) 
     .Select(x => new { Key = r.NextDouble(), Value = x }) 
     .OrderBy(x => x.Key) 
     .Take(n - 2) 
     .Select(x => x.Value) 
     .Union(new[] { 0, m }) 
     .OrderBy(x => x) 
     .ToDictionary(x => i++); 

    return dict.Skip(1).Select(x => x.Value - dict[x.Key - 1]); 
} 
+0

¡Uso inteligente de un diccionario para imitar Pairwise! Probablemente me quedaré con r.Next (1, m-1) para aleatorizar los intervalos: mezclar la secuencia de todos los 20010 elementos agrega bastante sobrecarga. Además, Concat es preferible a Union si no necesita la operación real establecida: Union tiene una lógica extra para eliminar duplicados, lo que no puede suceder aquí. – dahlbyk

6

Esto es divertido. Inspirado por David, aquí hay una implementación de la solución de mjv que usa solo operadores proporcionados por LINQ. Desde clave Diccionario de David es simplemente un índice, podemos usar una matriz en lugar de la funcionalidad por parejas:

var r = new Random(); 
var a = Enumerable.Repeat(null, n - 1)  // Seq with (n-1) elements... 
        .Select(x => r.Next(1, m)) // ...mapped to random values 
        .Concat(new [] { 0, m }) 
        .OrderBy(x => x) 
        .ToArray(); 

return a.Skip(1).Select((x,i) => x - a[i]); 
+0

¡Ooh! ¡Aun mejor! ¡Me gusta! Ahora estamos llegando a un cierto _conciso_ código. –

1

Yo probé las soluciones de David y dahlbyk pero no tuvo suerte. Así que esto es lo que se me ocurrió después de leer la respuesta de mjv:

public static class IntExtensions 
{ 
    public static IEnumerable<int> Split(this int number, int parts) 
    { 
     var slots = Enumerable.Repeat(0, parts).ToList(); 
     var random = new Random(); 

     while (number > 0) 
     { 
      var slot = random.Next(0, parts); 
      slots[slot]++; 
      number--; 
     } 
     return slots; 
    } 
}