Найдите наименьшее регулярное число, которое не меньше N

Обычные числа - это числа, которые равномерно делят степень 60. Например, 60 2 = 3600 = 48 × 75, поэтому 48 и 75 являются делителями степени 60. Таким образом, они также являются обычными числами.

Это расширение округления до следующей степени двойки.

У меня есть целочисленное значение N, которое может содержать большие простые множители, и я хочу округлить его до числа, состоящего только из маленьких простых множителей (2, 3 и 5).

Примеры:

  • f(18) == 18 == 21 * 32
  • f(19) == 20 == 22 * 51
  • f(257) == 270 == 21 * 33 * 51

Что было бы эффективным способом найти наименьшее число, удовлетворяющее этому требованию?

Используемые значения могут быть большими, поэтому я бы не хотел перечислять все обычные числа, начиная с 1, и не поддерживать массив всех возможных значений.


person finnw    schedule 11.02.2012    source источник
comment
Что ты пробовал? Читали ли вы цитаты в разделе алгоритмов статьи в Википедии, на которую вы указали ссылку, или связанная статья о гладких числах?   -  person Jordan Running    schedule 11.02.2012
comment
@Jordan да, я знаком с ленивой функциональной техникой для генерации всех регулярных чисел (которые можно было бы использовать как решение моей проблемы методом перебора). Я также читал часть об оценке количества гладких чисел в диапазоне. Как вы думаете, это может быть здесь полезно? Если да, то не стесняйтесь вставлять это в ответ!   -  person finnw    schedule 11.02.2012
comment
Также известные как числа Хэмминга уродливые числа и 5-гладкие числа. Полезно для выбора размеров данных для выполнения БПФ.   -  person endolith    schedule 30.09.2013


Ответы (8)


Хорошо, надеюсь, в третий раз здесь очарование. Рекурсивный алгоритм ветвления для начального ввода p, где N - число, которое «строится» в каждом потоке. NB 3a-c здесь запускаются как отдельные потоки или иным образом (квази) асинхронно.

  1. Вычислите следующую по величине степень двойки после p, назовите это R. N = p.

  2. N> R? Выйти из этой ветки. Состоит ли p только из малых простых множителей? Готово. В противном случае переходите к шагу 3.

  3. После любого из 3a-c переходите к шагу 4.

    a) Округлить p до ближайшего кратного 2. Это число можно выразить как m * 2.
    b) Округлить p до ближайшего кратного 3. Это число может быть выражено как m * 3.
    c) Округлите p до ближайшего кратного 5. Это число можно выразить как m * 5.

  4. Перейдите к шагу 2 с p = m.

Я пропустил бухгалтерию, связанную с отслеживанием N, но, как я понимаю, это довольно просто.

Изменить: забыл 6, спасибо ypercube.

Изменить 2: если бы это было до 30, (5, 6, 10, 15, 30) поняли, что это было ненужно, удалили это.

Редактировать 3: (Последнее, что я обещаю!) Добавлена ​​проверка степени 30, которая помогает предотвратить использование этого алгоритма всей вашей оперативной памяти.

Редактировать 4: Изменено значение power-30 на power-of-2, за наблюдение finnw.

person Matt Phillips    schedule 11.02.2012
comment
На шаге 1 нельзя использовать следующую по величине степень 2 вместо 30? - person finnw; 11.02.2012
comment
@finnw Да, ты прав. Нарушаю свое обещание и редактирую соответственно. - person Matt Phillips; 11.02.2012
comment
Вы реализовали это? Я попытался проследить, как будет работать этот алгоритм при N = 1025; Лучшее решение - 1080, но не думаю, что оно его найдет. - person finnw; 12.02.2012
comment
@finnw По общему признанию, нет, но для вашего примера вы получите следующую последовательность: 1025 - ›1026 = 2 x 513 -› 514 = 2 x 257 - ›258 = 2 x 129 -› 129 = 3 x 43 - ›45 = 3 x 15 - ›15 = 3 x 5. Тогда N в этой точке = 2 x 2 x 2 x 3 x 3 x 3 x 5 = 1080. Ключевым моментом является то, что в некоторых случаях« округление в большую сторону »бессмысленно, если множитель уже присутствует. Теперь будет сгенерировано много путей, и ваш пример заставляет меня понять, что первый путь, который нужно завершить, не всегда может иметь наименьшее значение N. Поэтому я думаю, вам нужно подождать, пока all потоки завершаются, сортируются и берут самый низкий. - person Matt Phillips; 12.02.2012
comment
@finnw Но следующая степень 2 = 1089, поэтому я не думаю, что это приведет к комбинаторной катастрофе. - person Matt Phillips; 12.02.2012
comment
@finnw Исправление, вам не нужно сохранять их все и сортировать, вы просто сохраняете запись текущего наименьшего N и обновляете, когда появляется лучший. - person Matt Phillips; 12.02.2012
comment
Хорошо, я предположил, что он будет начинаться с 1025 - ›25 × 41, что верно, но приводит к худшему решению. - person finnw; 12.02.2012
comment
Эти примечания к редактированию сбивают с толку, предположительно потому, что упомянутые вещи больше не существуют? Почему бы просто не оставить их в истории редактирования? Вот для чего это нужно. - person endolith; 01.10.2013
comment
Этот ответ работает только в том случае, если он начинается с p = 1 (с некоторыми проверками действительности для входных целых чисел меньше единицы), который не указан в тексте, и неэффективен по сравнению с более поздними ответами WillNess и @endolith, которые только зацикливаются на две из трех переменных, поскольку третья подразумевается двумя другими. - person GordonBGood; 25.08.2016

Можно произвольно создать срез последовательности Хэмминга вокруг n-го члена во времени ~ n^(2/3) путем прямого перечисления троек (i,j,k) такое, что N = 2^i * 3^j * 5^k.

Алгоритм работает с log2(N) = i+j*log2(3)+k*log2(5); перечисляет все возможные k и для каждого, все возможные j, находит верхний i и, таким образом, тройку (k,j,i) и сохраняет его в «полосе», если внутри заданной «ширины» ниже заданного высокого логарифмического верхнего значения (когда width ‹1 может быть не более одного такого i), затем сортирует их по логарифму.

WP говорит, что n ~ (log N)^3, то есть время выполнения ~ (log N)^2. Здесь нас не заботит точное положение найденной тройки в последовательности, поэтому все вычисления подсчета из исходный код можно выбросить:

slice hi w = sortBy (compare `on` fst) b where       -- hi>log2(N) is a top value
  lb5=logBase 2 5 ; lb3=logBase 2 3                  -- w<1 (NB!) is log2(width)
  b  = concat                                        -- the slice
      [ [ (r,(i,j,k)) | frac < w ]                   -- store it, if inside width
        | k <- [ 0 .. floor ( hi   /lb5) ],  let p = fromIntegral k*lb5,
          j <- [ 0 .. floor ((hi-p)/lb3) ],  let q = fromIntegral j*lb3 + p,
          let (i,frac)=properFraction(hi-q) ;    r = hi - frac ]   -- r = i + q
                    -- properFraction 12.7 == (12, 0.7)

-- update: in pseudocode:
def slice(hi, w):
    lb5, lb3 = logBase(2, 5), logBase(2, 3)  -- logs base 2 of 5 and 3
    for k from 0 step 1 to floor(hi/lb5) inclusive:
        p = k*lb5
        for j from 0 step 1 to floor((hi-p)/lb3) inclusive:
           q = j*lb3 + p
           i = floor(hi-q)
           frac = hi-q-i                     -- frac < 1 , always
           r = hi - frac                     -- r == i + q
           if frac < w:
              place (r,(i,j,k)) into the output array
   sort the output array's entries by their "r" component
        in ascending order, and return thus sorted array

Перечислив тройки в срезе, можно легко выполнить сортировку и поиск, требуя практически O(1) времени (для произвольно тонкого среза), чтобы найти первую тройку выше N. На самом деле, для постоянной ширины (логарифмической) количество чисел в срезе (элементы «верхней корки» в (i,j,k)-пространстве ниже log(N) плоскости) снова m ~ n^2/3 ~ (log N)^2, а сортировка занимает m log m времени (так что поиск, даже линейный, тогда требуется ~ m времени выполнения). Но ширина может быть уменьшена для большего Ns, следуя некоторым эмпирическим наблюдениям; а постоянные коэффициенты для перебора троек в любом случае намного выше, чем для последующей сортировки.

Даже с постоянной шириной (логарифмической) он выполняется очень быстро, вычисляя 1 000 000-е значение в последовательности Хэмминга мгновенно и миллиардный за 0,05 с.

Первоначальная идея «верхней полосы троек» принадлежит Луи Клаудеру, как цитируется в моем опубликовать в блогах DDJ в 2008 году.

обновление: как указано GordonBGood в комментарии, нет необходимости во всей группе, а скорее одно или два значения выше и ниже целевого. С этой целью алгоритм легко корректируется. Входные данные также должны быть проверены на предмет того, что они являются числом Хэмминга перед продолжением алгоритма, чтобы избежать проблем с округлением с двойной точностью. Нет проблем с округлением при сравнении логарифмов чисел Хэмминга, которые, как было известно заранее, отличаются (хотя приближается к триллионной записи в последовательности используется около 14 значащих цифр в логарифмических значениях, оставляя только 1-2 цифры в запасе, поэтому на самом деле ситуация может быть сомнительной; но для 1-миллиардной нам нужно только 11 значащих цифр).

update2: оказывается, что двойная точность логарифмов ограничивает это числами от 20 000 до 40 000 десятичных цифр (т. е. от 10 до 100 триллионных чисел Хэмминга). Если в этом есть реальная необходимость для таких больших чисел, алгоритм можно переключить обратно на работу с самими целочисленными значениями, а не с их логарифмами, что будет медленнее.

person Will Ness    schedule 20.08.2012
comment
Хотел бы я понять Haskell. : / На первый взгляд это лучший ответ. - person endolith; 04.10.2013
comment
@endolith, это очень простые вещи. f x y - это приложение-функция, f(x,y). понимание списка [x | p x] - это список, содержащий единицу x, если p(x) истинно; в противном случае пусто. понимание списка [x | k <- [0..10], j<- [k..10], let x=k+j*42] извлекает каждый k из диапазона от 0 до 10, и для каждого k он извлекает каждое j из диапазона от k до 10 (как если бы там было два вложенных цикла). properFraction является встроенным, например 3.14 он возвращает пару (3,0.14). fst(a,b) == a - еще один встроенный. concat объединяет списки в данном списке, чтобы сформировать один список: [[1],[],[3]] --> [1,3]. - person Will Ness; 04.10.2013
comment
@endolith, наконец, fromIntegral i*x is fromIntegral(i) * x is just i*x, где i - значение некоторого целочисленного типа, а x некоторый плавающий тип. fromIntegral похож на приведение типа; нам не разрешено напрямую умножать int и double в Haskell. поэтому алгоритм идет от log2(N) = i+j*log2(3)+k*log2(5); перечисляет все возможные k и для каждого, все возможные j, находит верхний i и, таким образом, тройку (k,j,i) и сохраняет его, если внутри заданной ширины ниже заданного high логарифмического верхнего значения (когда width < 1 может быть только одно такое i), затем сортирует их по их логвалам. - person Will Ness; 04.10.2013
comment
Дает ли это правильный результат для n = 11? Я пытался сделать это на ideone, и он выводит (0,1,1), что, я думаю, означает 3 * 5 = 15, но следующее обычное число - 12, что будет (2,1,0) = 2 ^ 2 * 3. Также это должно быть следующее обычное число ›= to n, но, похоже, это вычисляет следующее обычное число› n? - person endolith; 11.05.2014
comment
@endolith, запускающий эту запись ideone с входом 11, дает 11-е число в последовательности Хэмминга, основанное на 1. Запуск его с символом «а» в качестве входных данных дает первые 20 элементов в последовательности: [1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25 , 27,30,32,36]. Как видите, там 11-е число 15. - person Will Ness; 11.05.2014
comment
Что здесь не указано, так это то, что нам вообще не нужно сохранять бэнд, поскольку мы можем просто проверить каждый ‹= вход. Также не указана проблема использования представления журнала в отношении точности: с ошибками округления: если входное значение уже является обычным числом, сравнение приближения журнала может обнаружить, что приблизительный журнал либо немного завышен, либо немного занижен, чем логарифмическая аппроксимация входного ответа. Чтобы решить эту проблему, нужно сохранить пару значений выше и пару ниже входного значения, а затем в качестве последнего шага сканирования для минимума действительного числа ‹= входного. - person GordonBGood; 25.08.2016
comment
@GordonBХорошая точность достаточно хороша для сравнения логарифмов довольно больших (насколько велики? Интересный вопрос) чисел Хэмминга, известных как разные, поэтому просто нужно предварительно протестировать ввод на предмет того, что он уже является числом Хэмминга . Тогда вы, конечно, правы, сохраните пару значений выше целевого логарифма и одного ниже достаточно, а после перечисления диапазонов выполните пару сравнений bigint. - person Will Ness; 25.08.2016
comment
@GordonBGood Расчет 1Т числа, похоже, использует 13 значащих цифр в двойных логвалах. - person Will Ness; 25.08.2016
comment
@GordonBХорошее исправление: 14 значащих цифр (см. Новый результат внизу страницы). - person Will Ness; 26.08.2016
comment
@WillNess, как уже отмечалось, мои расчеты показывают, что потребность в большем количестве цифр растет очень медленно с увеличением диапазона, и что, вероятно, существует достаточная точность, поэтому у нас закончится время, прежде чем у нас закончится место. Я думаю, что большинству будет достаточно, если мы сможем использовать HammingRound для ввода миллионов цифр за разумное время. Теперь я думаю, что мы должны рассчитать нижнюю и верхнюю границы на основе точности ошибки, чтобы гарантировать, что есть хотя бы одно значение, меньшее или равное или большее, чем ввод, а затем вернуться к помещению их в отсортированный диапазон перед окончательной квалификацией. - person GordonBGood; 26.08.2016
comment
Я также думаю, что просто предварительно протестировать ввод на предмет того, что он является числом Хэмминга, уже не является хорошей идеей, поскольку для этого требуется много операций bigint: многократное деление на 5 и 3 до тех пор, пока остаток не будет равен нулю, а затем убедиться, что последний остаток - степень двойки. Поскольку мы уже создадим диапазон значений, охватывающий это число, последняя проверка найдет, является ли входное число одним из них, с помощью всего лишь нескольких сравнений bigint. - person GordonBGood; 26.08.2016
comment
@GordonBGood BigFloat ошибся. он в 1000 раз медленнее, чем Double. Вместо этого я добавил проверку для isBandTrulySorted = let a=map (trival.snd) s in and $ zipWith (>) a (tail a), которая вычисляет фактические целочисленные значения из всех троек в отсортированном бэнде и сравнивает их, чтобы проверить, действительно ли бэнд был отсортирован. 100T все еще было отсортировано (3,3 минуты), но 1000 триллионов было нет (время выполнения на моем ящике 15,2 минуты). - person Will Ness; 27.08.2016
comment
Мне нужно поспать над такого рода проблемами, прежде чем придумать ответ. Я думаю, что у нас все в порядке, если мы расширим точность от использования 52-битного положительного мантиса double до использования псевдодесятичного Word64 logrep с псевдодесятичным смещением в двоичных разрядах 'x', оставляя двоичные места '64 -x 'для всей части . Это может быть немного быстрее в 64-битном коде, так как при использовании целочисленных операций, а не операций FP, хотя и медленнее в 32-битном коде для 64-битных операций. Тогда 24-битная целая часть и 40-битный гидроразрыв должны выполнить свою работу. Я напишу код за день или два, но займу немного личного времени. - person GordonBGood; 28.08.2016
comment
@GordonBGood или просто переключитесь обратно к целочисленным значениям из их логарифмов, так что сортировка не будет проблемой и замедление, надеюсь, не будет серьезным. Тогда единственная оставшаяся проблема - это перечисление, которое тоже придется как-то подправить. И здесь, как вы отметили, сортировка вообще не нужна, так что реализация вашего предложения поможет. Но есть ли какая-то потребность в вообще для этого с числами больше примерно 20 000 десятичных знаков - это совершенно другой вопрос. :) - person Will Ness; 28.08.2016
comment
Ваша проверка isSorted на самом деле довольно хороша, поскольку полоса, вероятно / почти наверняка, будет содержать некоторые значения с низким j / k, а некоторые с высоким. Использование этого показало, что точность только 40 двоичных десятичных цифр недостаточна, поэтому logrep должен быть больше 64 бит для расширенных диапазонов. Вероятно, есть некоторая выгода от использования целых чисел разной точности вместо целых чисел напрямую из-за значительно уменьшенного диапазона журналов, но мое тестирование показывает, что он все еще в 20 раз медленнее. Вы, вероятно, правы, что мы должны просто ограничить это выходными значениями 50 000 цифр или около того., Особенно для этого вопроса. - person GordonBGood; 29.08.2016
comment
WillNess, пожалуйста, взгляните на мой ответ, который включает коды Python и Haskell. Эти коды вообще не используют память или целочисленные вычисления, и вы увидите завершение моей идеи по минимизации диапазона сравнения до нескольких значений выше целевого. Я реализовал ваше решение с отсортированными полосами ошибок, а затем выбрал соответствие критериям минимума ›= цели с помощью двоичного поиска, чтобы минимизировать количество сравниваемых значений, но с успехом этой идеи с чрезвычайно узкой полосой нет Похоже, в этом нет никакого смысла. - person GordonBGood; 22.11.2018
comment
@GordonBХорошо интересно, спасибо; когда у меня будет шанс. :) - person Will Ness; 22.11.2018

Вот решение на Python, основанное на ответе Несса, но с некоторыми сокращениями и использованием чистой целочисленной математики, чтобы избежать попадания в пространство журнала ошибки числовой точности:

import math

def next_regular(target):
    """
    Find the next regular number greater than or equal to target.
    """
    # Check if it's already a power of 2 (or a non-integer)
    try:
        if not (target & (target-1)):
            return target
    except TypeError:
        # Convert floats/decimals for further processing
        target = int(math.ceil(target))

    if target <= 6:
        return target

    match = float('inf') # Anything found will be smaller
    p5 = 1
    while p5 < target:
        p35 = p5
        while p35 < target:
            # Ceiling integer division, avoiding conversion to float
            # (quotient = ceil(target / p35))
            # From https://stackoverflow.com/a/17511341/125507
            quotient = -(-target // p35)

            # Quickly find next power of 2 >= quotient
            # See https://stackoverflow.com/a/19164783/125507
            try:
                p2 = 2**((quotient - 1).bit_length())
            except AttributeError:
                # Fallback for Python <2.7
                p2 = 2**(len(bin(quotient - 1)) - 2)

            N = p2 * p35
            if N == target:
                return N
            elif N < match:
                match = N
            p35 *= 3
            if p35 == target:
                return p35
        if p35 < match:
            match = p35
        p5 *= 5
        if p5 == target:
            return p5
    if p5 < match:
        match = p5
    return match

На английском языке: перебирайте каждую комбинацию 5 и 3, быстро находя следующую степень 2> = цель для каждой пары и сохраняя наименьший результат. (Это пустая трата времени - перебирать все возможные кратные 2, если только одно из них может быть правильным). Он также возвращается раньше, если когда-либо обнаруживает, что цель уже является обычным числом, хотя в этом нет строгой необходимости.

Я протестировал его довольно тщательно, проверяя каждое целое число от 0 до 51200000 и сравнивая со списком в OEIS http://oeis.org/A051037, а также многие большие числа, которые ± 1 от обычных чисел, и т. д. Это теперь доступен в SciPy как fftpack.helper.next_fast_len, чтобы найти оптимальные размеры для БПФ (исходный код).

Я не уверен, что метод журнала работает быстрее, потому что мне не удалось заставить его работать достаточно надежно, чтобы проверить его. Думаю, у него такое же количество операций? Я не уверен, но это достаточно быстро. Требуется ‹3 секунды (или 0,7 секунды с gmpy), чтобы вычислить, что 2 142 × 3 80 × 5 444 - следующее обычное число выше 2 2 × 3 454 × 5 249 +1 (100000000-е регулярное число, состоящее из 392 цифр)

person endolith    schedule 29.10.2013
comment
Вы правы в том, что метод журнала выполняет примерно такое же количество операций, только намного быстрее, поскольку ему не приходится иметь дело с математикой с множественной точностью. Проблема с тем, чтобы заставить его работать, заключается в том, что это приближения, и особенно, если входное значение уже является обычным числом, определенный журнал для него по сравнению с сгенерированным журналом обычных номеров может не совсем совпадать из-за ошибок округления. Решение состоит в том, чтобы добавить немного логики, чтобы сохранить пару значений просто ‹= и пару только› для сравнения журналов, а затем в качестве последнего шага преобразовать их в bigint и найти min ›= входное значение. - person GordonBGood; 25.08.2016
comment
@GordonBGood Звучит неплохо. Хотите опубликовать ответ? :) - person endolith; 25.08.2016
comment
точности обычно достаточно для сравнения довольно больших чисел Хэмминга, которые известны как разные. Здесь просто предварительно проверьте ввод, является ли он уже обычным числом или нет. - person Will Ness; 25.08.2016
comment
Работа над ответом; необходимо оценить разницу в точности между приблизительным бревном, определенным напрямую, и бревном, вычисленным с помощью обычных числовых циклов. @WillNess, да, точность достаточна для сравнения очень больших чисел Хэмминга (10 миллионов цифр или около того), поскольку они становятся очень разреженными с диапазоном, но это необходимо сравнить с приблизительным логарифмом входного значения, определенным другими способами (для номер ввода), который не имеет тех же условий ошибки. - person GordonBGood; 26.08.2016
comment
@GordonBGood, вы пропустили мой другой недавний комментарий? на 1T (~ 8500 цифр) остается место только для 2-3 значащих цифр в значениях логарифма (с двойными, конечно; всегда можно переключиться на отношения, но это будет медленнее). мы говорим о сравнении логвала, да? нет возможности для накопления ошибок в циклах, поскольку значения не накапливаются с помощью (+), а пересчитываются с помощью (*). для 1T-го максимальное значение логарифма составляет 28052,292341476037, а минимальное расстояние между логвалами в полосе составляет 2,9831426218152046e-9. Так что, что еще хуже, осталось около 1 запасной цифры. - person Will Ness; 26.08.2016
comment
@WillNess, Мое мышление было основано на 53-битной точности с одним битом для двойного. Если значения lb3 и lb5 log2 подходят для каждого бита, журнал '2' является точным, а logrep представляет собой сумму кратного log2 3 и кратного log2 5 (с меньшими кратными для данного диапазона для больших). , то точность будет около 26 бит для значения log2, равного примерно 64 миллионам или примерно 20 миллионам десятичных цифр, хотя это медленно. Я думаю, что для очень больших диапазонов требуемые дополнительные цифры растут очень медленно, поэтому удвоения может быть достаточно. - person GordonBGood; 26.08.2016
comment
@WillNess, для исходного алгоритма ошибка должна быть достаточно низкой, чтобы абсолютная ошибка в max j * lb3 и max k * lb5 никогда не превышала 1 (разница между последовательными степенями 2 в значениях log2) или ошибки друг друга для меньшие значения j и k, чтобы относительные сравнения были точными. Используя калькулятор Windows, значения lb3 и lb5 кажутся достаточно хорошими, поэтому нам не нужно беспокоиться примерно до 2 ^ 10 ^ 15 или около того, или около миллиардов цифр, и мы никогда даже не захотим вычислить больше, чем несколько миллионов за один В любом случае несколько дней;) Я думаю, что это адекватно. - person GordonBGood; 26.08.2016
comment
Неужели нужно использовать float('inf')? Разве между target и 2 * target нет степени двойки? - person Dror Speiser; 10.08.2018
comment
@DrorSpeiser Что-нибудь вредит от использования бесконечности? Он найдет следующую степень двойки внутри цикла - person endolith; 11.08.2018
comment
@endolith: нет, конечно нормально. Все остальное делается целыми числами, вот и все :) - person Dror Speiser; 11.08.2018
comment
@endolith, см. мой ответ, в котором эта работа продвигается вперед и выполняется быстрее за счет использования логарифмов для большей части исключения операций bigint. - person GordonBGood; 25.11.2018

Вы хотите найти наименьшее число m, то есть m >= N и m = 2^i * 3^j * 5^k, где все i,j,k >= 0.

Логарифмируя уравнения можно переписать как:

 log m >= log N
 log m = i*log2 + j*log3 + k*log5

Вы можете вычислить log2, log3, log5 и logN с (достаточно высокой, в зависимости от размера N) точностью. Тогда эта проблема выглядит как проблема целочисленного линейного программирования, и вы можете попытаться решить ее с помощью одного известных алгоритмов для этой NP-трудной задачи.

person ypercubeᵀᴹ    schedule 11.02.2012
comment
Преобразованная задача (в общем) NP-трудна. Этот конкретный пример общей проблемы целочисленного программирования может иметь более приятные решения. Или исходная (не преобразованная) задача теории чисел может иметь более приятный алгоритм. - person ypercubeᵀᴹ; 11.02.2012

ИСПРАВЛЕНО / ИСПРАВЛЕНО: исправлены коды для передачи scipy tests:

Вот ответ, основанный на ответе эндолита, но почти исключающий длинные целочисленные вычисления с разной точностью, используя представления логарифма float64 для создания базы сравнение, чтобы найти тройные значения, которые соответствуют критериям, прибегая к сравнениям с полной точностью только тогда, когда есть вероятность, что значение логарифма может быть недостаточно точным, что происходит только тогда, когда цель очень близка либо к предыдущему, либо к следующему регулярному числу:

import math

def next_regulary(target):
    """
    Find the next regular number greater than or equal to target.
    """
    if target < 2: return ( 0, 0, 0 )
    log2hi = 0
    mant = 0
    # Check if it's already a power of 2 (or a non-integer)
    try:
        mant = target & (target - 1)
        target = int(target) # take care of case where not int/float/decimal
    except TypeError:
        # Convert floats/decimals for further processing
        target = int(math.ceil(target))
        mant = target & (target - 1)

    # Quickly find next power of 2 >= target
    # See https://stackoverflow.com/a/19164783/125507
    try:
        log2hi = target.bit_length()
    except AttributeError:
        # Fallback for Python <2.7
        log2hi = len(bin(target)) - 2

    # exit if this is a power of two already...
    if not mant: return ( log2hi - 1, 0, 0 )

    # take care of trivial cases...
    if target < 9:
        if target < 4: return ( 0, 1, 0 )
        elif target < 6: return ( 0, 0, 1 )
        elif target < 7: return ( 1, 1, 0 )
        else: return ( 3, 0, 0 )

    # find log of target, which may exceed the float64 limit...
    if log2hi < 53: mant = target << (53 - log2hi)
    else: mant = target >> (log2hi - 53)
    log2target = log2hi + math.log2(float(mant) / (1 << 53))

    # log2 constants
    log2of2 = 1.0; log2of3 = math.log2(3); log2of5 = math.log2(5)

    # calculate range of log2 values close to target;
    # desired number has a logarithm of log2target <= x <= top...
    fctr = 6 * log2of3 * log2of5
    top = (log2target**3 + 2 * fctr)**(1/3) # for up to 2 numbers higher
    btm = 2 * log2target - top # or up to 2 numbers lower

    match = log2hi # Anything found will be smaller
    result = ( log2hi, 0, 0 ) # placeholder for eventual matches
    count = 0 # only used for debugging counting band
    fives = 0; fiveslmt = int(math.ceil(top / log2of5))
    while fives < fiveslmt:
        log2p = top - fives * log2of5
        threes = 0; threeslmt = int(math.ceil(log2p / log2of3))
        while threes < threeslmt:
            log2q = log2p - threes * log2of3
            twos = int(math.floor(log2q)); log2this = top - log2q + twos

            if log2this >= btm: count += 1 # only used for counting band
            if log2this >= btm and log2this < match:
                # logarithm precision may not be enough to differential between
                # the next lower regular number and the target, so do
                # a full resolution comparison to eliminate this case...
                if (2**twos * 3**threes * 5**fives) >= target:
                    match = log2this; result = ( twos, threes, fives )
            threes += 1
        fives += 1

    return result

print(next_regular(2**2 * 3**454 * 5**249 + 1)) # prints (142, 80, 444)

Поскольку большинство длинных вычислений с высокой точностью было исключено, gmpy не требуется, а в IDEOne приведенный выше код занимает 0,11 секунды вместо 0,48 секунды для решения endolith, чтобы найти следующее регулярное число больше 100-миллионного, как показано; требуется 0,49 секунды вместо 5,48 секунды, чтобы найти следующее регулярное число после миллиардного (следующее будет (761,572,489) после (1334,335,404) + 1), и разница будет становиться еще больше по мере увеличения диапазона, поскольку вычисления с высокой точностью становятся все длиннее для эндолита версия по сравнению с почти никакой здесь. Таким образом, эта версия может вычислить следующее регулярное число от триллионного числа в последовательности примерно за 50 секунд на IDEOne, тогда как с эндолитной версией это, вероятно, займет больше часа.

Описание алгоритма на английском языке почти такое же, как и для эндолитовой версии, но отличается следующим: 1) вычисляет логарифмическую оценку целевого значения аргумента (мы не можем использовать встроенную функцию log напрямую, так как диапазон может быть слишком велик для представления в виде 64-битного числа с плавающей запятой), 2) сравнивает значения представления журнала при определении квалифицирующих значений внутри оценочного диапазона выше и ниже целевого значения, состоящего только из двух или трех чисел (в зависимости от округления), 3 ) сравнивать значения с разной точностью только в том случае, если в пределах указанной выше узкой полосы; 4) выводит тройные индексы, а не полное длинное целое с разной точностью (будет около 840 десятичных цифр для единицы после миллиардной, в десять раз больше, чем для триллионной ), который при необходимости можно легко преобразовать в длинное значение с множественной точностью.

Этот алгоритм почти не использует память, кроме потенциально очень большого целого целого числа с множественной точностью, промежуточных сравнительных значений примерно того же размера и выходного расширения троек, если это необходимо. Этот алгоритм является усовершенствованием по сравнению с эндолитной версией, поскольку он успешно использует значения логарифма для большинства сравнений, несмотря на их недостаточную точность, и сужает диапазон сравниваемых чисел до нескольких.

Этот алгоритм будет работать для диапазонов аргументов несколько выше десяти триллионов (время вычисления несколько минут при скорости IDEOne), когда он больше не будет правильным из-за отсутствия точности в значениях представления журнала согласно обсуждению @ WillNess; чтобы исправить это, мы можем изменить представление журнала на представление логарифма «сверните свой собственный», состоящее из целого числа фиксированной длины (124 бита для примерно двойного диапазона экспоненты, хорошо для целей, содержащих более ста тысяч цифр, если один готов подождать); это будет немного медленнее из-за того, что небольшие целочисленные операции с множественной точностью будут медленнее, чем операции float64, но не намного медленнее, поскольку размер ограничен (может быть, в три раза или около того).

Теперь ни одна из этих реализаций Python (без использования C, Cython или PyPy или чего-то еще) не является особенно быстрой, поскольку они примерно в сто раз медленнее, чем реализованные на скомпилированном языке. Для справки, вот версия Haskell:

{-# OPTIONS_GHC -O3 #-}

import Data.Word
import Data.Bits

nextRegular :: Integer -> ( Word32, Word32, Word32 )
nextRegular target
  | target < 2                   = ( 0, 0, 0 )
  | target .&. (target - 1) == 0 = ( fromIntegral lg2hi - 1, 0, 0 )
  | target < 9                   = case target of
                                     3 -> ( 0, 1, 0 )
                                     5 -> ( 0, 0, 1 )
                                     6 -> ( 1, 1, 0 )
                                     _ -> ( 3, 0, 0 )
  | otherwise                    = match
 where
  lg3 = logBase 2 3 :: Double; lg5 = logBase 2 5 :: Double
  lg2hi = let cntplcs v cnt =
                let nv = v `shiftR` 31 in
                if nv <= 0 then
                  let cntbts x c =
                        if x <= 0 then c else
                        case c + 1 of
                          nc -> nc `seq` cntbts (x `shiftR` 1) nc in
                  cntbts (fromIntegral v :: Word32) cnt
                else case cnt + 31 of ncnt -> ncnt `seq` cntplcs nv ncnt
          in cntplcs target 0
  lg2tgt = let mant = if lg2hi <= 53 then target `shiftL` (53 - lg2hi)
                      else target `shiftR` (lg2hi - 53)
           in fromIntegral lg2hi +
                logBase 2 (fromIntegral mant / 2^53 :: Double)
  lg2top = (lg2tgt^3 + 2 * 6 * lg3 * lg5)**(1/3) -- for 2 numbers or so higher
  lg2btm = 2* lg2tgt - lg2top -- or two numbers or so lower
  match =
    let klmt = floor (lg2top / lg5)
        loopk k mtchlgk mtchtplk =
          if k > klmt then mtchtplk else
          let p = lg2top - fromIntegral k * lg5
              jlmt = fromIntegral $ floor (p / lg3)
              loopj j mtchlgj mtchtplj =
                if j > jlmt then loopk (k + 1) mtchlgj mtchtplj else
                let q = p - fromIntegral j * lg3
                    ( i, frac ) = properFraction q; r = lg2top - frac
                    ( nmtchlg, nmtchtpl ) =
                      if r < lg2btm || r >= mtchlgj then
                        ( mtchlgj, mtchtplj ) else
                      if 2^i * 3^j * 5^k >= target then
                        ( r, ( i, j, k ) ) else ( mtchlgj, mtchtplj )
                in nmtchlg `seq` nmtchtpl `seq` loopj (j + 1) nmtchlg nmtchtpl
          in loopj 0 mtchlgk mtchtplk
    in loopk 0 (fromIntegral lg2hi) ( fromIntegral lg2hi, 0, 0 )


trival :: ( Word32, Word32, Word32 ) -> Integer
trival (i,j,k) = 2^i * 3^j * 5^k

main = putStrLn $ show $ nextRegular $ (trival (1334,335,404)) + 1 -- (1126,16930,40)

Этот код вычисляет следующее регулярное число после миллиардного за слишком малое время, чтобы его можно было измерить, и следующее за триллионным за 0,69 секунды в IDEOne (и потенциально может работать даже быстрее, за исключением того, что IDEOne не поддерживает LLVM). Даже Джулия будет работать примерно на этой скорости Haskell после "разминки" перед JIT-компиляцией.

EDIT_ADD: код Джулии следующий:

function nextregular(target :: BigInt) :: Tuple{ UInt32, UInt32, UInt32 }
    # trivial case of first value or anything less...
    target < 2 && return ( 0, 0, 0 )

    # Check if it's already a power of 2 (or a non-integer)
    mant = target & (target - 1)

    # Quickly find next power of 2 >= target
    log2hi :: UInt32 = 0
    test = target
    while true
        next = test & 0x7FFFFFFF
        test >>>= 31; log2hi += 31
        test <= 0 && (log2hi -= leading_zeros(UInt32(next)) - 1; break)
    end

    # exit if this is a power of two already...
    mant == 0 && return ( log2hi - 1, 0, 0 )

    # take care of trivial cases...
    if target < 9
        target < 4 && return ( 0, 1, 0 )
        target < 6 && return ( 0, 0, 1 )
        target < 7 && return ( 1, 1, 0 )
        return ( 3, 0, 0 )
    end

    # find log of target, which may exceed the Float64 limit...
    if log2hi < 53 mant = target << (53 - log2hi)
    else mant = target >>> (log2hi - 53) end
    log2target = log2hi + log(2, Float64(mant) / (1 << 53))

    # log2 constants
    log2of2 = 1.0; log2of3 = log(2, 3); log2of5 = log(2, 5)

    # calculate range of log2 values close to target;
    # desired number has a logarithm of log2target <= x <= top...
    fctr = 6 * log2of3 * log2of5
    top = (log2target^3 + 2 * fctr)^(1/3) # for 2 numbers or so higher
    btm = 2 * log2target - top # or 2 numbers or so lower

    # scan for values in the given narrow range that satisfy the criteria...
    match = log2hi # Anything found will be smaller
    result :: Tuple{UInt32,UInt32,UInt32} = ( log2hi, 0, 0 ) # placeholder for eventual matches
    fives :: UInt32 = 0; fiveslmt = UInt32(ceil(top / log2of5))
    while fives < fiveslmt
        log2p = top - fives * log2of5
        threes :: UInt32 = 0; threeslmt = UInt32(ceil(log2p / log2of3))
        while threes < threeslmt
            log2q = log2p - threes * log2of3
            twos = UInt32(floor(log2q)); log2this = top - log2q + twos

            if log2this >= btm && log2this < match
                # logarithm precision may not be enough to differential between
                # the next lower regular number and the target, so do
                # a full resolution comparison to eliminate this case...
                if (big(2)^twos * big(3)^threes * big(5)^fives) >= target
                    match = log2this; result = ( twos, threes, fives )
                end
            end
            threes += 1
        end
        fives += 1
    end
    result
end
person GordonBGood    schedule 22.11.2018
comment
Прохладный. Вы запускали на нем все тесты? github.com/scipy/scipy/blob/ master / scipy / fftpack / tests / Это быстрее для небольших чисел (~ 10000) или только для больших? - person endolith; 25.11.2018
comment
@endolith, он не быстрее (не сильно отличается) для небольших аргументов, но все быстрее для больших аргументов. Спасибо за ссылку на тесты; Я использовал их, чтобы найти пару проблем в коде, которые теперь исправлены. Текущий исправленный код проходит все предоставленные тесты. - person GordonBGood; 26.11.2018

Вот еще одна возможность, о которой я только что подумал:

Если N имеет длину X бит, то наименьшее регулярное число RN будет в диапазоне
[2X-1, 2X]

например если N = 257 (двоичное 100000001), то мы знаем, что R равно 1xxxxxxxx, если R не равно в точности следующей степени 2 (512)

Чтобы сгенерировать все регулярные числа в этом диапазоне, мы можем сначала сгенерировать нечетные регулярные числа (т. Е. Кратные степени 3 и 5), затем взять каждое значение и умножить на 2 (путем битового сдвига) столько раз, сколько необходимо, чтобы получить это в этот диапазон.

В Python:

from itertools import ifilter, takewhile
from Queue import PriorityQueue

def nextPowerOf2(n):
    p = max(1, n)
    while p != (p & -p):
        p += p & -p
    return p

# Generate multiples of powers of 3, 5
def oddRegulars():
    q = PriorityQueue()
    q.put(1)
    prev = None
    while not q.empty():
        n = q.get()
        if n != prev:
            prev = n
            yield n
            if n % 3 == 0:
                q.put(n // 3 * 5)
            q.put(n * 3)

# Generate regular numbers with the same number of bits as n
def regularsCloseTo(n):
    p = nextPowerOf2(n)
    numBits = len(bin(n))
    for i in takewhile(lambda x: x <= p, oddRegulars()):
        yield i << max(0, numBits - len(bin(i)))

def nextRegular(n):
    bigEnough = ifilter(lambda x: x >= n, regularsCloseTo(n))
    return min(bigEnough)
person finnw    schedule 12.02.2012
comment
привет, я добавил сюда новый ответ, который показывает, как напрямую перечислять (i, j, k) троек в узкой окрестности log (N), что очень быстро. - person Will Ness; 20.08.2012
comment
на самом деле, это довольно близко по своему замыслу к тому, что я опубликовал, только отличается по реализации. :) - person Will Ness; 22.08.2012
comment
Это не работает для nextRegular (7), nextRegular (31), nextRegular (61) и т. Д. С ValueError: min() arg is an empty sequence - person endolith; 06.10.2013

Знаешь что? Я положу деньги на предположение, что на самом деле «тупой» алгоритм самый быстрый. Это основано на наблюдении, что следующее обычное число, как правило, не кажется намного большим, чем данный ввод. Так что просто начните считать, и после каждого приращения рефакторинг и посмотрите, нашли ли вы обычное число. Но создайте по одному потоку обработки для каждого доступного ядра, а для N ядер пусть каждый поток проверяет каждое N-е число. Когда каждый поток нашел число или пересек пороговое значение степени двойки, сравните результаты (сохраните лучшее число) и готово.

person Matt Phillips    schedule 12.02.2012
comment
Да, было бы интересно его протестировать. Полагаю, вы правы, если мы говорим о малых числах (<10000 или около того). Но по мере того, как числа становятся больше, увеличиваются и расстояния между обычными числами. Крайний пример - 48000001 (следующее обычное число - 48600000, и для его поиска потребуется 2,8 миллиона делений). - person finnw; 12.02.2012
comment
привет, я добавил сюда новый ответ, который показывает, как напрямую перечислять (i, j, k) троек в узкой окрестности log (N), что очень быстро. - person Will Ness; 20.08.2012
comment
Это основано на наблюдении, что следующее обычное число, как правило, не кажется намного большим, чем данный ввод. Я не думаю, что это хорошее предположение. По мере того, как вы поднимаетесь, они удаляются все дальше и отец. Следующее обычное число выше 50000001 - 50331648, и это только 995-е число. Я подозреваю, что создание списка обычных номеров до тех пор, пока не окажется выше вашей цели, будет быстрее. - person endolith; 30.09.2013
comment
Я тестировал алгоритм итерации и разложения по сравнению со списком генерации, пока вы не перейдете к алгоритму. Алгоритм факторизации быстрее для небольших чисел, но намного медленнее для больших чисел. 854296876 → 859963392 занимает 26 мс против 18 секунд при использовании метода факторинга. - person endolith; 01.10.2013
comment
Фактически, величина n-го числа Хэмминга равна M (n) ~ exp (n ** (1/3)), поэтому они растут экспоненциально все больше и больше друг от друга с ростом n. - person Will Ness; 06.12.2018
comment
но их логарифмы становятся все ближе и ближе . - person Will Ness; 24.03.2020

Я написал небольшую программу на C # для решения этой проблемы. Он не очень оптимизирован, но это только начало. Это решение довольно быстро работает с числами размером до 11 цифр.

private long GetRegularNumber(long n)
{
    long result = n - 1;
    long quotient = result;

    while (quotient > 1)
    {
        result++;
        quotient = result;

        quotient = RemoveFactor(quotient, 2);
        quotient = RemoveFactor(quotient, 3);
        quotient = RemoveFactor(quotient, 5);
    }

    return result;
}

private static long RemoveFactor(long dividend, long divisor)
{
    long remainder = 0;
    long quotient = dividend;
    while (remainder == 0)
    {
        dividend = quotient;
        quotient = Math.DivRem(dividend, divisor, out remainder);
    }
    return dividend;
}
person david.s    schedule 11.02.2012
comment
Подходит ли для этого C #? Не будет ли он медленнее, особенно в итерациях, чем C или C ++? - person Matt Phillips; 11.02.2012
comment
Я почти уверен, что любой программист может легко переписать это на c / c ++. C # был для меня самым быстрым способом проверить свою идею. - person david.s; 11.02.2012
comment
N_i ~ exp( i^(1/3) ) то есть промежутки между числами Хэмминга растут экспоненциально. Так что, похоже, ваш подход будет испытывать очень явное замедление по мере увеличения численности. 11 цифр - это не очень много. - person Will Ness; 21.08.2012