Comment faire des pourcentages arrondis jusqu’à 100%

Considérons les quatre pourcentages ci-dessous, représentés par des nombres float :

  13.626332% 47.989636% 9.596008% 28.788024% ----------- 100.000000% 

Je dois représenter ces pourcentages en nombres entiers. Si j’utilise simplement Math.round() , je me retrouve avec un total de 101%.

 14 + 48 + 10 + 29 = 101 

Si j’utilise parseInt() , je termine avec un total de 97%.

 13 + 47 + 9 + 28 = 97 

Qu’est-ce qu’un bon algorithme pour représenter un nombre quelconque de pourcentages en nombres entiers tout en maintenant un total de 100%?


Edit : Après avoir lu certains commentaires et réponses, il y a clairement plusieurs façons de résoudre ce problème.

Dans mon esprit, pour restr fidèle aux chiffres, le «bon» résultat est celui qui minimise l’erreur globale, définie par l’arrondi des erreurs par rapport à la valeur réelle:

  value rounded error decision ---------------------------------------------------- 13.626332 14 2.7% round up (14) 47.989636 48 0.0% round up (48) 9.596008 10 4.0% don't round up (9) 28.788024 29 2.7% round up (29) 

En cas d’égalité (3.33, 3.33, 3.33), une décision arbitraire peut être prise (par exemple, 3, 4, 3).

Comme aucune des réponses ne semble le résoudre correctement, voici ma version semi-obscure en utilisant des traits de soulignement :

 function foo(l, target) { var off = target - _.reduce(l, function(acc, x) { return acc + Math.round(x) }, 0); return _.chain(l). sortBy(function(x) { return Math.round(x) - x }). map(function(x, i) { return Math.round(x) + (off > i) - (i >= (l.length + off)) }). value(); } foo([13.626332, 47.989636, 9.596008, 28.788024], 100) // => [48, 29, 14, 9] foo([16.666, 16.666, 16.666, 16.666, 16.666, 16.666], 100) // => [17, 17, 17, 17, 16, 16] foo([33.333, 33.333, 33.333], 100) // => [34, 33, 33] foo([33.3, 33.3, 33.3, 0.1], 100) // => [34, 33, 33, 0] 

Il existe plusieurs façons de procéder, à condition de ne pas vous fier aux données décimales d’origine.

La première et peut-être la méthode la plus populaire serait la plus grande méthode du rest

Qui est fondamentalement:

  1. Arrondir tout
  2. Obtenir la différence en sum et 100
  3. Dissortingbuer la différence en ajoutant 1 aux éléments par ordre décroissant de leurs parties décimales

Dans votre cas, ça irait comme ceci:

 13.626332% 47.989636% 9.596008% 28.788024% 

Si vous prenez les parties entières, vous obtenez

 13 47 9 28 

ce qui fait 97, et vous voulez en append trois autres. Maintenant, vous regardez les parties décimales, qui sont

 .626332% .989636% .596008% .788024% 

et prenez les plus gros jusqu’à ce que le total atteigne 100. Vous obtiendrez ainsi:

 14 48 9 29 

Alternativement, vous pouvez simplement choisir d’afficher une décimale au lieu de valeurs entières. Les nombres seraient donc 48,3 et 23,9 etc. Cela ferait chuter l’écart de 100 à 100.

La meilleure façon de procéder est probablement de garder un compte courant (non intégral) de votre position et d’arrondir cette valeur, puis de l’utiliser avec l’historique pour déterminer la valeur à utiliser. Par exemple, en utilisant les valeurs que vous avez données:

 Value CumulValue CumulRounded PrevBaseline Need --------- ---------- ------------ ------------ ---- 0 13.626332 13.626332 14 0 14 ( 14 - 0) 47.989636 61.615968 62 14 48 ( 62 - 14) 9.596008 71.211976 71 62 9 ( 71 - 62) 28.788024 100.000000 100 71 29 (100 - 71) --- 100 

A chaque étape, vous ne arrondissez pas le nombre lui-même. Au lieu de cela, vous arrondissez la valeur accumulée et calculez le meilleur nombre entier qui atteint cette valeur par rapport à la ligne de base précédente. Cette ligne de base correspond à la valeur cumulée (arrondie) de la ligne précédente.

Cela fonctionne parce que vous ne perdez pas d’ informations à chaque étape mais utilisez plutôt les informations plus intelligemment. Les valeurs arrondies «correctes» sont dans la dernière colonne et vous pouvez voir qu’elles totalisent 100.

Le but de l’arrondissement est de générer le moins d’erreur possible. Lorsque vous arrondissez une valeur unique, ce processus est simple et direct et la plupart des gens le comprennent facilement. Lorsque vous arrondissez plusieurs numéros en même temps, le processus devient plus complexe – vous devez définir comment les erreurs vont se combiner, c’est-à-dire ce qui doit être minimisé.

La réponse bien votée de Varun Vohra minimise la sum des erreurs absolues et est très simple à mettre en œuvre. Cependant, il y a des cas de bord qu’il ne gère pas – quel devrait être le résultat de l’arrondi de 24.25, 23.25, 27.25, 25.25 ? L’un d’entre eux doit être arrondi au lieu de baisser. Vous devriez probablement choisir arbitrairement le premier ou le dernier de la liste.

Peut-être vaut-il mieux utiliser l’erreur relative au lieu de l’erreur absolue . L’arrondi de 23,25 à 24 le modifie de 3,2% tandis que l’arrondi de 27,25 à 28 ne le change que de 2,8%. Maintenant, il y a un gagnant clair.

Il est possible de modifier cela encore plus loin. Une technique courante consiste à quadriller chaque erreur, de sorte que les erreurs importantes soient disproportionnellement plus nombreuses que les petites. J’utiliserais aussi un diviseur non linéaire pour obtenir l’erreur relative – il ne semble pas juste qu’une erreur à 1% soit 99 fois plus importante qu’une erreur à 99%. Dans le code ci-dessous, j’ai utilisé la racine carrée.

L’algorithme complet est le suivant:

  1. Faites la sum des pourcentages après les avoir arrondis et soustrayez-les de 100. Cela vous indique combien de ces pourcentages doivent être arrondis à la place.
  2. Générez deux scores d’erreur pour chaque pourcentage, un lorsque celui-ci est arrondi et l’autre lorsqu’il est arrondi. Prenez la différence entre les deux.
  3. Trier les différences d’erreur produites ci-dessus.
  4. Pour le nombre de pourcentages qui doivent être arrondis, prenez un élément de la liste sortingée et augmentez le pourcentage arrondi de 1.

Vous pouvez toujours avoir plusieurs combinaisons avec la même sum d’erreur, par exemple 33.3333333, 33.3333333, 33.3333333 . C’est inévitable et le résultat sera complètement arbitraire. Le code que je donne ci-dessous préfère arrondir les valeurs sur la gauche.

Rassembler tout cela en Python ressemble à ceci.

 def error_gen(actual, rounded): divisor = sqrt(1.0 if actual < 1.0 else actual) return abs(rounded - actual) ** 2 / divisor def round_to_100(percents): if not isclose(sum(percents), 100): raise ValueError n = len(percents) rounded = [int(x) for x in percents] up_count = 100 - sum(rounded) errors = [(error_gen(percents[i], rounded[i] + 1) - error_gen(percents[i], rounded[i]), i) for i in range(n)] rank = sorted(errors) for i in range(up_count): rounded[rank[i][1]] += 1 return rounded >>> round_to_100([13.626332, 47.989636, 9.596008, 28.788024]) [14, 48, 9, 29] >>> round_to_100([33.3333333, 33.3333333, 33.3333333]) [34, 33, 33] >>> round_to_100([24.25, 23.25, 27.25, 25.25]) [24, 23, 28, 25] >>> round_to_100([1.25, 2.25, 3.25, 4.25, 89.0]) [1, 2, 3, 4, 90] 

Comme vous pouvez le voir avec ce dernier exemple, cet algorithme est toujours capable de fournir des résultats non intuitifs. Même si 89.0 n’a pas besoin d’être arrondi, l’une des valeurs de cette liste doit être arrondie; L’erreur relative la plus faible résulte de l’arrondi de cette valeur élevée plutôt que des alternatives beaucoup plus petites.

Cette réponse préconisait à l’origine de passer par toutes les combinaisons possibles d’arrondis / arrondis, mais comme indiqué dans les commentaires, une méthode plus simple fonctionne mieux. L’algorithme et le code reflètent cette simplification.

NE PAS additionner les nombres arrondis. Vous allez avoir des résultats inexacts. Le total pourrait être considérablement réduit en fonction du nombre de termes et de la répartition des fractions.

Affichez les nombres arrondis mais additionnez les valeurs réelles. Selon la manière dont vous présentez les chiffres, la manière de procéder varie. De cette façon, vous obtenez

  14
  48
  dix
  29
  __
 100 

De toute façon, vous allez avoir des divergences. Dans votre exemple, il n’y a aucun moyen d’afficher des chiffres qui totalisent 100 sans arrondir une valeur dans le mauvais sens (la moindre erreur changerait de 9,596 à 9).

MODIFIER

Vous devez choisir entre l’un des éléments suivants:

  1. Précision des articles
  2. Précision de la sum (si vous additionnez des valeurs arrondies)
  3. Cohérence entre les éléments arrondis et la sum arrondie)

La plupart du temps, lorsque vous traitez les pourcentages # 3 est la meilleure option car elle est plus évidente lorsque le total est égal à 101% que lorsque les éléments individuels ne totalisent pas 100%, vous conservez la précision des éléments individuels. “Arrondir” de 9,596 à 9 est inexact à mon avis.

Pour expliquer cela, j’ajoute parfois une note de bas de page qui explique que les valeurs individuelles sont arrondies et peuvent ne pas totaliser 100% – quiconque comprend l’arrondissement doit pouvoir comprendre cette explication.

J’ai écrit une aide à l’arrondi de la version C #, l’algorithme est identique à la réponse de Varun Vohra , j’espère que cela vous aidera.

 public static List GetPerfectRounding(List original, decimal forceSum, int decimals) { var rounded = original.Select(x => Math.Round(x, decimals)).ToList(); Debug.Assert(Math.Round(forceSum, decimals) == forceSum); var delta = forceSum - rounded.Sum(); if (delta == 0) return rounded; var deltaUnit = Convert.ToDecimal(Math.Pow(0.1, decimals)) * Math.Sign(delta); List applyDeltaSequence; if (delta < 0) { applyDeltaSequence = original .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new { x, index }) .OrderBy(a => original[a.index] - rounded[a.index]) .ThenByDescending(a => a.index) .Select(a => a.index).ToList(); } else { applyDeltaSequence = original .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new { x, index }) .OrderByDescending(a => original[a.index] - rounded[a.index]) .Select(a => a.index).ToList(); } Enumerable.Repeat(applyDeltaSequence, int.MaxValue) .SelectMany(x => x) .Take(Convert.ToInt32(delta/deltaUnit)) .ForEach(index => rounded[index] += deltaUnit); return rounded; } 

Il réussit le test unitaire suivant:

 [TestMethod] public void TestPerfectRounding() { CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List {3.333m, 3.334m, 3.333m}, 10, 2), new List {3.33m, 3.34m, 3.33m}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List {3.33m, 3.34m, 3.33m}, 10, 1), new List {3.3m, 3.4m, 3.3m}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List {3.333m, 3.334m, 3.333m}, 10, 1), new List {3.3m, 3.4m, 3.3m}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 13.626332m, 47.989636m, 9.596008m, 28.788024m }, 100, 0), new List {14, 48, 9, 29}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 16.666m, 16.666m, 16.666m, 16.666m, 16.666m, 16.666m }, 100, 0), new List { 17, 17, 17, 17, 16, 16 }); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 33.333m, 33.333m, 33.333m }, 100, 0), new List { 34, 33, 33 }); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 33.3m, 33.3m, 33.3m, 0.1m }, 100, 0), new List { 34, 33, 33, 0 }); } 

Vous pouvez essayer de garder une trace de votre erreur due à l’arrondissement, puis d’arrondir le résultat si l’erreur cumulée est supérieure à la fraction du nombre actuel.

 13.62 -> 14 (+.38) 47.98 -> 48 (+.02 (+.40 total)) 9.59 -> 10 (+.41 (+.81 total)) 28.78 -> 28 (round down because .81 > .78) ------------ 100 

Pas sûr si cela fonctionnerait en général, mais cela semble fonctionner de la même manière si l’ordre est inversé:

 28.78 -> 29 (+.22) 9.59 -> 9 (-.37; rounded down because .59 > .22) 47.98 -> 48 (-.35) 13.62 -> 14 (+.03) ------------ 100 

Je suis sûr qu’il y a des cas extrêmes où cela pourrait tomber en panne, mais toute approche sera au moins quelque peu arbitraire puisque vous modifiez essentiellement vos données d’entrée.

Une fois, j’ai écrit un outil non-arrondi, pour trouver la perturbation minimale à un ensemble de nombres correspondant à un objective. C’était un problème différent, mais on pourrait en théorie utiliser une idée similaire ici. Dans ce cas, nous avons un ensemble de choix.

Ainsi, pour le premier élément, on peut soit arrondir à 14, soit à 13. Le coût (dans un sens de programmation en nombre entier binary) de le faire est moins élevé pour l’arrondi que l’arrondi, déplacer cette valeur sur une plus grande distance. De même, nous pouvons arrondir chaque chiffre vers le haut ou le bas. Nous devons donc choisir parmi un total de 16 choix.

  13.626332 47.989636 9.596008 + 28.788024 ----------- 100.000000 

Je résoudrais normalement le problème général de MATLAB, en utilisant ici bintprog, un outil de programmation en nombres entiers binarys, mais il n’y a que peu de choix à tester, il est donc assez simple de tester chacune des 16 alternatives. Par exemple, supposons que nous devions arrondir cet ensemble comme suit:

  Original Rounded Absolute error 13.626 13 0.62633 47.99 48 0.01036 9.596 10 0.40399 + 28.788 29 0.21198 --------------------------------------- 100.000 100 1.25266 

L’erreur absolue totale est de 1,25266. Il peut être légèrement réduit par les arrondis suivants:

  Original Rounded Absolute error 13.626 14 0.37367 47.99 48 0.01036 9.596 9 0.59601 + 28.788 29 0.21198 --------------------------------------- 100.000 100 1.19202 

En fait, ce sera la solution optimale en termes d’erreur absolue. Bien sûr, s’il y avait 20 termes, l’espace de recherche serait de taille 2 ^ 20 = 1048576. Pour 30 ou 40 termes, cet espace sera de taille significative. Dans ce cas, vous devez utiliser un outil qui peut rechercher efficacement l’espace, peut-être en utilisant un schéma de twig et lié.

Je pense que ce qui suit atteindra ce que vous êtes après

 function func( orig, target ) { var i = orig.length, j = 0, total = 0, change, newVals = [], next, factor1, factor2, len = orig.length, marginOfErrors = []; // map original values to new array while( i-- ) { total += newVals[i] = Math.round( orig[i] ); } change = total < target ? 1 : -1; while( total !== target ) { // Iterate through values and select the one that once changed will introduce // the least margin of error in terms of itself. eg Incrementing 10 by 1 // would mean an error of 10% in relation to the value itself. for( i = 0; i < len; i++ ) { next = i === len - 1 ? 0 : i + 1; factor2 = errorFactor( orig[next], newVals[next] + change ); factor1 = errorFactor( orig[i], newVals[i] + change ); if( factor1 > factor2 ) { j = next; } } newVals[j] += change; total += change; } for( i = 0; i < len; i++ ) { marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i]; } // Math.round() causes some problems as it is difficult to know at the beginning // whether numbers should have been rounded up or down to reduce total margin of error. // This section of code increments and decrements values by 1 to find the number // combination with least margin of error. for( i = 0; i < len; i++ ) { for( j = 0; j < len; j++ ) { if( j === i ) continue; var roundUpFactor = errorFactor( orig[i], newVals[i] + 1) + errorFactor( orig[j], newVals[j] - 1 ); var roundDownFactor = errorFactor( orig[i], newVals[i] - 1) + errorFactor( orig[j], newVals[j] + 1 ); var sumMargin = marginOfErrors[i] + marginOfErrors[j]; if( roundUpFactor < sumMargin) { newVals[i] = newVals[i] + 1; newVals[j] = newVals[j] - 1; marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i]; marginOfErrors[j] = newVals[j] && Math.abs( orig[j] - newVals[j] ) / orig[j]; } if( roundDownFactor < sumMargin ) { newVals[i] = newVals[i] - 1; newVals[j] = newVals[j] + 1; marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i]; marginOfErrors[j] = newVals[j] && Math.abs( orig[j] - newVals[j] ) / orig[j]; } } } function errorFactor( oldNum, newNum ) { return Math.abs( oldNum - newNum ) / oldNum; } return newVals; } func([16.666, 16.666, 16.666, 16.666, 16.666, 16.666], 100); // => [16, 16, 17, 17, 17, 17] func([33.333, 33.333, 33.333], 100); // => [34, 33, 33] func([33.3, 33.3, 33.3, 0.1], 100); // => [34, 33, 33, 0] func([13.25, 47.25, 11.25, 28.25], 100 ); // => [13, 48, 11, 28] func( [25.5, 25.5, 25.5, 23.5], 100 ); // => [25, 25, 26, 24] 

Une dernière chose, j’ai couru la fonction en utilisant les nombres donnés à l’origine dans la question pour comparer à la sortie désirée

 func([13.626332, 47.989636, 9.596008, 28.788024], 100); // => [48, 29, 13, 10] 

C’était différent de ce que voulait la question => [48, 29, 14, 9]. Je ne pouvais pas comprendre cela jusqu’à ce que je regarde la marge d’erreur totale

 ------------------------------------------------- | original | question | % diff | mine | % diff | ------------------------------------------------- | 13.626332 | 14 | 2.74% | 13 | 4.5% | | 47.989636 | 48 | 0.02% | 48 | 0.02% | | 9.596008 | 9 | 6.2% | 10 | 4.2% | | 28.788024 | 29 | 0.7% | 29 | 0.7% | ------------------------------------------------- | Totals | 100 | 9.66% | 100 | 9.43% | ------------------------------------------------- 

Essentiellement, le résultat de ma fonction introduit le moins d’erreurs.

Violon ici

Si vous l’arrondissez, il n’y a pas de bon moyen de l’obtenir exactement dans tous les cas.

Vous pouvez prendre la partie décimale des N pourcentages que vous avez (dans l’exemple que vous avez donné, 4).

Ajouter les parties décimales. Dans votre exemple, vous avez un total de fraction = 3.

Placer les 3 chiffres avec les fractions les plus élevées et poser le rest au sol.

(Désolé pour les modifications)

Je ne suis pas sûr du niveau de précision dont vous avez besoin, mais je voudrais simplement append 1 les n premiers chiffres, n étant le plafond de la sum totale des décimales. Dans ce cas, c’est 3 , donc j’appendais 1 aux 3 premiers éléments et je mettrais le rest au sol. Bien sûr, ce n’est pas très précis, certains chiffres peuvent être arrondis à la hausse ou à la baisse, mais cela fonctionne bien et aboutira toujours à 100%.

Donc [ 13.626332, 47.989636, 9.596008, 28.788024 ] serait [14, 48, 10, 28] car Math.ceil(.626332+.989636+.596008+.788024) == 3

 function evenRound( arr ) { var decimal = -~arr.map(function( a ){ return a % 1 }) .reduce(function( a,b ){ return a + b }); // Ceil of total sum of decimals for ( var i = 0; i < decimal; ++i ) { arr[ i ] = ++arr[ i ]; // compensate error by adding 1 the the first n items } return arr.map(function( a ){ return ~~a }); // floor all other numbers } var nums = evenRound( [ 13.626332, 47.989636, 9.596008, 28.788024 ] ); var total = nums.reduce(function( a,b ){ return a + b }); //=> 100 

Vous pouvez toujours informer les utilisateurs que les chiffres sont arrondis et peuvent ne pas être très précis …

Si vous devez vraiment les arrondir, il y a déjà de très bonnes suggestions ici (le plus grand rest, la moindre erreur relative, etc.).

Il y a aussi déjà une bonne raison de ne pas arrondir (vous obtiendrez au moins un numéro qui “a l’air meilleur” mais qui est “faux”), et comment le résoudre (avertissez vos lecteurs) et c’est ce que je fais.

Permettez-moi d’append sur la “mauvaise” partie du numéro.

Supposons que vous ayez trois événements / entités / … avec des pourcentages que vous rapprochez comme:

 DAY 1 who | real | app ----|-------|------ A | 33.34 | 34 B | 33.33 | 33 C | 33.33 | 33 

Plus tard, les valeurs changent légèrement pour

 DAY 2 who | real | app ----|-------|------ A | 33.35 | 33 B | 33.36 | 34 C | 33.29 | 33 

Le premier tableau a le problème déjà mentionné d’avoir un nombre “faux”: 33,34 est plus proche de 33 que de 34.

Mais maintenant, vous avez une plus grande erreur. Si l’on compare le jour 2 au jour 1, la valeur réelle en pourcentage pour A augmente de 0,01%, mais l’approximation montre une diminution de 1%.

C’est une erreur qualitative, probablement tout à fait pire que l’erreur quantitative initiale.

On pourrait imaginer une approximation pour l’ensemble du jeu, mais vous devrez peut-être publier des données dès le premier jour, vous ne pourrez donc pas en savoir plus sur le deuxième jour. Donc, à moins que vous ne deviez vraiment, approximativement, vous feriez probablement mieux de ne pas.

Ceci est un cas pour l’arrondi du banquier, alias “round half-Même”. Il est pris en charge par BigDecimal. Son but est de s’assurer que l’arrondissement s’équilibre, c’est-à-dire qu’il ne favorise ni la banque ni le client.

Vérifiez si cela est valable ou non en ce qui concerne mes cas de test, je suis en mesure d’obtenir ce travail.

disons que le nombre est k;

  1. sortinger le pourcentage en descendant ou
  2. itérer sur chaque pourcentage de l’ordre décroissant.
  3. calculer le pourcentage de k pour le premier pourcentage prendre Math.Ceil de sortie.
  4. suivant k = k-1
  5. itérer jusqu’à ce que tout le pourcentage soit consommé.

J’ai implémenté la méthode de la réponse de Varun Vohra ici pour les listes et les dicts.

 import math import numbers import operator import itertools def round_list_percentages(number_list): """ Takes a list where all values are numbers that add up to 100, and rounds them off to integers while still retaining a sum of 100. A total value sum that rounds to 100.00 with two decimals is acceptable. This ensures that all input where the values are calculated with [fraction]/[total] and the sum of all fractions equal the total, should pass. """ # Check input if not all(isinstance(i, numbers.Number) for i in number_list): raise ValueError('All values of the list must be a number') # Generate a key for each value key_generator = itertools.count() value_dict = {next(key_generator): value for value in number_list} return round_dictionary_percentages(value_dict).values() def round_dictionary_percentages(dictionary): """ Takes a dictionary where all values are numbers that add up to 100, and rounds them off to integers while still retaining a sum of 100. A total value sum that rounds to 100.00 with two decimals is acceptable. This ensures that all input where the values are calculated with [fraction]/[total] and the sum of all fractions equal the total, should pass. """ # Check input # Only allow numbers if not all(isinstance(i, numbers.Number) for i in dictionary.values()): raise ValueError('All values of the dictionary must be a number') # Make sure the sum is close enough to 100 # Round value_sum to 2 decimals to avoid floating point representation errors value_sum = round(sum(dictionary.values()), 2) if not value_sum == 100: raise ValueError('The sum of the values must be 100') # Initial floored results # Does not add up to 100, so we need to add something result = {key: int(math.floor(value)) for key, value in dictionary.items()} # Remainders for each key result_remainders = {key: value % 1 for key, value in dictionary.items()} # Keys sorted by remainder (biggest first) sorted_keys = [key for key, value in sorted(result_remainders.items(), key=operator.itemgetter(1), reverse=True)] # Otherwise add missing values up to 100 # One cycle is enough, since flooring removes a max value of < 1 per item, # ie this loop should always break before going through the whole list for key in sorted_keys: if sum(result.values()) == 100: break result[key] += 1 # Return return result 

Voici une implémentation Python plus simple de @ varun-vohra réponse:

 def apportion_pcts(pcts, total): proportions = [total * (pct / 100) for pct in pcts] apportions = [math.floor(p) for p in proportions] remainder = total - sum(apportions) remainders = [(i, p - math.floor(p)) for (i, p) in enumerate(proportions)] remainders.sort(key=operator.itemgetter(1), reverse=True) for (i, _) in itertools.cycle(remainders): if remainder == 0: break else: apportions[i] += 1 remainder -= 1 return apportions 

Vous avez besoin de math , d’ itertools , d’ operator .