Imaginez que vous vendez ces chiffres métalliques utilisés pour numéroter les maisons, les portes de casiers, les chambres d'hôtel, etc. Vous devez déterminer combien de chaque chiffre vous devez expédier lorsque votre client a besoin de numéroter des portes ou des maisons :
- 1 à 100
- 51 à 300
- 1 à 2 000 avec des zéros à gauche
La solution évidente est de faire une boucle du premier au dernier chiffre, de convertir le compteur en une chaîne avec ou sans zéros à gauche, d'extraire chaque chiffre et de l'utiliser comme index pour incrémenter un tableau de 10 entiers.
Je me demande s'il n'existe pas une meilleure façon de résoudre ce problème, sans avoir à parcourir en boucle toute la plage des nombres entiers.
Les solutions dans n'importe quel langage ou pseudocode sont les bienvenues.
Edit :
Examen des réponses
John à CashCommons y Wayne Conrad que mon approche actuelle est suffisamment bonne et rapide. Permettez-moi d'utiliser une analogie stupide : Si l'on vous demandait de compter les cases d'un échiquier en moins d'une minute, vous pourriez terminer la tâche en comptant les cases une par une, mais une personne qui n'est pas en mesure de le faire ne pourrait pas le faire. meilleur La solution consiste à compter les côtés et à faire une multiplication, car on vous demandera peut-être plus tard de compter les tuiles d'un bâtiment.
Alex Reisner indique une loi mathématique très intéressante qui, malheureusement, ne semble pas être pertinente pour ce problème.
Andres suggère le même algorithme que j'utilise, mais en extrayant les chiffres avec des opérations %10 au lieu des sous-chaînes.
John à CashCommons y phord proposent de précalculer les chiffres nécessaires et de les stocker dans une table de consultation ou, pour plus de rapidité, dans un tableau. Cela pourrait être une bonne solution si nous avions une valeur entière maximale absolue, inamovible, gravée dans la pierre. Je n'en ai jamais vu.
Marque de haute performance et filtre a calculé les chiffres nécessaires pour différentes plages. Le résultat pour un millon semble indiquer qu'il y a une proportion, mais les résultats pour d'autres nombres montrent des proportions différentes.
filtre trouvé quelques formules qui peuvent être utilisées pour compter les chiffres d'un nombre qui est une puissance de dix. Robert Harvey a eu une expérience très intéressante en postant la question à MathOverflow. L'un des mathématiciens a écrit une solution en utilisant la notation mathématique.
Aaronaught développé et testé une solution en utilisant les mathématiques. Après l'avoir postée, il a examiné les formules provenant de Math Overflow et y a trouvé une faille (point pour Stackoverflow :).
noahlavine a développé un algorithme et l'a présenté en pseudocode.
Une nouvelle solution
Après avoir lu toutes les réponses, et fait quelques expériences, j'ai trouvé que pour une gamme d'entiers de 1 à 10 n -1 :
- Pour les chiffres de 1 à 9, n*10 (n-1) des pièces sont nécessaires
- Pour le chiffre 0, si on n'utilise pas de zéros de tête, n*10 n-1 - ((10 n -1) / 9) sont nécessaires
- Pour le chiffre 0, si on utilise des zéros de tête, n*10 n-1 - n sont nécessaires
La première formule a été trouvée par filtre (et probablement par d'autres), et j'ai trouvé les deux autres par essai et erreur (mais ils peuvent être inclus dans d'autres réponses).
Par exemple, si n = 6, la plage est de 1 à 999 999 :
- Pour les chiffres de 1 à 9, il faut 6*10. 5 \= 600 000 de chaque
- Pour le chiffre 0, sans les zéros de tête, nous avons besoin de 6*10. 5 - (10 6 -1)/9 = 600,000 - 111,111 = 488,889
- Pour le chiffre 0, avec les zéros de tête, nous avons besoin de 6*10. 5 - 6 = 599,994
Ces chiffres peuvent être vérifiés en utilisant Marque de haute performance les résultats.
En utilisant ces formules, j'ai amélioré l'algorithme original. Il boucle toujours du premier au dernier nombre de la plage d'entiers, mais, s'il trouve un nombre qui est une puissance de dix, il utilise les formules pour ajouter aux chiffres le nombre de la quantité pour une plage complète de 1 à 9 ou de 1 à 99 ou de 1 à 999, etc. Voici l'algorithme en pseudocode :
integer First,Last //First and last number in the range
integer Number //Current number in the loop
integer Power //Power is the n in 10^n in the formulas
integer Nines //Nines is the resut of 10^n - 1, 10^5 - 1 = 99999
integer Prefix //First digits in a number. For 14,200, prefix is 142
array 0..9 Digits //Will hold the count for all the digits
FOR Number = First TO Last
CALL TallyDigitsForOneNumber WITH Number,1 //Tally the count of each digit
//in the number, increment by 1
//Start of optimization. Comments are for Number = 1,000 and Last = 8,000.
Power = Zeros at the end of number //For 1,000, Power = 3
IF Power > 0 //The number ends in 0 00 000 etc
Nines = 10^Power-1 //Nines = 10^3 - 1 = 1000 - 1 = 999
IF Number+Nines <= Last //If 1,000+999 < 8,000, add a full set
Digits\[0-9\] += Power\*10^(Power-1) //Add 3\*10^(3-1) = 300 to digits 0 to 9
Digits\[0\] -= -Power //Adjust digit 0 (leading zeros formula)
Prefix = First digits of Number //For 1000, prefix is 1
CALL TallyDigitsForOneNumber WITH Prefix,Nines //Tally the count of each
//digit in prefix,
//increment by 999
Number += Nines //Increment the loop counter 999 cycles
ENDIF
ENDIF
//End of optimization
ENDFOR
SUBROUTINE TallyDigitsForOneNumber PARAMS Number,Count
REPEAT
Digits \[ Number % 10 \] += Count
Number = Number / 10
UNTIL Number = 0
Par exemple, pour la plage 786 à 3 021, le compteur sera incrémenté :
- Par 1 de 786 à 790 (5 cycles)
- Par 9 de 790 à 799 (1 cycle)
- Par 1 de 799 à 800
- Par 99 de 800 à 899
- Par 1 de 899 à 900
- Par 99 de 900 à 999
- Par 1 de 999 à 1000
- Par 999 de 1000 à 1999
- Par 1 de 1999 à 2000
- Par 999 de 2000 à 2999
- Par 1 de 2999 à 3000
- Par 1 de 3000 à 3010 (10 cycles)
- Par 9 de 3010 à 3019 (1 cycle)
- Par 1 de 3019 à 3021 (2 cycles)
Total : 28 cycles Sans optimisation : 2 235 cycles
Notez que cet algorithme résout le problème sans zéros de tête. Pour l'utiliser avec des zéros en tête, j'ai utilisé un hack :
Si l'on a besoin d'une plage de 700 à 1 000 avec des zéros de tête, il faut utiliser l'algorithme pour 10 700 à 11 000, puis soustraire 1 000 - 700 = 300 du compte du chiffre 1.
Benchmark et code source
J'ai testé l'approche originale, la même approche en utilisant %10 et la nouvelle solution pour quelques grandes plages, avec ces résultats :
Original 104.78 seconds
With %10 83.66
With Powers of Ten 0.07
Une capture d'écran de l'application de référence :
(source : <a href="http://clarion.sca.mx/images/stories/digitsbench.png" rel="nofollow noreferrer">clarion.sca.mx </a>)
Si vous souhaitez voir le code source complet ou exécuter le benchmark, utilisez ces liens :
- Code source complet (en Clarion ) : http://sca.mx/ftp/countdigits.txt
- Projet compilable et exe win32 : http://sca.mx/ftp/countdigits.zip
Réponse acceptée
noahlavine La solution est peut-être correcte, mais je n'ai pas pu suivre le pseudo-code, je pense qu'il manque certains détails ou qu'ils ne sont pas complètement expliqués.
Aaronaught La solution semble être correcte, mais le code est tout simplement trop complexe à mon goût.
J'ai accepté filtre J'ai choisi de répondre à cette question, car c'est son raisonnement qui m'a incité à développer cette nouvelle solution.
0 votes
Il existe une formule qui permet de saisir les numéros de début et de fin, ainsi que le numéro du chiffre, et qui renvoie un compte pour ce chiffre. Mais il faudrait que je la trouve.
1 votes
@Muad'Dib - Non, c'est un vrai problème que j'ai résolu dans une application il y a quelque temps, mais je n'aimais pas la façon dont je l'ai fait (j'ai terminé mes études il y a environ 20 ans).
0 votes
Je suis ravi que vous ayez trouvé mon post utile Carlos :) Le code que tu as soumis est étonnamment compact et puissant (avec la possibilité de mettre des zéros en tête), mais je n'arrive pas à le lire. J'ai l'impression qu'il boucle sur tous les nombres comme la méthode de base, et les noms de ces fonctions ne sont pas très descriptifs (DigitsOneNumber). Quoi qu'il en soit, je n'ai pu suivre aucune des réponses complètement, sauf la mienne, parce qu'elle était incomplète ! hehe. Je pense que votre question aurait pu faire l'impasse sur le slashdot - pour une solution optimale claire et complète.
0 votes
@strainer - j'ai posté l'algorithme en pseudocode, j'espère que c'est plus facile à lire maintenant. (BTW j'ai dû chercher 'chuffed' sur urbandictionary.com, je n'étais pas sûr que ce soit une bonne chose :-)
0 votes
C'est clair maintenant. Vous avez étendu la fonction de pointage simple au pointage multiple, et vous l'appliquez à partir du numéro précédent jusqu'à un "report d'unité" avec un incrément correspondant pour l'unité concernée, puis vous pointez les événements de report d'unité individuellement, et ensuite vous répétez en augmentant les unités. Très intéressant, code compact, vous avez en quelque sorte replié la boucle de base. Mais la toute dernière boucle peut devenir coûteuse, 22 cycles dans l'exemple mais potentiellement 999, pour certaines entrées cela pourrait vraiment étirer les choses.
0 votes
En fait, j'ai fait une erreur dans la dernière partie (corrigée maintenant), il n'a besoin que de 13 cycles parce qu'à 3010 l'optimisation se déclenche à nouveau, puis à 3100. Il ne faudra qu'environ 20 cycles pour passer de 3000 à 3999. Peut-être que des optimisations plus intelligentes peuvent être faites au détriment de la complexité du code.
0 votes
Ah, c'est un soulagement' C'était intéressant Carlos :)