2015-08-21 4 views
3

Donc, l'idée que j'ai, est de pouvoir diviser $ 2,00 en 10 personne, et chacun d'eux recevra $ x.xx montant d'argent au hasard. (N et M seront toujours limitées à 2 décimales et> 0)Diviser aléatoirement un nombre donné M en N parties

Ex: {0,12, 0,24, 1,03, 0,01, 0,2, 0,04, 0,11, 0,18, 0,05, 0,02}

Actuellement j'ai essayé :

private static BigDecimal[] randSum(int n, double m) 
{ 
    Random rand = new Random(); 
    BigDecimal randNums[] = new BigDecimal[n], sum = new BigDecimal(0).setScale(2); 

    for (int i = 0; i < randNums.length; i++) 
    { 
     randNums[i] = new BigDecimal(rand.nextDouble()).setScale(2, RoundingMode.HALF_EVEN); 
     sum = sum.add(randNums[i]); 
    } 

    for (int i = 0; i < randNums.length; i++) 
    { 
     BigDecimal temp1 = randNums[i].divide(sum, 2, RoundingMode.HALF_EVEN); 
     BigDecimal temp2 = temp1.multiply(new BigDecimal(m).setScale(2)); 
     randNums[i] = temp2; 
    } 

    return randNums; 
} 

public static void main(String[] args) 
{ 
    BigDecimal d[] = randSum(5, 2); 

    double sum = 0; 
    for (BigDecimal n : d) 
    { 
     sum += n.doubleValue(); 
     System.out.println(n); 
    } 
    System.out.println("total: " + sum); 
} 

Mais BigDecimals sont trop confus et ils ne s'additionnent pas. Parfois, le total est de 1,98 ou 2,01. Les doubles ne fonctionnent pas en raison du flottement en double précision.

Le code a été prise à partir de:

Getting N random numbers that the sum is M

+0

Que faire si 'm == 1.234'? Vous ne pouvez jamais générer de tels nombres. –

+0

J'ai fixé les conditions, N et M seront toujours limitées à 2 décimales et> 0 – feco

Répondre

5

Supposons que vous avez besoin d'une précision fixe (passée en argument prec):

static public BigDecimal[] split(BigDecimal sum, int prec, int count) { 
    int s = sum.scaleByPowerOfTen(prec).intValue(); 
    Random r = new Random(); 
    BigDecimal[] result = new BigDecimal[count]; 
    int[] v = new int[count]; 

    for (int i = 0; i < count - 1; i++) 
     v[i] = r.nextInt(s); 
    v[count - 1] = s; 

    Arrays.sort(v); 
    result[0] = BigDecimal.valueOf(v[0]).scaleByPowerOfTen(-prec); 
    for (int i = 1; i < count; i++) 
     result[i] = BigDecimal.valueOf(v[i] - v[i - 1]).scaleByPowerOfTen(-prec); 
    return result; 
} 

Cette approche utilise la propriété que Random.nextInt() est uniformément répartie. Après le tri, les valeurs de v[] tableau sont des points dont le montant total est divisé, de sorte que vous générer des résultats en utilisant les différences entre les éléments voisins:

[ 2, 5, 10, 11, ..., 197, 200] // v[] 
[0.02, 0.03, 0.05, 0.01, ..., ..., 0.03] // result[] 

Ici vous opérez avec des valeurs entières, de sorte que les problèmes d'arrondi ne se soucient pas plus.

0

Vous pouvez traiter ce problème comme des entiers, et au lieu de la somme à M, en font à somme 100M.

Faites l'algorithme, et vous obtiendrez un nombre non-entiers, tel que 10.345,
Maintenant - ce que vous aimeriez faire est de prendre la valeur plancher de chaque nombre (10 dans l'exemple ci-dessus), et pour augmenter le nombre à 11 avec probabilité proportionnelle à 0.345. Cela peut être fait en créant le tableau de rappel: rem[i] = value[i] - ceil(value[i]), et choisissez M - sum{ceil(value[i])} valeurs avec des remplacements, selon la probabilité pondérée de rem tableau.

code:

public static BigDecimal[] createRandomSumsTo(BigDecimal M, int n) { 
    int m = M.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN).intValue(); 
    double[] rands = new double[n]; 
    double sum = 0; 
    for (int i = 0; i < n; i++) { 
     rands[i] = rand.nextDouble(); 
     sum += rands[i]; 
    } 
    for (int i = 0; i < n; i++) rands[i] = (rands[i]/sum) * m; 
    int[] intVals = new int[n]; 
    double[] rem = new double[n]; 
    //create base and reminder array: 
    for (int i =0 ; i < n; i++) { 
     intVals[i] = (int) Math.floor(rands[i]); 
     rem[i] = rands[i] - intVals[i]; 
    } 
    //for efficiently chosing a random value by weight 
    double[] aux = new double[n+1]; 
    for (int i = 1 ; i < n+1; i++) { 
     aux[i] = aux[i-1] + rem[i-1]; 
    } 
    //normalize to sum to one. 
    for (int i = 0 ; i < n+1; i++) { 
     aux[i] = aux[i]/aux[n]; 
    } 
    int intsSum = 0; 
    for (int x : intVals) { 
     intsSum += x; 
    } 
    for (; intsSum < m; intsSum++) { 
     intVals[chooseWeighted(aux)]++; 
    } 
    //and create the BigDecimal array: 
    BigDecimal[] res = new BigDecimal[n]; 
    for (int i = 0; i < n; i++) { 
     res[i] = new BigDecimal(intVals[i]).divide(BigDecimal.TEN).divide(BigDecimal.TEN); 
    } 

    return res; 

} 

private static int chooseWeighted(double[] probabilities) { 
    double r = rand.nextDouble(); 
    int idx = Arrays.binarySearch(probabilities, r); 
    if (idx >= 0) return idx-1; 
    return (-1*idx) -2; 
} 
2

Je suggère de multiplier tous les nombres par 100 et de reformuler votre problème: générer les nombres entiers aléatoires non négatifs n qui somme égale au nombre entier m donné. Plus tard, vous pouvez diviser tous les nombres générés par 100 pour obtenir ce que vous voulez. Voici ma mise en œuvre (similaire à la version @SashaSalauyou):

private static int[] randSum(int n, int min, int m) { 
    Random rand = new Random(); 
    int[] nums = new int[n]; 
    int max = m - min*n; 
    if(max <= 0) 
     throw new IllegalArgumentException(); 
    for(int i=1; i<nums.length; i++) { 
     nums[i] = rand.nextInt(max); 
    } 
    Arrays.sort(nums, 1, nums.length); 
    for(int i=1; i<nums.length; i++) { 
     nums[i-1] = nums[i]-nums[i-1]+min; 
    } 
    nums[nums.length-1] = max-nums[nums.length-1]+min; 
    return nums; 
} 

I a également ajouté un paramètre supplémentaire, min qui est le nombre minimal désiré. Réglez-le sur 0 si vous acceptez les zéros dans la réponse. Sinon, vous pouvez le définir à 1 (puis après la division par 100 le nombre le plus bas possible sera 0.01).

+1

Merci, c'est aussi une excellente solution, en utilisant seulement des entiers, il aura de meilleures performances. Mais je vais donner le crédit à @SashaSalauyou, parce qu'il a donné la première réponse. J'aimerais pouvoir accepter les deux. – feco