Imagine that you are selling these metal numbers used to indicate homes, locker doors, hotel rooms, etc. You need to find how many of each digit should be sent when your client needs to indicate the door / house:
- from 1 to 100
- 51 to 300
- 1 to 2000 with zeros on the left
The obvious solution is to loop from the first to the last number, convert the counter to a string with or without zeros on the left, extract each digit and use it as an index to increase the array of 10 integers.
I wonder if there is a better way to solve this problem without having to quote the entire range of integers.
Permissions in any language or pseudo-code are welcome.
Edit:
Review Overview
John at CashCommons and Wayne Conrad note that my current approach is good and fast enough. Let me use a stupid analogy: if you were given the task of counting squares on a chessboard in less than 1 minute, you could finish the task by counting the squares one by one, but the best solution would be to count the sides and do the multiplication, because you may be asked later Count the tiles in the building.
Alex Reisner points to a very interesting mathematical law, which, unfortunately, does not seem to be relevant to this problem.
Andres suggests using the same algorithm that Im uses, but it extracts digits using% 10 instead of substrings.
John at CashCommons and phord offer a preliminary calculation of the required numbers and their storage in the look-up table or for raw speed - an array. This could be a good solution if we had an absolute, immutable, set in stone, maximum integer value. I have never seen one of them. The high performance mark and strainer have calculated the necessary numbers for different ranges. A result for one million seems to indicate that there is a fraction, but results for a different number show different proportions.
the filter found some formulas that can be used to count a digit for a number that is ten. Robert Harvey had a very interesting experience publishing a question in MathOverflow. One of the mathematicians wrote the solution using mathematical notation.
Aaronach developed and tested the solution using math. After publishing, he looked at the formulas that emerged from Math Overflow, and found a flaw in it (point to Stackoverflow :).
Noaklavin developed the algorithm and presented it in pseudo-code.
New solution
After reading all the answers and doing some experiments, I found that for a range of integers from 1 to 10 n -1:
- Digits 1 through 9 require fragments n * 10 (n-1)
- For the digit 0, if you do not use leading zeros, you need n * 10 n-1 - ((10 n -1) / 9)
- For digit 0, if leading zeros are used, n * 10 n-1 - n is necessary
The first formula was found by the retina (and probably by others), and I found the other two by trial and error (but they can be included in other answers).
For example, if n = 6, the range is from 1 to 999,999:
- For numbers 1 through 9, we need 6 * 10 5 = 600,000 of each
- For the digit 0 without leading zeros, we need 6 * 10 5 - (10 6 -1) / 9 = 600 000 - 111 111 = 488 889
- For the digit 0 with leading zeros, we need 6 * 10 5 - 6 = 599.994
These numbers can be verified using highly effective marking results.
Using these formulas, I improved the original algorithm. It is still in the range from the first to the last number in the range of integers, but if it finds a number that is a power of ten, it uses formulas to add to the numbers, counting the number for the full range from 1 to 9 or from 1 to 99 or from 1 to 999, etc. Here's the algorithm in pseudo code:
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
For example, for a range from 786 to 3.021, the counter will be increased:
- In 1 from 786 to 790 (5 cycles)
- K 9 from 790 to 799 (1 cycle)
- In 1 from 799 to 800
- K 99 from 800 to 899
- In 1 from 899 to 900
- K 99 from 900 to 999
- In 1 from 999 to 1000
- K 999 from 1000 to 1999
- By 1 year from 1999 to 2000
- By 999, from 2000 to 2999.
- In 1 from 2999 to 3000
- In 1 from 3000 to 3010 (10 cycles)
- In 9 from 3010 to 3019 (1 cycle)
- In 1 from 3019 to 3021 (2 cycles)
Total: 28 cycles. Without optimization: 2,235 cycles.
Note that this algorithm solves the problem without leading zeros. To use it with leading zeros, I used a hack:
If you need a range from 700 to 1000 with leading zeros, use the algorithm for 10 700-11 000, and then subtract 1000 - 700 = 300 from the number 1.
Source code and source code
I checked the original approach, the same approach using% 10 and a new solution for some large ranges, with these results:
Original 104.78 seconds
With% 10 83.66
With Powers of Ten 0.07
Screenshot of the test application:
alt text http://clarion.sca.mx/images/stories/digitsbench.png
If you want to see the full source code or run the test, use the following links:
Accepted answer
the noahlavine solution may be correct, but I just could not follow the pseudo-code, I think some details are missing or not fully explained.
Aaronaught's solution seems to be correct, but the code is too complicated for my taste.
I accepted the answers from the filters because his thought prompted me to develop this new solution.