Представьте, что вы продаете эти металлические цифры, используемые для обозначения домов, дверей шкафчиков, гостиничных номеров и т.д. Вам нужно найти, сколько из каждой цифры должно быть отправлено, когда вашему клиенту нужно указывать двери/дома:
- от 1 до 100
- от 51 до 300
- от 1 до 2000 с нулями слева
Очевидным решением является выполнение цикла от первого до последнего числа, преобразование счетчика в строку с нулями или без них слева, извлечение каждой цифры и использование ее в качестве индекса для увеличения массива из 10 целых чисел.
Интересно, есть ли лучший способ решить эту проблему, без необходимости цитировать весь диапазон целых чисел.
Разрешения на любом языке или псевдокоде приветствуются.
Изменить:
Обзор отзывов
Джон в CashCommons и Уэйн Конрад отмечают, что мой нынешний подход хорош и достаточно быстр. Позвольте мне использовать глупую аналогию: если вам была дана задача подсчета квадратов на шахматной доске менее чем за 1 минуту, вы могли бы закончить задачу, посчитав квадраты один за другим, но лучшим решением будет подсчет сторон и сделайте умножение, потому что позже вас могут попросить подсчитать плитки в здании.
Алекс Рейснер указывает на очень интересный математический закон, который, к сожалению, кажется, не имеет отношения к этой проблеме.
Андрес предлагает использовать тот же алгоритм, который использует Im, но извлекает цифры с помощью% 10 вместо подстрок.
John at CashCommons и phord предлагают предварительное вычисление требуемых цифр и их хранение в справочной таблице или для сырой скорости - массив. Это могло бы быть хорошим решением, если бы у нас было абсолютное, неизменяемое, установленное в камне, максимальное целочисленное значение. Я никогда не видел одного из них.
Высокопроизводительный знак и сетчатый фильтр рассчитали необходимые цифры для разных диапазонов. Результат за один миллион, по-видимому, указывает, что есть доля, но результаты для другого числа показывают разные пропорции.
фильтр нашел некоторые формулы, которые можно использовать для подсчета цифры для числа, которое составляет десять.
У Роберта Харви был очень интересный опыт публикации вопроса в MathOverflow. Один из математиков написал решение, используя математическую нотацию.
Aaronach разработал и протестировал решение с использованием математики. После публикации он просмотрел формулы, возникшие из Math Overflow, и обнаружил в нем недостаток (укажите на Stackoverflow:).
Ноаклавин разработал алгоритм и представил его в псевдокоде.
Новое решение
Прочитав все ответы и сделав несколько экспериментов, я обнаружил, что для диапазона целых чисел от 1 до 10 n -1:
- Для цифр от 1 до 9 требуются фрагменты n * 10 (n-1)
- Для цифры 0, если не использовать начальные нули, необходимо n * 10 n-1 - ((10 n -1)/9)
- Для цифры 0, если используются начальные нули, необходимо n * 10 n-1 - n
Первая формула была найдена сетчаткой (и, вероятно, другими), и я нашел два других путем проб и ошибок (но они могут быть включены в другие ответы).
Например, если n = 6, диапазон от 1 до 999,999:
- Для цифр с 1 по 9 нам нужно 6 * 10 5= 600 000 из каждого
- Для цифры 0 без начальных нулей нам нужно 6 * 10 5 - (10 6 -1)/9 = 600 000 - 111 111 = 488 889
- Для цифры 0 с начальными нулями нам нужно 6 * 10 5 - 6 = 599,994
Эти цифры можно проверить с помощью результатов высокоэффективной маркировки.
Используя эти формулы, я улучшил исходный алгоритм. Он по-прежнему находится в диапазоне от первого до последнего числа в диапазоне целых чисел, но если он найдет число, которое является степенью десяти, он использует формулы для добавления к цифрам, подсчитывая количество для полного диапазона от 1 до 9 или от 1 до 99 или от 1 до 999 и т.д. Здесь алгоритм в псевдокоде:
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
Например, для диапазона от 786 до 3,021 счетчик будет увеличен:
- В 1 от 786 до 790 (5 циклов)
- К 9 от 790 до 799 (1 цикл)
- В 1 от 799 до 800
- К 99 от 800 до 899
- В 1 от 899 до 900
- К 99 от 900 до 999
- В 1 от 999 до 1000
- К 999 от 1000 до 1999
- К 1 году с 1999 по 2000 год
- К 999 году с 2000 по 2999 год.
- В 1 от 2999 до 3000
- В 1 от 3000 до 3010 (10 циклов)
- В 9 от 3010 до 3019 (1 цикл)
- В 1 от 3019 до 3021 (2 цикла)
Всего: 28 циклов Без оптимизации: 2,235 циклов
Заметим, что этот алгоритм решает проблему без начальных нулей. Чтобы использовать его с ведущими нулями, я использовал хак:
Если требуется диапазон от 700 до 1000 с начальными нулями, используйте алгоритм для 10 700-11 000, а затем вычитайте 1000 - 700 = 300 из числа цифр 1.
Исходный код и исходный код
Я проверил оригинальный подход, тот же подход с использованием% 10 и нового решения для некоторых больших диапазонов, с этими результатами:
Original 104.78 seconds With %10 83.66 With Powers of Ten 0.07
Снимок экрана тестового приложения:
alt text http://clarion.sca.mx/images/stories/digitsbench.png
Если вы хотите увидеть полный исходный код или запустить тест, используйте следующие ссылки:
- Полный исходный код (в Clarion): http://sca.mx/ftp/countdigits.txt
- Компилируемый проект и win32 exe: http://sca.mx/ftp/countdigits.zip
Принятый ответ
noahlavine решение может быть правильным, но я просто не мог следовать псевдокоду, я думаю, что некоторые детали отсутствуют или не полностью объяснены.
Решение Aaronaught кажется правильным, но код слишком сложный для моего вкуса.
Я принял ответы от фильтров, потому что его мысль побудила меня разработать это новое решение.