Найдите наименьшее регулярное число, которое не меньше 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 или поддерживая массив всех возможных значений.

Ответ 1

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

  • Рассчитайте следующую наибольшую мощность 2 после p, назовите это R. N = p.

  • Является ли N > R? Выйдите из этой темы. Является ли p состоящим только из малых простых факторов? Все готово. В противном случае перейдите к шагу 3.

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

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

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

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

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

Отредактируйте 2: Если бы это было до 30, (5, 6, 10, 15, 30) поняли, что это было лишнее, вытащили это.

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

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

Ответ 2

Можно произвести произвольно тонкий срез последовательности Хэмминга вокруг 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 s, находит верх 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 entries by their "r" component
        in ascending order, and return thus sorted array

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

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

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

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

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

Ответ 3

Здесь решение в Python, основанное на Will Ness, отвечает, но принимает некоторые ярлыки и использует чистую математику с целым числом, чтобы избежать ошибок в числовом погрешности пространства журнала:

import math

def next_regular(target):
    """
    Find the next regular number greater than or equal to target.
    """
    # Check if it 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 (100 000 000-й регулярный номер, который имеет 392 цифры )

Ответ 4

Вы хотите найти наименьшее число 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) точность. Тогда эта проблема выглядит как проблема Integer Linear programming, и вы можете попытаться ее решить, используя один из известных алгоритмов для этой NP-жесткой проблемы.

Ответ 5

EDITED/CORRECTED: Исправлены коды для прохождения scipy-тестов:

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

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

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

Этот алгоритм будет работать для диапазонов аргументов, несколько превышающих десять триллионов (несколько минутных вычислений при ставках IDEOne), когда он больше не будет корректным из-за отсутствия точности в значениях представления журнала в соответствии с обсуждением @WillNess; чтобы исправить это, мы можем изменить представление журнала на логарифмическое представление "roll-your-own", состоящее из целого числа с фиксированной длиной (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: код Julia выглядит следующим образом:

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 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

Ответ 6

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

Если N равно X битам, то наименьшее регулярное число R ≥ N будет находиться в диапазоне [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)

Ответ 7

Я написал небольшую программу С# для решения этой проблемы. Это не очень оптимизировано, но это начало. Это решение довольно быстро для чисел размером до 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;
}

Ответ 8

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