Imaginez que vous vendiez ces chiffres métalliques utilisés pour numéroter les maisons, les portes de casiers, les chambres d'hôtel, etc.
La solution évidente est de faire une boucle du premier au dernier nombre, 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 existe une meilleure façon de résoudre ce problème, sans avoir à parcourir toute la plage des entiers.
Les solutions dans n'importe quelle langue ou pseudocode sont les bienvenues.
Examen des réponses
John chez CashCommons et Wayne Conrad commente que mon approche actuelle est bonne et assez rapide. Permettez-moi d'utiliser une analogie idiote: si vous aviez la tâche de compter les carrés sur un échiquier en moins d'une minute, vous pourriez terminer la tâche en comptant les carrés un par un, mais un mieux la solution consiste à compter les côtés et à faire une multiplication, car il se peut qu'on vous demande plus tard de compter les tuiles dans un bâtiment.
Alex Reisner pointe vers 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 des chiffres avec% 10 opérations au lieu de sous-chaînes.
John chez CashCommons et phord propose de pré-calculer les chiffres requis et de les stocker dans une table de recherche ou, pour la vitesse brute, un tableau . Cela pourrait être une bonne solution si nous avions une valeur entière absolue, inamovible, incrustée dans la pierre. Je n'en ai jamais vu un.
Marque haute performance et crépine 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 un autre nombre montrent des proportions différentes.
passoire a trouvé des formules qui peuvent être utilisées pour compter le chiffre pour le nombre qui sont une puissance de dix. Robert Harvey a eu une expérience très intéressante en posant la question sur MathOverflow. L'un des mathématiciens a écrit une solution en utilisant la notation mathématique.
Aaronaught a développé et testé une solution utilisant les mathématiques. Après l'avoir posté, il a examiné les formules provenant de Math Overflow et y a trouvé une faille (pointez sur 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 plage d'entiers de 1 à 10n-1:
La première formule a été trouvée par passoire (et probablement par d'autres), et j'ai trouvé les deux autres par essais et erreurs (mais elles peuvent être incluses dans d'autres réponses).
Par exemple, si n = 6, la plage est comprise entre 1 et 999 999:
Ces chiffres peuvent être vérifiés à l'aide des résultats High-Performance Mark.
En utilisant ces formules, j'ai amélioré l'algorithme d'origine. 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, compter la quantité pour une plage complète de 1 à 9. ou 1 à 99 ou 1 à 999 etc. Voici l'algorithme en pseudocode:
entier Premier, dernier // Premier et dernier nombre dans la plage entier Nombre // Nombre actuel dans la boucle entier Puissance // La puissance est le n sur 10 ^ n dans les formules entier Nines // Neuf est le résultat de 10 ^ n - 1, 10 ^ 5 - 1 = 99999 entier Préfixe // Premiers chiffres d'un nombre. Pour 14 200, le préfixe est 142 Tableau 0..9 chiffres // Contiendra le nombre de tous les chiffres POUR Numéro = Premier à dernier APPELER TallyDigitsForOneNumber AVEC nombre, 1 // Compter le nombre de chaque chiffre // dans le nombre, incrémenter de 1 // Début de l'optimisation. Les commentaires concernent Nombre = 1 000 et Dernier = 8 000. Puissance = Zéros à la fin du nombre // Pour 1 000, Puissance = 3 SI Puissance> 0 // Le nombre se termine par 0 00 000 etc Neuf = 10 ^ Puissance-1 // Neuf = 10 ^ 3 - 1 = 1000 - 1 = 999 Numéro IF + Neuf <= Dernier // Si 1000 + 999 <8000, ajoutez un jeu complet Chiffres [0-9] + = Puissance * 10 ^ (Puissance-1) // Ajouter 3 * 10 ^ (3-1) = 300 aux chiffres 0 à 9 Chiffres [0] - = -Power // Ajuster le chiffre 0 (première formule de zéros) Préfixe = Premiers chiffres de Number // Pour 1000, le préfixe est 1 APPELER TallyDigitsForOneNumber WITH Prefix, Nines // Tally le nombre de chaque // chiffre en préfixe, // incrément de 999 Nombre + = neuf // Incrémente le compteur de boucles 999 cycles ENDIF ENDIF // Fin de l'opti mization ENDFOR SUBROUTINE TallyDigitsForOneNumber PARAMS Number, Count REPEAT Digits [Number% 10] + = Count Number = Numéro/10 JUSQU'À Numéro = 0
Par exemple, pour la plage 786 à 3 021, le compteur sera incrémenté:
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 non significatifs, j'ai utilisé un hack:
Si une plage de 700 à 1 000 avec des zéros en tête est nécessaire, utilisez l'algorithme de 10 700 à 11 000, puis soustrayez 1 000 - 700 = 300 du nombre de chiffres 1.
Benchmark et code source
J'ai testé l'approche originale, la même approche en utilisant% 10 et la nouvelle solution pour certaines grandes gammes, avec ces résultats:
Original 104,78 secondes Avec% 10 83,66 Avec des pouvoirs de dix 0,07
Une capture d'écran de l'application de référence:
(source: clarion.sca.mx )
Si vous souhaitez voir le code source complet ou exécuter le benchmark, utilisez ces liens:
Réponse acceptée
noahlavine la solution est peut-être correcte, mais je ne pouvais tout simplement pas suivre le pseudo-code, je pense qu'il manque des détails ou pas complètement expliqué.
Aaronaught la solution semble être correcte, mais le code est tout simplement trop complexe à mon goût.
J'ai accepté la réponse de passoire, car sa ligne de pensée m'a guidé pour développer cette nouvelle solution.
Pour récupérer les chiffres d'un nombre, nous n'aurions besoin de faire une conversion de chaîne coûteuse que si nous ne pouvions pas faire de mod, les chiffres peuvent être poussés le plus rapidement d'un nombre comme celui-ci:
feed=number;
do
{ digit=feed%10;
feed/=10;
//use digit... eg. digitTally[digit]++;
}
while(feed>0)
cette boucle doit être très rapide et peut simplement être placée à l'intérieur d'une boucle des nombres du début à la fin pour la manière la plus simple de compter les chiffres.
Pour aller plus vite, pour une plus grande gamme de nombres, je cherche une méthode optimisée pour comptabiliser tous les chiffres de 0 à la signification du nombre * 10 ^ (du début à la fin me fait vibrer)
voici un tableau montrant les décomptes de chiffres de quelques chiffres significatifs uniques .. ceux-ci incluent 0, mais pas la valeur supérieure elle-même, - c'était une erreur mais c'est peut-être un peu plus facile de voir les modèles (les chiffres des valeurs supérieures étant absents ici) Ces décomptes n'incluent pas les zéros de fin,
1 10 100 1000 10000 2 20 30 40 60 90 200 600 2000 6000
0 1 1 10 190 2890 1 2 3 4 6 9 30 110 490 1690
1 0 1 20 300 4000 1 12 13 14 16 19 140 220 1600 2800
2 0 1 20 300 4000 0 2 13 14 16 19 40 220 600 2800
3 0 1 20 300 4000 0 2 3 14 16 19 40 220 600 2800
4 0 1 20 300 4000 0 2 3 4 16 19 40 220 600 2800
5 0 1 20 300 4000 0 2 3 4 16 19 40 220 600 2800
6 0 1 20 300 4000 0 2 3 4 6 19 40 120 600 1800
7 0 1 20 300 4000 0 2 3 4 6 19 40 120 600 1800
8 0 1 20 300 4000 0 2 3 4 6 19 40 120 600 1800
9 0 1 20 300 4000 0 2 3 4 6 9 40 120 600 1800
edit: éclaircir mes pensées d'origine:
du tableau de force brute montrant les décomptes de 0 (inclus) à poweroTen (notinc), il est visible qu'un majordigit de tenpower:
increments tally[0 to 9] by md*tp*10^(tp-1)
increments tally[1 to md-1] by 10^tp
decrements tally[0] by (10^tp - 10)
(to remove leading 0s if tp>leadingzeros)
can increment tally[moresignificantdigits] by self(md*10^tp)
(to complete an effect)
si ces ajustements de pointage étaient appliqués pour chaque chiffre significatif, le pointage devrait être modifié comme s'il était compté de 0 à la fin 1
les réglages peuvent être inversés pour supprimer la plage précédente (numéro de départ)
Merci Aaronaught pour votre réponse complète et testée.
Il existe une solution mathématique claire à un problème comme celui-ci. Supposons que la valeur soit complétée par zéro au nombre maximal de chiffres (ce n'est pas le cas, mais nous compenserons cela plus tard), et raisonnons à travers:
Le modèle évident pour un chiffre donné, si la plage est de 0 à une puissance de 10, est N * 10N-1, où [~ # ~] n [~ # ~] est la puissance de 10.
Et si la plage n'est pas une puissance de 10? Commencez avec la puissance la plus faible de 10, puis augmentez. Le cas le plus simple à traiter est un maximum comme 399. Nous savons que pour chaque multiple de 100, chaque chiffre se produit au moins 20 fois, mais nous devons compenser le nombre de fois qu'il apparaît dans le position du chiffre le plus significatif, qui sera exactement 100 pour les chiffres 0-3 et exactement zéro pour tous les autres chiffres. Plus précisément, le montant supplémentaire à ajouter est de 10N pour les chiffres pertinents.
En mettant cela dans une formule, pour les limites supérieures qui sont 1 de moins que certains multiples d'une puissance de 10 (c'est-à-dire 399, 6999, etc.), cela devient: M * N * 10N-1 + iif (d <= M, 10N, 0)
Il ne vous reste plus qu'à gérer le reste (que nous appellerons R ). Prenez 445 comme exemple. Quel que soit le résultat pour 399, plus la plage 400-445. Dans cette plage, le MSD se produit R plusieurs fois, et tous les chiffres (y compris le MSD) apparaissent également aux mêmes fréquences qu'ils le feraient de la plage [0 - - R ].
Il ne nous reste plus qu'à compenser les zéros non significatifs. Ce modèle est facile - c'est juste:
dixN + 10N-1 + 10N-2 + ... + ** 10
Mise à jour: Cette version prend correctement en compte les "zéros de remplissage", c'est-à-dire les zéros en position médiane lorsqu'il s'agit du reste ([4 0, 4 1, 4 2, ...]). Comprendre les zéros de remplissage est un peu moche, mais le code révisé (pseudocode de style C) le gère:
function countdigits(int d, int low, int high) {
return countdigits(d, low, high, false);
}
function countdigits(int d, int low, int high, bool inner) {
if (high == 0)
return (d == 0) ? 1 : 0;
if (low > 0)
return countdigits(d, 0, high) - countdigits(d, 0, low);
int n = floor(log10(high));
int m = floor((high + 1) / pow(10, n));
int r = high - m * pow(10, n);
return
(max(m, 1) * n * pow(10, n-1)) + // (1)
((d < m) ? pow(10, n) : 0) + // (2)
(((r >= 0) && (n > 0)) ? countdigits(d, 0, r, true) : 0) + // (3)
(((r >= 0) && (d == m)) ? (r + 1) : 0) + // (4)
(((r >= 0) && (d == 0)) ? countpaddingzeros(n, r) : 0) - // (5)
(((d == 0) && !inner) ? countleadingzeros(n) : 0); // (6)
}
function countleadingzeros(int n) {
int tmp= 0;
do{
tmp= pow(10, n)+tmp;
--n;
}while(n>0);
return tmp;
}
function countpaddingzeros(int n, int r) {
return (r + 1) * max(0, n - max(0, floor(log10(r))) - 1);
}
Comme vous pouvez le voir, il est devenu un peu plus laid, mais il fonctionne toujours en temps O (log n), donc si vous devez gérer des nombres en milliards, cela vous donnera toujours des résultats instantanés. :-) Et si vous l'exécutez sur la plage [0 - 1000000], vous obtenez exactement la même distribution que celle publiée par High-Performance Mark, donc je suis presque certain que c'est correct.
Pour info, la raison de la variable inner
est que la fonction de début zéro est déjà récursive, donc elle ne peut être comptée que lors de la première exécution de countdigits
.
pdate 2: Dans le cas où le code est difficile à lire, voici une référence pour ce que chaque ligne de la déclaration de retour countdigits
signifie (j'ai essayé les commentaires en ligne mais ils ont rendu le code encore plus difficile à lire) lis):
Je suppose que vous voulez une solution où les nombres sont dans une plage et que vous avez le nombre de début et de fin. Imaginez commencer avec le numéro de début et compter jusqu'à ce que vous atteigniez le numéro de fin - cela fonctionnerait, mais ce serait lent. Je pense que l'astuce pour un algorithme rapide est de réaliser que pour remonter d'un chiffre à la place 10 ^ x et garder tout le reste identique, vous devez utiliser tous les chiffres avant 10 ^ x fois plus tous les chiffres 0 -9 10 ^ (x-1) fois. (Sauf que votre comptage peut avoir impliqué un report au-delà du xième chiffre - je corrige cela ci-dessous.)
Voici un exemple. Supposons que vous comptez de 523 à 1004.
Pour accélérer le dernier bit, regardez la partie sur les deux endroits les plus à droite. Il utilise chaque chiffre 10 + 1 fois. En général, 1 + 10 + ... + 10 ^ n = (10 ^ (n + 1) - 1)/9, que nous pouvons utiliser pour accélérer encore plus le comptage.
Mon algorithme consiste à compter du numéro de début au numéro de fin (en utilisant le comptage en base 10), mais utilisez le fait ci-dessus pour le faire rapidement. Vous parcourez les chiffres du numéro de départ du plus petit au plus significatif, et à chaque endroit, vous comptez pour que ce chiffre soit le même que celui du numéro de fin. À chaque point, n est le nombre de comptes à rebours que vous devez faire avant d'arriver à un report, et m le nombre que vous devez faire par la suite.
Supposons maintenant que le pseudocode compte comme langue. Voici donc ce que je ferais:
convertir les nombres de début et de fin en tableaux de chiffres début [] et fin [] créer un tableau qui compte [] avec 10 éléments qui stocke le nombre de copies de chaque chiffre que vous besoin de parcourir le numéro de départ de droite à gauche. au i-ème chiffre, soit d le nombre de chiffres que vous devez compter pour passer de ce chiffre au i-ème chiffre du numéro de fin. (c'est-à-dire soustraire les chiffres équivalents de mod 10) ajouter d * (10 ^ i - 1)/9 à chaque entrée en nombre. soit m la valeur numérique de tous les chiffres à droite de ce chiffre, n soit 10 ^ i - m. pour chaque chiffre e de la gauche du numéro de départ jusqu'au i- inclusivement. e chiffre, ajoutez n au nombre de ce chiffre. pour j de 1 à d incrémentez le i-ème chiffre de un, y compris tout report pour chaque chiffre e de la gauche du numéro de départ jusqu'au i-ème chiffre inclus, ajouter 10 ^ i au nombre de ce chiffre pour chaque chiffre e de la gauche du numéro de départ jusqu'au et en incluant le i-ème chiffre, ajoutez m au nombre de ce chiffre. définissez le i-ème chiffre du numéro de départ comme étant le i-ème chiffre de la fin nombre.
Oh, et comme la valeur de i augmente de un à chaque fois, gardez une trace de votre ancien 10 ^ i et multipliez-le simplement par 10 pour obtenir le nouveau, au lieu d'exponentier à chaque fois.
Voici une très mauvaise réponse, j'ai honte de la poster. J'ai demandé à Mathematica de comptabiliser les chiffres utilisés dans tous les nombres de 1 à 1 000 000, sans aucun 0 en tête. Voici ce que j'ai obtenu:
0 488895
1 600001
2 600000
3 600000
4 600000
5 600000
6 600000
7 600000
8 600000
9 600000
La prochaine fois que vous commanderez des chiffres collants à vendre dans votre quincaillerie, commandez dans ces proportions, vous ne vous tromperez pas.
I a posé cette question sur Math Overflow , et j'ai reçu une fessée pour avoir posé une question aussi simple. Un des utilisateurs a eu pitié de moi et a dit que si je le publiais sur The Art of Problem Solving , il y répondrait; donc je l'ai fait.
Voici la réponse qu'il a postée:
http://www.artofproblemsolving.com/Forum/viewtopic.php?p=1741600#17416
Gênant, mon math-fu est insuffisant pour comprendre ce qu'il a posté (le gars a 19 ans ... c'est tellement déprimant). J'ai vraiment besoin de prendre des cours de mathématiques.
Du côté positif, l'équation est récursive, il devrait donc être simple de la transformer en une fonction récursive avec quelques lignes de code, par quelqu'un qui comprend les mathématiques.
Votre approche est bonne. Je ne sais pas pourquoi vous auriez besoin de quelque chose plus rapidement que ce que vous avez décrit.
Ou, cela vous donnerait une solution instantanée: avant d'en avoir réellement besoin, calculez ce dont vous auriez besoin de 1 à un nombre maximum. Vous pouvez stocker les numéros nécessaires à chaque étape. Si vous avez une plage comme votre deuxième exemple, ce serait ce qui est nécessaire pour 1 à 300, moins ce qui est nécessaire pour 1 à 50.
Vous disposez maintenant d'une table de recherche qui peut être appelée à volonté. Faire jusqu'à 10 000 prendrait seulement quelques Mo et, quoi, quelques minutes pour calculer, une fois?
Je sais que cette question a une réponse acceptée, mais j'ai été chargé d'écrire ce code pour un entretien d'embauche et je pense que j'ai trouvé une solution alternative qui est rapide, ne nécessite aucune boucle et peut utiliser ou supprimer les zéros de tête selon les besoins.
C'est en fait assez simple mais pas facile à expliquer.
Si vous listez les n premiers nombres
1
2
3
.
.
.
9
10
11
Il est habituel de commencer à compter les chiffres requis du numéro de la pièce de départ au numéro de la pièce de fin de gauche à droite, donc pour ce qui précède, nous avons un 1, un 2, un 3 ... un 9, deux 1 un zéro , quatre 1, etc. La plupart des solutions que j'ai vues ont utilisé cette approche avec une certaine optimisation pour l'accélérer.
Ce que j'ai fait, c'est de compter verticalement en colonnes, comme en centaines, dizaines et unités. Vous connaissez le numéro de pièce le plus élevé afin que nous puissions calculer le nombre de chaque chiffre dans la colonne des centaines via une seule division, puis récapituler et calculer le nombre dans la colonne des dizaines, etc. Ensuite, nous pouvons soustraire les zéros de tête si nous le souhaitons.
Plus facile à visualiser si vous utilisez Excel pour écrire les chiffres mais utilisez une colonne distincte pour chaque chiffre du nombre
A B C
- - -
0 0 1 (assuming room numbers do not start at zero)
0 0 2
0 0 3
.
.
.
3 6 4
3 6 5
.
.
.
6 6 9
6 7 0
6 7 1
^
sum in columns not rows
Donc, si le numéro de salle le plus élevé est 671, la colonne des centaines aura 100 zéros verticalement, suivis de 100 uns et ainsi de suite jusqu'à 71 six, ignorez 100 des zéros si nécessaire car nous savons qu'ils sont tous en tête.
Reculez ensuite jusqu'aux dizaines et effectuez la même opération, nous savons qu'il y aura 10 zéros suivis de 10 uns, etc., répétés six fois, puis la dernière fois jusqu'à 2 sept. Encore une fois peut ignorer les 10 premiers zéros car nous savons qu'ils mènent. Enfin, bien sûr, faites les unités, en ignorant le premier zéro comme requis.
Il n'y a donc pas de boucles tout est calculé avec division. J'utilise la récursivité pour voyager "vers le haut" des colonnes jusqu'à ce que le maximum soit atteint (dans ce cas des centaines), puis redescendre en totalisant au fur et à mesure.
J'ai écrit cela en C # et je peux poster du code si quelqu'un est intéressé, n'a pas fait de timing de référence, mais il est essentiellement instantané pour des valeurs allant jusqu'à 10 ^ 18 chambres.
Impossible de trouver cette approche mentionnée ici ou ailleurs, alors j'ai pensé qu'elle pourrait être utile pour quelqu'un.
Cela ne répond pas à votre question exacte, mais il est intéressant de noter la distribution des premiers chiffres selon loi de Benford . Par exemple, si vous choisissez un ensemble de nombres au hasard, 30% d'entre eux commenceront par "1", ce qui est quelque peu contre-intuitif.
Je ne connais aucune distribution décrivant les chiffres suivants, mais vous pourriez être en mesure de le déterminer empiriquement et de trouver une formule simple pour calculer une approximation nombre de chiffres requis pour toute plage de nombres.
Si vous avez besoin d'une vitesse brute sur de nombreuses itérations, essayez une table de recherche:
int nDigits[10000][10] ; // Don't try this on the stack, kids!
n=0..9999:
if (n>0) nDigits[n] = nDigits[n-1]
d=0..9:
nDigits[n][d] += countOccurrencesOf(n,d) //
Number of digits "between" two numbers becomes simple subtraction.
For range=51 to 300, take the counts for 300 and subtract the counts for 50.
0's = nDigits[300][0] - nDigits[50][0]
1's = nDigits[300][1] - nDigits[50][1]
2's = nDigits[300][2] - nDigits[50][2]
3's = nDigits[300][3] - nDigits[50][3]
etc.
Si "mieux" signifie "plus clair", alors j'en doute. Si cela signifie "plus rapide", alors oui, mais je n'utiliserais pas un algorithme plus rapide à la place d'un plus clair sans besoin impérieux.
#!/usr/bin/Ruby1.8
def digits_for_range(min, max, leading_zeros)
bins = [0] * 10
format = [
'%',
('0' if leading_zeros),
max.to_s.size,
'd',
].compact.join
(min..max).each do |i|
s = format % i
for digit in s.scan(/./)
bins[digit.to_i] +=1 unless digit == ' '
end
end
bins
end
p digits_for_range(1, 49, false)
# => [4, 15, 15, 15, 15, 5, 5, 5, 5, 5]
p digits_for_range(1, 49, true)
# => [13, 15, 15, 15, 15, 5, 5, 5, 5, 5]
p digits_for_range(1, 10000, false)
# => [2893, 4001, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000]
Ruby 1.8, un langage connu pour être "lent", exécute le code ci-dessus en 0,135 seconde. Cela inclut le chargement de l'interpréteur. N'abandonnez pas un algorithme évident, sauf si vous avez besoin de plus de vitesse.
Vous pouvez séparer chaque chiffre ( regardez ici pour un exemple ), créer un histogramme avec des entrées de 0 à 9 (qui comptera le nombre de chiffres apparaissant dans un nombre) et multiplier par le nombre de 'nombres ' a demandé.
Mais si ce n'est pas ce que vous cherchez, pouvez-vous donner un meilleur exemple?
Édité:
Maintenant, je pense que j'ai eu le problème. Je pense que vous pouvez en tenir compte (pseudo C):
int histogram[10];
memset(histogram, 0, sizeof(histogram));
for(i = startNumber; i <= endNumber; ++i)
{
array = separateDigits(i);
for(j = 0; k < array.length; ++j)
{
histogram[k]++;
}
}
Des chiffres séparés implémentent la fonction dans le lien.
Chaque position de l'histogramme aura la quantité de chaque chiffre. Par exemple
histogram[0] == total of zeros
histogram[1] == total of ones
...
Cordialement